vue3+uniapp canvas图片加水印,文字水印自动换行,多水印,最全水印
·
效果图

所有内容皆可调整。 角度,字体大小颜色等。
下面代码全部粘贴到.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>
更多推荐
所有评论(0)