<template>
	<view class="containerBox">
		<view class="containerCanvas" v-if="tabValue == 0">
			<!-- 主容器 -->
			<view class="container" ref="container">
				<view v-for="img in images" :key="img.id" class="draggable-image" :style="{
	        left: img.left + 'px',
	        top: img.top + 'px',
	        width: img.width + 'px',
	        height: img.height + 'px',
	        zIndex: img.active ? 999 : 1
	      }" @touchstart="startDrag(img, $event)" @touchmove.prevent="onDrag($event)" @touchend="stopDrag">
					<image :src="img.src" style="width: 100%; height: 100%;" />
					<view class="resize-handle top-left" @touchstart.stop="startResize(img, $event, 'top-left')"
						@touchmove.stop.prevent="onDrag($event)"></view>
					<view class="resize-handle top-right" @touchstart.stop="startResize(img, $event, 'top-right')"
						@touchmove.stop.prevent="onDrag($event)"></view>
					<view class="resize-handle bottom-left" @touchstart.stop="startResize(img, $event, 'bottom-left')"
						@touchmove.stop.prevent="onDrag($event)"></view>
					<view class="resize-handle bottom-right" @touchstart.stop="startResize(img, $event, 'bottom-right')"
						@touchmove.stop.prevent="onDrag($event)"></view>
				</view>


				<canvas id="previewCanvas" :style="{
	        position: 'absolute',
	        left: '-9999px',
	        width: containerRect.width + 'px',
	        height: containerRect.height + 'px'
	      }" canvas-id="previewCanvas"></canvas>
			</view>

			<!-- 图片库 -->
			<view @click="deleteImage" class="delBtn" v-if="selectedImage && !selectedImage.isBackground">
				<u-icon name="trash" color="#fff" size="26"></u-icon>
			</view>

			<view class="footer">
				<view class="disFlex justify-between">
					<u-button type="primary" text="添加" @click="openimgtab" color="#2979ff"
						style="width: 25%;margin: 0;"></u-button>
					<u-button type="primary" text="保存" @click="capturePage" color="#2979ff"
						style="width: 25%;margin: 0;"></u-button>
					<u-button type="primary" text="重置" @click="resetImage" color="#e54d42"
						style="width: 25%;margin: 0;"></u-button>

				</view>
			</view>



			<!-- 控制面板 -->
			<!-- 	<view class="control-panel" v-if="selectedImage">
				<button class="delete-btn" @tap="deleteImage">删除</button>
			</view> -->
		</view>
	</view>

	<u-popup :show="imgtab" @close="imgtabclose" :zIndex="100" :round="10" :closeable="true">
		<view class="popupBottom text-center" style="padding-bottom: 50rpx;">
			<!-- <view class="pop_title">提示</view> -->
			<view v-if="!isAmplify" class="pop_title border-bottom" style="padding: 30rpx 0;"
				@click="gopage(`/pages/mine/DoorsW?type=change&isData=${bgimage?true:false}`)">门窗素材</view>
			<my-popup v-if="isAmplify" ref="mypopup" @getimg="afterdata" :value="bgimage" :deleIcon="false"
				:imgsecond="true" :isshowList="false" :count="1" :upcount="1" imgalign="right">
				<template v-slot:imgtitle>
					<view class="pop_title" style="padding: 30rpx 0;width: 100vw;">建筑背景</view>
				</template>
			</my-popup>
		</view>
	</u-popup>
