效果图

所有内容皆可调整。 角度,字体大小颜色等。

下面代码全部粘贴到.vue页面,找到对应方法直接使用即可,uv-upload标签使用的是:https://www.uvui.cn/components/code.html

页面内import导入的页面及查询信息接口方法报错自行删除即可,主要用到的方法为:function drawDiagonalWatermark、const addWatermark、function drawTextWrap、let canvasUrl = await addWatermark(lists[i].url)//增加水印

<template>
	<view class="container">
		<uni-nav-bar @clickLeft="blackfack()" :backgroundColor="rgba"  
		:statusBar="true" :border="false" fixed 
		left-icon="left" title="服务凭证" />
		
		<view class="beijing"></view>
		<view class="sdign ">
			<view >
				<view class="fontSize34">1.服务开始前照片</view>
				<view class="color999 bottom_bj fontSize30">请上传服务开始前照片,最少上传1张,最多5张</view>
				<uv-upload :fileList="fileLists.fileListQ"
					@afterRead="afterRead" 
					@delete="deletePic" 
					name="Q" 
					:deletable="!getTagStyle(sid)"
					:disabled="getTagStyle(sid)"
					multiple
					:maxCount="5"
					:sizeType="['compressed']" 
					 accept="image" 
					 :capture="['album', 'camera']" 
					 width="75" 
					 height="75" 
					 :uploadSize="38">
				</uv-upload>
				<view @click="seeImg(1)" class="textAlignCenter color-006AEB shili_paishe fontSize32">查看拍摄示例</view>
			</view>
			<view v-if="serviceImageStatus == 'SERVICE_END_IMAGE' || (sid == 'IN_SERVICE' && !btntyor)">
				<view class="fontSize34" style="margin-top: 30rpx;">2.服务完成后照片</view>
				<view class="color999 bottom_bj fontSize30">请上传服务完成后照片,最少上传1张,最多5张</view>
				<uv-upload :fileList="fileLists.fileListH"
					@afterRead="afterRead" 
					@delete="deletePic" 
					name="H" 
					:deletable="!getTagStyle(sid)"
					:disabled="getTagStyle(sid)" 
					multiple
					:maxCount="5"
					:sizeType="['compressed']" 
					 accept="image" 
					 :capture="['album', 'camera']" 
					 width="75" 
					 height="75" 
					 :uploadSize="38">
				</uv-upload>
				<view @click="seeImg(2)" class="textAlignCenter color-006AEB shili_paishe fontSize32">查看拍摄示例</view>
			</view>
			
		</view>
		
		<uni-popup ref="popup" type="bottom" :is-mask-click="false">
			<!-- 服务前示例 -->
			<groupFWQ v-if="monyExampleType == 1" ref="bComponentRef" @callParent="close_discount"></groupFWQ>
			<!-- 服务后示例 -->
			<groupFWH  v-if="monyExampleType == 2" ref="groupCouponCodeRef" @callParent="close_discount"></groupFWH>
		</uni-popup>
		
		<!-- 水印图片上传canvas -->
		<view style="width: 0;height: 0;overflow: hidden;position:fixed;left: 200%;">
			<canvas canvas-id="firstCanvas" class="canvas" :style="{ 'width': canvas_w + 'px', 'height': canvas_h + 'px' }"></canvas>
		</view>
		
		<view v-if="!getTagStyle(sid)" class="flexBottom" :style="'padding-bottom:'+safeBottom">
			<button @click="goNext()" class="login-btn">确认提交</button>
		</view>
	</view>
</template>