</template>
<script setup>
	import {
		ref,
		reactive,
		onMounted,
		watch,
		getCurrentInstance
	} from 'vue'
	import {
		onLoad,
		onShow,
		onReady,
		onUnload
	} from '@dcloudio/uni-app'
	import {
		toast,
		loading
	} from "@/hooks/useInterface";
	import {
		async
	} from 'regenerator-runtime';



	onUnload(() => {
		uni.$off('isRefresh'); // 页面卸载时移除监听
	});

	const isAmplify = ref(true)
	onShow(() => {
		// 每次进入页面时先移除旧的监听
		uni.$off('isRefresh');
		// 重新注册监听
		uni.$on('isRefresh', function(data) {
			console.log('监听到事件来自返回的参数:' + data);
			if (data) {
				isAmplify.value = false
				addImage(data)
			}
		})
	})


	// 响应式数据
	const tabValue = ref(0)
	const images = ref([])
	const selectedImage = ref(null)
	const containerRect = ref({
		width: 0,
		height: 0
	})

	// 状态变量
	let isDragging = false
	let isResizing = false
	let currentImg = null
	let startX = 0
	let startY = 0
	let initialLeft = 0
	let initialTop = 0
	let initialWidth = 0
	let initialHeight = 0
	let resizeDirection = ''
	let originalRight = 0
	let originalBottom = 0


	const imgtab = ref(false)
	const bgimage = ref('')

	// 图片库数据
	const imageLibrary = ref([])

	// 初始化容器尺寸
	onReady(() => {
		const query = uni.createSelectorQuery()
		query.select('.container').boundingClientRect(data => {
			if (data) {
				containerRect.value = {
					width: data.width,
					height: data.height
				}
			}
		}).exec()
	})

	const gopage = (e) => {
		uni.navigateTo({
			url: e
		})
		imgtabclose()
	}
	const openimgtab = () => {
		imgtab.value = true
	}
	const imgtabclose = () => {
		imgtab.value = false
	}
	const addImg = (e) => {
		// #ifdef APP-PLUS
		console.log(plus.os.name);
		if (plus.os.name == 'Android') {
			setTimeout(() => {
				authpup.value.open()
			}, 20)
		}
		// #endif
		uni.chooseImage({
			count: 1, // 默认9
			sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
			sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
			success: (file) => {
				proxy.$useFileUpload(file).then(item => {
					console.log(item);
					addImage(item)
				})
			}
		})
	}

	const afterdata = async (e) => {
		let img = await proxy.$useFileUpload(e)
		bgimage.value = img
		addImage(img, true)
	}


	const addImage = (src, isBackground = false) => {
		console.log(src, "srcsrcsrcsrc")
		uni.getImageInfo({
			src,
			success: (res) => {
				images.value.push({
					id: Date.now(),
					src: src,
					isBackground: isBackground,
					left: 20,
					top: 50,
					// width: 100,
					// height: 100 * (res.height / res.width),
					width: isAmplify.value ? containerRect.value.width - 50 : 100,
					height: isAmplify.value ? containerRect.value.width : 100 * (res.height / res
						.width),
					active: false
				})

				isAmplify.value = false
			},
			fail: () => {
				uni.showToast({
					title: '图片加载失败',
					icon: 'none'
				})
			}
		})
	}

	// 拖拽逻辑
	const isDelShow = ref(false)
	const startDrag = (img, event) => {
		const touch = event.touches[0]
		isDragging = true
		currentImg = img
		startX = touch.clientX - img.left
		startY = touch.clientY - img.top
		selectedImage.value = img
	}

	// 缩放逻辑
	const startResize = (img, event, direction) => {
		const touch = event.touches[0]
		isResizing = true
		resizeDirection = direction
		currentImg = img
		selectedImage.value = img
		// 记录初始位置和尺寸
		initialLeft = img.left
		initialTop = img.top
		initialWidth = img.width
		initialHeight = img.height

		// 计算边界限制值
		originalRight = initialLeft + initialWidth
		originalBottom = initialTop + initialHeight

		// 记录初始触摸点坐标
		startX = touch.clientX
		startY = touch.clientY

		//记录裁剪范围
		if (selectedImage.value.isBackground) {
			console.log("建筑背景:", img)
			bgSize.height = img.newHeight
			bgSize.width = img.newWidth
		}

		console.log('Start resize:', {
			direction,
			initialLeft,
			initialTop,
			initialWidth,
			initialHeight,
			startX: touch.clientX,
			startY: touch.clientY
		})

	}

	const onDrag = (event) => {
		if (isResizing && currentImg) {
			const touch = event.touches[0]
			const deltaX = touch.clientX - startX
			const deltaY = touch.clientY - startY
			const minSize = 50 // 最小尺寸限制

			let newWidth = initialWidth
			let newHeight = initialHeight
			let newLeft = initialLeft
			let newTop = initialTop

			switch (resizeDirection) {
				case 'top-left':
					newLeft = Math.max(0, Math.min(initialLeft + deltaX, originalRight - minSize))
					newTop = Math.max(0, Math.min(initialTop + deltaY, originalBottom - minSize))
					newWidth = originalRight - newLeft
					newHeight = originalBottom - newTop
					break

				case 'top-right':
					newWidth = Math.max(minSize, initialWidth + deltaX)
					newHeight = Math.max(minSize, initialHeight - deltaY)
					newTop = initialTop + deltaY
					break

				case 'bottom-left':
					newWidth = Math.max(minSize, initialWidth - deltaX)
					newHeight = Math.max(minSize, initialHeight + deltaY)
					newLeft = initialLeft + deltaX
					break

				case 'bottom-right':
					newWidth = Math.max(minSize, initialWidth + deltaX)
					newHeight = Math.max(minSize, initialHeight + deltaY)
					break
			}

			// 应用边界限制
			newWidth = Math.min(newWidth, containerRect.value.width - newLeft)
			newHeight = Math.min(newHeight, containerRect.value.height - newTop)

			// 更新图片属性
			currentImg.width = newWidth
			currentImg.height = newHeight
			currentImg.left = newLeft
			currentImg.top = newTop


			//记录裁剪范围
			if (selectedImage.value.isBackground) {
				bgSize.height = newHeight
				bgSize.width = newWidth
			}

			console.log('Resizing:', {
				direction: resizeDirection,
				newWidth,
				newHeight,
				newLeft,
				newTop
			})
		} else if (isDragging && currentImg) {
			const touch = event.touches[0]
			const newLeft = touch.clientX - startX
			const newTop = touch.clientY - startY

			// 边界约束
			currentImg.left = Math.max(0, Math.min(
				newLeft,
				containerRect.value.width - currentImg.width
			))

			currentImg.top = Math.max(0, Math.min(
				newTop,
				containerRect.value.height - currentImg.height
			))
		}
	}

	// 停止操作
	const stopDrag = () => {
		isDragging = false
		isResizing = false
		currentImg = null
	}

	// 删除图片
	const deleteImage = () => {
		if (selectedImage.value) {
			images.value = images.value.filter(img => img.id !== selectedImage.value.id)
			selectedImage.value = null
		}
	}
	//重置
	const resetImage = () => {
		uni.showModal({
			title: '提示',
			content: '确认重置画布嘛?',
			success: function(res) {
				if (res.confirm) {
					isAmplify.value = true
					images.value = [] // 清空所有图片元素
					bgimage.value = '' // 清空背景图
					const context = uni.createCanvasContext('previewCanvas', this)
					context.clearRect(0, 0, containerRect.value.width, containerRect.value.height)
					context.draw() // 确保清空画布操作生效
				}
			}
		})
	}

	// 保存图片
	const bgSize = reactive({
		width: 0,
		height: 0
	})
	const capturePage = async () => {
		try {
			const context = uni.createCanvasContext('previewCanvas', this)

			// 清空画布
			context.clearRect(0, 0, containerRect.value.width, containerRect.value.height)

			// 绘制所有图片
			const drawPromises = images.value.map(img => {
				return new Promise((resolve) => {
					context.drawImage(
						img.src,
						img.left,
						img.top,
						img.width,
						img.height
					)
					resolve()
				})
			})

			await Promise.all(drawPromises)

			context.draw(false, () => {
				uni.canvasToTempFilePath({
					canvasId: 'previewCanvas',
					x: 20,
					y: 50,
					width: bgSize.width, // 使用背景图实际宽度
					height: bgSize.height, // 使用背景图实际高度
					destWidth: bgSize.width, 
					destHeight: bgSize.height,
					quality: 1,
					success: (res) => {

						// #ifdef H5
						saveFlieImage(res.tempFilePath)
						// #endif

						// #ifdef APP-PLUS
						var path = plus.io.convertLocalFileSystemURL(res.tempFilePath); //绝对路径
						var fileReader = new plus.io.FileReader();
						fileReader.readAsDataURL(path);
						fileReader.onloadend = function(evt) { //读取文件成功完成的回调函数
							console.log(evt.target.result); //输出base64内容
							saveFlieImage(evt.target.result)
						}
						// #endif

					},
					fail: (err) => {
						console.error('生成临时文件失败:', err)
						uni.showToast({
							title: '生成图片失败',
							icon: 'none'
						})
					}
				})
			})
		} catch (e) {
			console.error('保存出错:', e)
			uni.showToast({
				title: '保存失败',
				icon: 'none'
			})
		}
	}


	const saveFlieImage = (savedFilePath) => {
		// console.log(savedFilePath)
		processBase64Image(savedFilePath).then(result => {
			
		});
	}



	const processBase64Image = async (base64String) => {
		try {
			// 分割 Base64 字符串
			const [header, data] = base64String.split(';base64,');
			const mimeType = header.split(':')[1];
			const rawData = atob(data);

			// 平台判断
			let tempFilePaths = [];
			// #ifdef H5
			// H5 环境:使用 Blob
			const blob = new Blob([new Uint8Array(rawData.length).map((_, i) => rawData.charCodeAt(i))], {
				type: mimeType
			});
			const blobURL = URL.createObjectURL(blob);
			tempFilePaths = [blobURL];
			// #endif

			// #ifdef MP-WEIXIN
			// 微信小程序:写入临时文件
			const fs = wx.getFileSystemManager();
			const filePath = `${wx.env.USER_DATA_PATH}/temp_${Date.now()}.${mimeType.split('/')[1]}`;
			fs.writeFileSync(filePath, rawData, 'base64');
			tempFilePaths = [filePath];
			// #endif

			// #ifdef APP
			// App 环境:使用原生 API 保存文件
			const fileName = `image_${Date.now()}.${mimeType.split('/')[1]}`;
			const url = `_doc/${fileName}`;
			const bitmap = new plus.nativeObj.Bitmap('temp');
			await new Promise((resolve, reject) => {
				bitmap.loadBase64Data(base64String, () => {
					bitmap.save(url, {
						overwrite: true
					}, () => {
						bitmap.clear();
						resolve();
					}, (err) => reject(err));
				}, (err) => reject(err));
			});
			tempFilePaths = [url];
			// #endif

			// 获取图片尺寸(H5/小程序/APP 通用)
			const getImageDimensions = (base64) => {
				return new Promise(async (resolve) => {
					let dimensions = {
						width: 0,
						height: 0
					};

					// 平台判断
					// #ifdef H5
					// H5 环境:使用 Image 对象
					const img = new Image();
					img.onload = () => resolve({
						width: img.width,
						height: img.height
					});
					img.onerror = () => resolve(dimensions);
					img.src = base64;
					// #endif

					// #ifdef MP-WEIXIN
					// 微信小程序:使用 wx.getImageInfo
					try {
						const res = await wx.getImageInfo({
							src: base64
						});
						dimensions = {
							width: res.width,
							height: res.height
						};
					} catch (e) {
						console.error('获取图片尺寸失败:', e);
					}
					resolve(dimensions);
					// #endif

					// #ifdef APP
					// App 环境:使用 uni.getImageInfo
					try {
						const res = await uni.getImageInfo({
							src: base64
						});
						dimensions = {
							width: res.width,
							height: res.height
						};
					} catch (e) {
						console.error('获取图片尺寸失败:', e);
					}
					resolve(dimensions);
					// #endif
				});
			};

			const dimensions = await getImageDimensions(base64String);

			return {
				tempFilePaths,
				tempFiles: [{
					path: tempFilePaths[0],
					type: mimeType,
					size: rawData.length,
					base64: base64String,
					name: `image_${Date.now()}.${mimeType.split('/')[1]}`,
					...dimensions
				}],
				errMsg: "chooseImage:ok"
			};
		} catch (error) {
			console.error('处理Base64时出错:', error);
			throw error;
		}
	};
</script>
<style lang="scss">
	/* 容器样式 */
	// .container {
	// 	position: fixed;
	// 	top: 0;
	// 	left: 0;
	// 	width: 100vw;
	// 	height: 100vh;
	// 	background: #ffffff;
	// 	touch-action: none;
	// }







	page {
		width: 100%;
		height: 100%;
	}


	.containerBox {
		width: 100%;
		/* #ifdef H5 */
		height: calc(100% - 110rpx);
		/* #endif */
		/* #ifdef APP-PLUS */
		height: calc(100% - 178rpx);
		/* #endif */
		display: flex;
		flex-direction: column;
		background-color: #fff;
		overflow: hidden;

		.containerCanvas {
			flex: 1;
			display: flex;
			flex-direction: column;

			.container {
				width: 100%;
				flex: 1;
				overflow: hidden;
				background: white;
				display: flex;
				flex-direction: column;
				position: relative;

				/* 可拖拽图片样式 */
				.draggable-image {
					position: absolute;
					touch-action: none;
					border: 2rpx solid #eee;
				}

				/* 缩放手柄样式 */
				.resize-handle {
					position: absolute;
					width: 40rpx;
					height: 40rpx;
					background: #fff;
					border: 4rpx solid #2196F3;
					border-radius: 50%;

					&.top-left {
						top: -20rpx;
						left: -20rpx;
					}

					&.top-right {
						top: -20rpx;
						right: -20rpx;
					}

					&.bottom-left {
						bottom: -20rpx;
						left: -20rpx;
					}

					&.bottom-right {
						bottom: -20rpx;
						right: -20rpx;
					}
				}
			}

			.footer {
				width: 100%;
				flex-shrink: 0;
				padding: 20rpx;
				background: rgba(240, 240, 240, 0.95);

				/* 图片库样式 */
				.image-library {
					width: 100%;
					height: 160rpx;
					white-space: nowrap;
					margin-top: 20rpx;

					.library-image {
						display: inline-block;
						width: 160rpx;
						height: 160rpx;
						margin-right: 20rpx;
						border-radius: 16rpx;
						overflow: hidden;
						border: 2rpx solid #ddd;

						image {
							width: 100%;
							height: 100%;
						}
					}
				}
			}





		}


		// 素材区
		.canvasBox {
			flex: 1;
			width: 100%;
			display: flex;
			flex-direction: column;

			.navTab {
				flex-shrink: 0;
				width: 100%;
				display: flex;
				justify-content: space-between;
				width: 100%;
				height: 70rpx;
				background-color: #fff;

				.itemName {
					width: 50%;
					text-align: center;
					line-height: 70rpx;
					border-bottom: 4rpx solid #fff;
				}

				.active {
					border-bottom: 4rpx solid #009CFF;
					color: #0081ff;
				}
			}

			.navBox {
				flex: 1;
				width: 100%;
			}
		}
	}


	.imageBoxShow {
		width: 600rpx;
		height: 700rpx;
		box-sizing: border-box;
		padding: 30rpx;
		border-radius: 10rpx;

		.imageYu {
			width: 100%;
			height: 540rpx;
			display: block;
			margin: 0 auto;
		}

		.imageBtn {
			display: flex;
			box-sizing: border-box;
			margin-top: 20rpx;
		}
	}

	.popupBottom {
		width: 100%;
		/* height: 650rpx; */
		background: #fff;
		box-sizing: border-box;
		border-radius: 20rpx;
		padding: 40rpx 0rpx;
		font-size: 28rpx;
		position: relative;
		margin: auto;
	}

	.pop_title {
		font-size: 30rpx;
		font-weight: 500;
	}

	.delBtn {
		position: fixed;
		right: 10rpx;
		bottom: 20%;
		padding: 20rpx;
		z-index: 9999;
		background: #f56c6c;
		color: #fff;
		border-radius: 50%;
	}
</style>

更多推荐