<script setup>
	import {
		reactive,
		ref,
		getCurrentInstance,
	} from "vue";
	import {
		onLoad,onShow,onPageScroll
	} from '@dcloudio/uni-app'
	import QRCode from '/common/qrcode.js';
	import utils from "@/common/utils.js"
	import time from '@/common/time.js'
	import http from '@/common/http.js'
	import groupFWQ from "./components/groupFWQ.vue"
	import groupFWH from "./components/groupFWH.vue"
	
	const globalProperties = getCurrentInstance().appContext.config.globalProperties; // 获取全局挂载
	const paddingTop = ref(44)
	const safeBottom = ref(0)
	const popup = ref(false)
	const monyExampleType = ref()//1 服务前示例,2服务后示例
	const fileLists = reactive({
	  fileListQ: [], 
	  fileListH: [], 
	});
	const canvas_w = ref(0)
	const canvas_h = ref(0)
	const canvas_img = ref("")
	const locationInfo = ref("")
	const rgba= ref('rgba(0,0,0,0)') 
	const showtype= ref(0) 
	const item = ref("")
	const eid = ref("")
	const oid = ref("")
	const cid = ref("")
	const pname = ref("")
	const cname = ref("")
	const type = ref("")
	const sid = ref("")
	const path = ref("")
	const serviceImageStatus = ref("")//未上传-NO_SERVICE,服务前上传SERVICE_START_IMAGE,服务后上传SERVICE_END_IMAGE
	const pid = ref("")
	const did = ref("")
	const ocid = ref("")
	const orderDiscountPrice = ref("")
	const btnIs = ref("")
	const phofwid = ref("")//服务图片表id 
	const btntyor = ref("")//为1 表示从详情页面查看进入,而不是按钮
	
	
	
	onLoad((optison) => {
		let gewin = uni.getWindowInfo();//获取窗口信息
		paddingTop.value = gewin.statusBarHeight + 2
		//设置ios 底部安全距离
		safeBottom.value = gewin.safeAreaInsets.bottom>0?gewin.safeAreaInsets.bottom+"px":"30rpx";
		path.value = optison.path
		serviceImageStatus.value = optison.sistatus
			
		let a = uni.getStorageSync("completePhoneinfo");
		
		item.value = a.item
		eid.value = a.eid
		oid.value = a.oid
		cid.value = a.cid
		pname.value = a.pname
		cname.value = a.cname
		type.value = a.type
		sid.value = a.sid
		pid.value = a.pid
		did.value = a.did
		ocid.value = a.ocid
		orderDiscountPrice.value = a.orderDiscountPrice
		btnIs.value = a.btnIs
		btntyor.value = a.btntyor
		
		if(serviceImageStatus.value == "SERVICE_START_IMAGE"){
			//需要 服务完成上传图片
			if(sid.value == 'IN_SERVICE'){
				//"完成服务"按钮进入的要设置1
				showtype.value = 1
			}else{
				showtype.value = 0
			}
			
		}else if(serviceImageStatus.value == "SERVICE_END_IMAGE"){
			showtype.value = 2
		}
		
		globalProperties.$api.orderServiceImagegetDetail({
			orderHeaderId: oid.value,//	订单头ID		true	
		}).then(response => {
			console.log(111,response)
			phofwid.value = response.data.id
			fileLists.fileListQ = response.data.serviceStartImages || []
			fileLists.fileListH = response.data.serviceEndImages || []
		}).catch(error => {
			uni.showToast({
				title: error || '已传图片查询失败!',
				icon: 'none',
				duration: 3000,
			})
		});
	})
	onShow(() => {
		uni.getLocation({
			type: 'gcj02', 
			success(res) {
				//2025-05-09日 定没有定位信息也要接着往下走
				if(res.longitude && res.latitude){
					utils.getRegeo(res.longitude, res.latitude).then(info => {
						uni.hideLoading();
						locationInfo.value = info
					})
					
				}else{
					uni.hideLoading();
				}
				 
			}, 
			fail(res) { 
				uni.hideLoading();
			}
		});
	})
	//监听页面滚动,设置顶部导航栏颜色
	onPageScroll((e) => {
	    handleScroll(e.scrollTop)
	})
	//团购券金额示例关闭
	const close_discount = (e) => {
		popup.value.close()
	}
	//false 不可编辑
	const getTagStyle= (statusId) => {
		if (["PAID", "COMPLETED", "UNDER_REVIEW", "REVIEW_FAILED", "PLATFORM_CANCELED", "CUSTOMER_CANCELED","ENGINEER_CANCELED"].includes(statusId)) {
			return true
		} else {
			return false
		}
	}
	//设置导航栏背景样式
	const handleScroll= (top) => {
		// 获取当前的滚动距离 top
	    if (top > 5) {
			let opacity = top / 300
			opacity = opacity > 1 ? 1 : opacity
		
			// 根据滚动距离设置导航栏背景色
			let alpha = Math.min(top / 100, 1); // 控制透明度的变化,200为阈值
			rgba.value =`rgba(255, 255, 255, ${alpha})`
			
			//rgba.value = `rgba(255, 255, 255, 1)`;
	    } else {
			rgba.value = 'rgba(0,0,0,0)'
	    }
	}
	//关闭
	const close = () => {
		//this.$refs.popup.open() // 打开vue2
		popup.value.close()
	}
	//打开
	const seeImg = (type) => {
		monyExampleType.value = type
		popup.value.open()
	}
	
	// 删除图片
	const deletePic = (event) => {
		const listName = `fileList${event.name}`;
		fileLists[listName].splice(event.index, 1);
	}
	// 新增图片
	const afterRead = async (event) => {
		// 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式
		let lists = [].concat(event.file);
		const listName = `fileList${event.name}`; // 动态生成属性名,如 fileListGZ
	
		let fileListLen = fileLists[listName].length;
		lists.map((item) => {
			fileLists[listName].push({
				...item,
				status: "uploading",
				message: "上传中",
			});
		});
		for (let i = 0; i < lists.length; i++) {
			let canvasUrl = await addWatermark(lists[i].url)//增加水印
			//H5是base64 要处理成blob后上传
			if(canvasUrl && canvasUrl.includes("data:image/jpeg;base64")){
				const arr = canvasUrl.split(',');
				const mime = arr[0].match(/:(.*?);/)[1];
				const bstr = atob(arr[1]);
				let n = bstr.length;
				const u8arr = new Uint8Array(n);
				while (n--) {
				  u8arr[n] = bstr.charCodeAt(n);
				}
				const blob = new Blob([u8arr], { type: mime });
				const blobUrl = URL.createObjectURL(blob);
				var result = await uploadFilePromise(blobUrl);//oss上传
			}else{
				var result = await uploadFilePromise(canvasUrl);//oss上传
			}
			
			let item = fileLists[listName][fileListLen];
		
			fileLists[listName].splice(
				fileListLen,
				1,
				Object.assign(item, {
					status: "success",
					message: "",
					fileId: result.data[0].id,
					name: result.data[0].name,
					url: result.data[0].url,
				})
			);
			fileListLen++;
		}
	}
	const uploadFilePromise = (url) => {
		return utils.uploadPhoto(url, {
			serviceModule: "serviceImages:" + time.getToday(4),
			submit: 1,
			fileList: [url]
		})
	}
	//服务前图片上传
	const orderServiceImageinsert = (src) => {
		console.log(1111,fileLists.fileListQ)
		if(fileLists.fileListQ.length == 0) return globalProperties.$test.toast('请上传服务前照片!', 3000);
		//判断是否存在上传中,上传失败的图片,存在不让点击提交
		let forIs = false;
		let arr = [];
		console.log(fileLists.fileListQ)
		for (var i = 0; i < fileLists.fileListQ.length; i++) {
			arr.push(fileLists.fileListQ[i].fileId)
			if(fileLists.fileListQ[i].status == "uploading"){
				forIs = true
				globalProperties.$test.toast('请等待图片上传完成!', 3000);
				break 
			}else if(fileLists.fileListQ[i].status == "failed"){
				forIs = true
				globalProperties.$test.toast('图片上传失败请重试!', 3000);
				break 
			}
		}
		if(forIs)return
		
		return new Promise((resolve, reject) => {
			globalProperties.$api.orderServiceImageinsert({
				orderHeaderId: oid.value,//	订单头ID		true	
				serviceStartImages: JSON.stringify(arr),//	服务前图片列表,存储最多5张图片的id(JSON格式)
			}).then(response => {
				resolve(true)//成功
			}).catch(error => {
				uni.showToast({
					title: error || '操作失败!',
					icon: 'none',
					duration: 3000,
				})
				reject(false)//失败
			});
		});
	}
	//服务后图片上传
	const orderServiceImageupdate = (src) => {
		if(fileLists.fileListQ.length == 0) return globalProperties.$test.toast('请上传服务前照片!', 3000);
		//serviceImageStatus.value == "SERVICE_START_IMAGE" && sid.value == 'IN_SERVICE' && btntyor.value != 1代表当前服务凭证为服务前图片已上传,订单状态为服务中,是通过“完成服务”按钮进入
		//serviceImageStatus.value == "SERVICE_END_IMAGE"为服务后图片已经上传
		if((serviceImageStatus.value == "SERVICE_START_IMAGE" && sid.value == 'IN_SERVICE' && btntyor.value != 1) || serviceImageStatus.value == "SERVICE_END_IMAGE"){
			if(fileLists.fileListH.length == 0) return globalProperties.$test.toast('请上传服务后照片!', 3000);
		}
		//判断是否存在上传中,上传失败的图片,存在不让点击提交
		let forIs = false;
		let arr = [];
		for (var i = 0; i < fileLists.fileListQ.length; i++) {
			arr.push(fileLists.fileListQ[i].fileId)
			if(fileLists.fileListQ[i].status == "uploading"){
				forIs = true
				globalProperties.$test.toast('请等待图片上传完成!', 3000);
				break 
			}else if(fileLists.fileListQ[i].status == "failed"){
				forIs = true
				globalProperties.$test.toast('图片上传失败请重试!', 3000);
				break 
			}
		}
		if(forIs) return
		
		let forIsB = false;
		let arrB = [];
		for (var i = 0; i < fileLists.fileListH.length; i++) {
			arrB.push(fileLists.fileListH[i].fileId)
			if(fileLists.fileListH[i].status == "uploading"){
				forIsB = true
				globalProperties.$test.toast('请等待图片上传完成!', 3000);
				break 
			}else if(fileLists.fileListH[i].status == "failed"){
				forIsB = true
				globalProperties.$test.toast('图片上传失败请重试!', 3000);
				break 
			}
		}
		if(forIsB) return
		
		return new Promise((resolve, reject) => {
			globalProperties.$api.orderServiceImageupdate({
				id: phofwid.value,//	服务图片表id		true	
				orderHeaderId: oid.value,//	订单ID		true	
				serviceStartImages: JSON.stringify(arr),//	服务前图片列表,存储最多5张图片的id(JSON格式)		false	
				serviceEndImages: arrB.length == 0?null:JSON.stringify(arrB),//	服务后图片列表,存储最多5张图片的id(JSON格式)
			}).then(response => {
				resolve(true)//成功
			}).catch(error => {
				uni.showToast({
					title: error || '操作失败!',
					icon: 'none',
					duration: 3000,
				})
				reject(false)//失败
			});
		});
	}
	//下一步
	const goNext = async (event) => {
		var insertO = ""
		if(btntyor.value == 1){
			console.log(33223)
			insertO = await orderServiceImageupdate()
		}else{
			if(showtype.value == 0){
				console.log(211234)
				insertO = await orderServiceImageinsert()
			}
			if(showtype.value == 1){
				console.log(121212)
				insertO = await orderServiceImageupdate()
			}
		}
		console.log(222,insertO)
		if(!insertO){
			return
		}
		//订单详情-查看进来直接返回上衣页面
		if(btntyor.value == 1){
			blackfack()
			return
		}
		
		
		//进入报价
		if(item.value.quotationStatus == "UNDERREVIEW" || item.value.quotationStatus == "REVIEWED_PASS" || item.value.quotationStatus == "REVIEWED_REJECT"){
			item.value.type_type = "edit"
		}
		if(type.value){
			item.value.type_type = type.value
		}
		let pid = item.value.provinceId;
		let did = item.value.districtId;
		let ocid = item.value.openCityId;
		let qs = item.value.quotationStatus;
		
		if(path.value == "orderlistindex" && showtype.value != 1){
			uni.redirectTo({
				url: "/pages/standardMeter/schemeQuotation/schemeQuotation?oid=" + oid.value +"&sid=" + sid.value +"&eid=" + eid.value +"&cid=" + cid.value+"&pname=" + pname.value+"&cname=" + cname.value+"&type=" + type.value+"&pid=" + pid+"&did=" + did+"&ocid=" + ocid+"&qs="+qs
			})
			return 
		}
		//服务完成上传图片后 跳转好评贴花
		if(showtype.value == 1){
			//判断订单  开城时是否开启好评贴花
			globalProperties.$api.getInfoCityTH({
				id: item.value.openCityId
			}).then(response => {
				if(response.data.channelFavorableComment == 1){
					uni.setStorageSync("orderinfoS", item.value);
					uni.redirectTo({
						url:"/pages/goodReview/goodReview?oid=" + oid.value + "&sid=" + sid.value+"&pid=" + pid+"&did=" + did+"&ocid=" + ocid 
					})
				}else{
					uni.redirectTo({
						url:"/pages/orderDetails/queRenCcount?oid=" + oid.value + "&eid=" + eid.value + "&orderDiscountPrice=" + orderDiscountPrice.value + "&cid=" + cid.value + "&pname=" + pname.value + "&cname=" + cname.value + "&sid=" + sid.value + "&qm=" + item.value.quotationMethod
						  + "&opencityid=" + item.value.openCityId + "&opencityname=" + item.value.openCityName+"&pid=" + pid+"&did=" + did+"&ocid=" + ocid 
					})
				}
				// channelFavorableComment	渠道好评:0关闭 1开启	integer(int32)	
			}).catch(error => {
				uni.redirectTo({
					url:"/pages/orderDetails/queRenCcount?oid=" + oid.value + "&eid=" + eid.value + "&orderDiscountPrice=" + orderDiscountPrice.value + "&cid=" + cid.value + "&pname=" + pname.value + "&cname=" + cname.value + "&sid=" + sid.value + "&qm=" + item.value.quotationMethod
					  + "&opencityid=" + item.value.openCityId + "&opencityname=" + item.value.openCityName+"&pid=" + pid+"&did=" + did+"&ocid=" + ocid 
				})
			});
			return 
		}
		
		
		if(item.value.type_type == "edit"){
			//quotationMethod	报价方式 1.一口价 2.计价器(收取上门费) 3.自定义报价,4计价器
			if(item.value.quotationMethod == 3){
				//自定义报价
				uni.redirectTo({
					url: "/pages/quotation/customQuotation/customQuotation?oid=" + oid.value +"&sid=" + sid.value +"&eid=" + eid.value +"&cid=" + cid.value+"&pname=" + pname.value+"&cname=" + cname.value+"&type=" + type.value
				})
			}else if(item.value.quotationMethod == 1 || item.value.quotationMethod == 2){//一口价
				uni.redirectTo({
					url: "/pages/schemeQuotation/schemeQuotation?oid=" + oid.value +"&sid=" + sid.value +"&eid=" + eid.value +"&cid=" + cid.value+"&pname=" + pname.value+"&cname=" + cname.value+"&type=" + type.value+"&pid=" + pid+"&did=" + did+"&ocid=" + ocid
				})
			}else{//计价器
				uni.redirectTo({
					url: "/pages/standardMeter/schemeQuotation/schemeQuotation?oid=" + oid.value +"&sid=" + sid.value +"&eid=" + eid.value +"&cid=" + cid.value+"&pname=" + pname.value+"&cname=" + cname.value+"&type=" + type.value+"&pid=" + pid+"&did=" + did+"&ocid=" + ocid+"&qs="+qs
				})
			}
		}else{
			uni.redirectTo({
				url: "/pages/standardMeter/schemeQuotation/schemeQuotation?oid=" + oid.value +"&sid=" + sid.value +"&eid=" + eid.value +"&cid=" + cid.value+"&pname=" + pname.value+"&cname=" + cname.value+"&type=" + type.value+"&pid=" + pid+"&did=" + did+"&ocid=" + ocid+"&qs="+qs
			})
			//this.$refs.popup_discount.open()
		}
	}
	const blackfack = () =>{
		uni.navigateBack({
			delta: 1
		})
	}
	/**
	 * 绘制倾斜且充满指定区域的文字水印
	 * @param {Object} ctx - Canvas 上下文(小程序/H5/App 通用)
	 * @param {String} text - 水印文字
	 * @param {Object} area - 水印区域 {x: 起始X, y: 起始Y, width: 区域宽, height: 区域高}
	 * @param {Number} angle - 倾斜角度(如-30,负数向左斜,正数向右斜)
	 * @param {Object} style - 样式 {fontSize: 字号, color: 颜色, alpha: 透明度, spacing: 文字间距}
	 */
	function drawDiagonalWatermark(ctx, text, area, angle = -30, style = {}) {
	  // 默认样式
	  const {
	    fontSize = 20,
	    color = '#ffffff',
	    alpha = 0.5, // 水印透明度(0-1)
	    spacing = 300 // 文字间距(越小越密)
	  } = style;
	
	  // 1. 适配多端 API(小程序 vs H5/App)
	  const isMiniProgram = ['devtools', 'ios', 'android'].includes(uni.getSystemInfoSync().platform);
	  if (isMiniProgram) {
	    ctx.setFontSize(fontSize);
	    ctx.setFillStyle(color);
	    ctx.setGlobalAlpha(alpha); // 小程序设置透明度
	  } else {
	    ctx.font = `${fontSize}px sans-serif`;
	    ctx.fillStyle = color;
	    ctx.globalAlpha = alpha; // H5/App 设置透明度
	  }
	
	  // 2. 计算文字宽高(用于间距计算)
	  const textWidth = ctx.measureText(text).width;
	  const textHeight = fontSize * 1.2; // 近似文字高度(字号的1.2倍)
	
	  // 3. 角度转弧度(rotate 接收弧度值)
	  const radian = (Math.PI / 180) * angle;
	
	  // 4. 保存当前画布状态(旋转后恢复,避免影响后续绘制)
	  ctx.save();
	
	  // 5. 循环绘制文字,铺满指定区域
	  // 计算循环范围(超出区域1倍,确保完全铺满)
	  const rangeX = area.width + spacing;
	  const rangeY = area.height + spacing;
	  
	  // 双层循环:控制行/列
	  for (let x = -rangeX; x < rangeX; x += spacing) {
	    for (let y = -rangeY; y < rangeY; y += spacing) {
	      // 重置画布状态(避免多次旋转叠加偏移)
	      ctx.save();
	      // 移动画布到当前绘制点(旋转中心)
	      const drawX = area.x + x;
	      const drawY = area.y + y;
	      ctx.translate(drawX, drawY);
	      // 旋转画布(核心:实现文字倾斜)
	      ctx.rotate(radian);
	      // 绘制文字(旋转后,文字以 (0,0) 为基准绘制)
	      ctx.fillText(text, 0, 0);
	      // 恢复画布状态(取消当前旋转/平移)
	      ctx.restore();
	    }
	  }
	
	  // 6. 恢复初始画布状态
	  ctx.restore();
	}
	/**
	 * 添加水印
	 * @param {string} src 图片地址
	 */
	const addWatermark = (src) => {
		var that =this;
		return new Promise((resolve, reject) => {
			//定义canvas
			var ctx = uni.createCanvasContext('firstCanvas');
			//获取图片信息
			uni.getImageInfo({
				src:src,
				success: res => {
					let width = res.width, height = res.height;
					//设置主页面canvas标签宽高
					canvas_w.value = width;
					canvas_h.value = height;
					//获取当前时间
					let newDate = new Date();
					let year = newDate.getFullYear() //年
					let month = newDate.getMonth() + 1 //月
					let day = newDate.getDate() //日
					var hour = newDate.getHours()
					var minute = newDate.getMinutes()
					var second = newDate.getSeconds()
					if (second < "10") {
						second = '0' + second
					}
					if (minute < "10") {
						minute = '0' + minute
					}
					var photographTime = year + '-' + month + '-' + day + ' ' +hour + ':' +minute + ':' +second
					var roleNameInfo = year + '年' + month + '月' + day + '日 ' +hour + ':' +minute + ':' +second
					//创建canvas
					ctx.drawImage(res.path, 0, 0, width, height); //先画出图片
					//将声明的时间放入canvas
					//ctx.setFontSize(22) //注意:设置文字大小必须放在填充文字之前,否则不生效
					//ctx.setFillStyle('white'); //white
					var padding = 20;
					var textMaxWidth = width - 2 * padding;
					// 绘制换行文字
					let textA = roleNameInfo;
					drawTextWrap(
					  ctx,        // 上下文
					  textA,       // 文本内容
					  padding,    // X起始坐标(左内边距)
					  height - 100,         // Y起始坐标
					  textMaxWidth, // 最大宽度
					  25,         // 行高
					  '#fff',     // 文字颜色
					  22          // 字体大小
					);
					if(locationInfo.value){
						try {
							// 绘制换行文字
							let textB = locationInfo.value.data.regeocode.formatted_address;
							drawTextWrap(
							  ctx,        // 上下文
							  textB,       // 文本内容
							  padding,    // X起始坐标(左内边距)
							  height - 60,         // Y起始坐标
							  textMaxWidth, // 最大宽度
							  25,         // 行高
							  '#fff',     // 文字颜色
							  22         // 字体大小
							);
						} catch (error) {
							//TODO handle the exception
						}
						
					}
					//区域绘制水印 斜向水印
					drawDiagonalWatermark(ctx, http.watermark_text, {
					  x: 0,
					  y: 0,
					  width: width,
					  height: height
					}, -30);
					//ctx.fillText(roleNameInfo, 30, height - 100);
					//ctx.fillText("长春市数控刀具诶对接人几年不见阿萨德饭饭房东东方今典你方便", 30, height - 60);
					setTimeout(function(){
						ctx.draw(false, () => {
							uni.canvasToTempFilePath({
								fileType: 'jpg',
								quality: 1,
								canvasId: 'firstCanvas',
								complete: com => {},
								success: function(ress) {
									let path = ress.tempFilePath;
									// 在H5平台下,tempFilePath 为 base64
									canvas_img.value = path;//base64
									resolve(path)
								},
								fail: (err) => {
									uni.hideLoading()
									console.log(err)
									reject(err)
								}
							})
						});
					}, 200);
				}
			});
		});
	}
	/**
	 * Canvas 文字自动换行绘制函数
	 * @param {Object} ctx - canvas 上下文对象
	 * @param {String} text - 需要绘制的文本
	 * @param {Number} x - 绘制起始X坐标
	 * @param {Number} y - 绘制起始Y坐标
	 * @param {Number} maxWidth - 最大宽度(超出换行)
	 * @param {Number} lineHeight - 行高
	 * @param {String} color - 文字颜色
	 * @param {Number} fontSize - 字体大小
	 */
	function drawTextWrap(ctx, text, x, y, maxWidth, lineHeight, color = '#000', fontSize = 14) {
	  // 设置字体样式
	  ctx.setFontSize(fontSize);
	  ctx.setFillStyle(color);
	  
	  // 处理空文本
	  if (!text) return;
	  
	  let chr = text.split(''); // 将文本拆分为单个字符
	  let temp = ''; // 存储当前行的文字
	  let row = []; // 存储所有行的文字
	  
	  // 逐字符计算宽度,拆分文本到不同行
	  for (let i = 0; i < chr.length; i++) {
	    // 测试当前临时文本 + 下一个字符的宽度
	    if (ctx.measureText(temp + chr[i]).width > maxWidth) {
	      row.push(temp); // 超出宽度,将当前临时文本加入行数组
	      temp = chr[i]; // 重置临时文本为当前字符
	    } else {
	      temp += chr[i]; // 未超出,继续拼接字符
	    }
	  }
	  // 把最后一行文字加入数组
	  row.push(temp);
	  
	  // 逐行绘制文字
	  for (let i = 0; i < row.length; i++) {
	    ctx.fillText(row[i], x, y + i * lineHeight);
	  }
	  
	  // 返回总行数(方便后续计算整体高度)
	  return row.length;
	}
</script>

<style lang="scss" scoped>
	
	.flexBottom{
		width: 100%;
		padding: 30rpx;
		box-sizing: border-box;
		background-color: #fff;
		position: fixed;
		left: 0;
		bottom: 0;
		z-index: 90;
		display: flex;
		justify-content: space-between;
	}
	.login-btn {
		width: 100%;
		height: 90rpx;
		line-height: 90rpx;
		background: linear-gradient(90deg, #FF6B15 0%, #FF8C3E 100%);
		color: white;
		border-radius: 32px;
		font-size: 16px;
		border: none;
	}
	uni-button:after{
		border: 0px solid #fff !important;
	}
	
	.beijing{
		background-image: url("/static/img/shangmenyanzhengBJ.png");
		background-size: 100% 100%;
		position: absolute;
		top: 0;
		left: 0;
		height: 300rpx;
		width: 100%;
		z-index: 1;
	}
	.sdign{
		position: relative;
		z-index: 2;
		display: flex;
		flex-direction: column;
		padding: 30rpx;
		box-sizing: border-box;
		padding-bottom: 100px;
	}
	.bottom_bj{
		margin-bottom: 20rpx;
		margin-top: 10rpx;
	}
	.shili_paishe{
		margin-top: 10rpx;
		margin-bottom: 30rpx;
	}
	.uPpopup {
		width: 500rpx;
		padding: 30rpx;
		background-color: #fff;
		border-radius: 20rpx;
		display: flex;
		justify-content: center;
		flex-direction: column;
		align-items: center;
	}
	
	.uPpopupText {
		margin-top: 10rpx;
		margin-bottom: 10rpx;
	}
	
	
	.uPpopupOk {
		width: 90%;
		height: 76rpx;
		line-height: 76rpx;
		text-align: center;
		background: #fff;
		color: #000;
		border: 2rpx solid #DBDBDB;
		border-radius: 32px;
		margin-top: 30rpx;
	}
	.img_Shili{
		width: 350rpx;
		margin-top: 20rpx;
		margin-bottom: 20rpx;
		border-radius: 10rpx;
	}
</style>

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