生成的临时路径是没有完整的路径没办法上传到服务器
16:37:40.993 添加水印后的路径, _doc/uniapp_temp_1710923708347/canvas/17109238597881.png
16:37:41.041 添加水印后的完整路径, file://storage/emulated/0/Android/data/com.jingruan.zjd/apps/__UNI__BE4B000/doc/uniapp_temp_1710923708347/canvas/17109238597881.png
使用以下代码得到完整的路径
javascript
let path = 'file:/' + plus.io.convertLocalFileSystemURL(tempFilePath);
完整代码如下 使用的插件市场的hpy-watermark组件 一共2个
效果是
其他页面调用方式
javascript
<!--
增加水印上传villageReviewForm.preciseAddr 是通过高德获取的定位地址
-->
<hpy-watermark ref="uploadImage" :address="villageReviewForm.preciseAddr" @waterMark="waterMark"></hpy-watermark>
高德获取定位
javascript
uni.getLocation({
type: 'gcj02',
geocode: true,
isHighAccuracy: true,
success: res => {
const {
province,
city,
district,
street,
streetNum,
poiName
} = res.address;
this.villageReviewForm.preciseAddr =
`${district}${street}${streetNum}${poiName}${res.longitude},${res.latitude}`;
console.log("经纬度地点",this.villageReviewForm.preciseAddr)
// 数据渲染
this.$forceUpdate();
}
});
获取上传数据结果
javascript
const fileList = this.$refs.uploadImage.fileList
组件样式
hpy-watermark.vue
javascript
<template>
<view>
<view class="watermark-content">
<canvas canvas-id="watermarkCanvas" id="watermarkCanvas" :style="{width:canvasWidth + 'px', height:canvasHeight + 'px'}"></canvas>
</view>
<upload-image v-model="fileList" style="margin-left: 15rpx" :image-styles="imageStyles" :files-list="filesList" :delIcon="delIcon" @choose="chooseImage" @delFile="delFile">
<slot>
<view class="is-add">
<view class="icon-add"></view>
<view class="icon-add rotate"></view>
</view>
</slot>
</upload-image>
</view>
</template>
<script>
import {
fileServerIp
} from "@/common/utils/config.js"
import Session from "@/common/Session";
import uploadImage from './upload-image.vue'
export default {
components: {
uploadImage,
},
name:'hpy-watermark',
props:{
address:{
type:String,
default:''
},
delIcon: {
type: Boolean,
default: true
},
listStyles: {
type: Object,
default () {
return {
// 是否显示边框
border: true,
// 是否显示分隔线
dividline: true,
// 线条样式
borderStyle: {}
}
}
},
imageStyles: {
type: Object,
default () {
return {
width: 'auto',
height: 'auto'
}
}
},
/**
* 文字文字位置(默认:左下角)可选值:左上角:topLeft、右上角:topRight、左下角:bottomLeft、右下角:bottomRight
*/
markAlign:{
type:String,
default:function(){
return 'bottomLeft'
}
},
/**
* 设置文本的水平对齐方式,默认:start,文本在指定的位置开始。
* end 文本在指定的位置结束。
* center 文本的中心被放置在指定的位置。
* left 文本左对齐。
* right 文本右对齐。
*/
textAlign:{
type:String,
default:function(){
return 'start';
}
},
/**
* 设置文本的垂直对齐方式,默认:alphabetic文本基线是普通的字母基线。
* top 文本基线是 em 方框的顶端。
* hanging 文本基线是悬挂基线。
* middle 文本基线是 em 方框的正中。
* ideographic 文本基线是表意基线。
* bottom 文本基线是 em 方框的底端。
*/
textBaseline:{
type:String,
default:function(){
return 'alphabetic';
}
},
/**
* 文字大小
*/
fontSize:{
type:[Number, String],
default:30
},
/**
* 文字颜色
*/
fontColor:{
type:String,
default:function(){
return 'red'
}
},
/**
* 阴影颜色
*/
shadowColor:{
type:String,
default:function(){
return 'rgba(0, 0, 0, 1.0)';
}
},
/**
* 阴影边框大小
*/
shadowWidth:{
type:[Number, String],
default:2
},
/**
* 图片的质量,取值范围为 (0, 1],不在范围内时当作1处理
*/
quality:{
type:[Number, String],
default:1
},
/**
* 目标文件的类型,只支持 'jpg' 或 'png'。默认为 'png'
*/
fileType:{
type:String,
default:function(){
return 'png'
}
}
},
data() {
return {
fileList: [],
files: [],
filesList:[],
canvasWidth:0,
canvasHeight:0
};
},
watch: {
fileList: {
handler(newVal, oldVal) {
this.filesList=newVal;
},
immediate: true
},
},
methods: {
// 选择图片
chooseImage() {
if(this.isEmpty(this.address)){
uni.showToast({
icon:'none',
title:'请打开定位或者重新获取'
});
return;
}
uni.chooseImage({
count: this.limit, // 限制的图片数量
sizeType: ['compressed'], // original 原图,compressed 压缩图,默认二者都有
sourceType: [ 'camera'],// album 从相册选图,camera 使用相机,默认二者都有
success: (res) => {
var imgPathList = res.tempFilePaths;
if(imgPathList.length > 0){
this.addImages(imgPathList);
}
},
fail: (err) => {
console.log('chooseImage fail', err)
if("chooseImage:fail cancel" == err.errMsg){
uni.showToast({
icon:'none',
title:'取消了选择'
});
}else{
}
}
});
},
// 添加图片
addImages(filePaths){
if(filePaths.length > 0){
var fillTexts = ["地址:"+this.address];
fillTexts.push("时间:" + this.getNowTime());
// 添加水印
this.addWaterMark({
filePaths,
fillTexts
});
}
},
/**
* 水印添加回调,在H5平台下,filePath 为 base64
*/
waterMark(filePath){
this.imageList.push(filePath);
},
/**
* 获取当前时间
*/
getNowTime(){
var date = new Date(),
year = date.getFullYear(),
month = date.getMonth() + 1,
day = date.getDate(),
hour = date.getHours() < 10 ? "0" + date.getHours() : date.getHours(),
minute = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes(),
second = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
month >= 1 && month <= 9 ? (month = "0" + month) : "";
day >= 0 && day <= 9 ? (day = "0" + day) : "";
return (year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
},
/**
* 删除文件
* @param {Object} index
*/
delFile(index) {
this.$emit('delete', {
tempFile: this.filesList[index],
tempFilePath: this.filesList[index].url
})
this.filesList.splice(index, 1)
},
/**
* 增加水印
* @param {Object} {filePaths:['图片地址1', '图片地址2'], fillTexts:['水印1', '水印2']}
*/
async addWaterMark({ filePaths = [], fillTexts = [] }) {
try{
for (const filePath of filePaths) {
await this.drawImage(filePath, fillTexts.reverse());
}
}catch(e){
// TODO handle the exception
}finally{
// uni.hideLoading();
}
},
/**
* 绘制单个图片
*/
async drawImage(filePath, fillTexts){
uni.showLoading({title:'图片处理中···'});
const ctx = uni.createCanvasContext('watermarkCanvas', this);
return new Promise(resolve => {
uni.getImageInfo({
src: filePath,
success: (image) => {
this.canvasWidth = image.width;
this.canvasHeight = image.height;
ctx.clearRect(0, 0, image.width, image.height);
setTimeout(()=>{
ctx.drawImage(image.path, 0, 0, image.width, image.height);
ctx.setFontSize(this.fontSize);
ctx.setFillStyle(this.fontColor);
// 设置阴影
let shadowWidth = Number(this.shadowWidth + "");
if(shadowWidth > 0){
ctx.shadowColor = this.shadowColor;
ctx.shadowOffsetX = shadowWidth;
ctx.shadowOffsetY = shadowWidth;
}
// 设置水平对齐方式
ctx.textAlign = this.textAlign;
// 设置垂直对齐方式
ctx.textBaseline = this.textBaseline;
const maxText = fillTexts.reduce((text, val) => {
return text.length >= val.length ? text : val;
});
fillTexts.forEach((mark, index) => {
if(this.markAlign == "bottomRight"){
ctx.fillText(mark, image.width - (ctx.measureText(maxText).width+60), image.height - (index*60+60));
}else if(this.markAlign == "topLeft"){
ctx.fillText(mark, 20, (index*60+60));
}else if(this.markAlign == "topRight"){
ctx.fillText(mark, image.width - (ctx.measureText(maxText).width+60), (index*60+60));
}else{
ctx.fillText(mark, 20, image.height - (index*60+60));
}
});
ctx.draw(false, (() => {
setTimeout(()=>{
uni.canvasToTempFilePath({
canvasId: 'watermarkCanvas',
fileType:this.fileType,
quality:Number(this.quality + "" || "1"),
success: (res) => {
console.log("添加水印后的路径",res.tempFilePath )
this.saveUploadImage(res.tempFilePath )
},
fail:(err) => {
uni.hideLoading();
console.log(err)
},
complete: () => {
resolve();
}
}, this);
}, 300);
})());
}, 200);
},
fail: (e) => {
resolve();
}
});
});
},
saveUploadImage(tempFilePath){
uni.showLoading({title:'图片上传中···'});
// #ifdef APP-PLUS
var p = plus.io.convertLocalFileSystemURL(tempFilePath);
this.url = 'file:/' + p
console.log("添加水印后的完整路径",this.url )
// #endif
uni.uploadFile({
url: fileServerIp + 'common/upload',
name: "file",
// #ifdef H5
filePath: tempFilePath,
// #endif
// #ifdef APP-PLUS
filePath: this.url,
// #endif
header: {
Authorization: "Bearer " + Session.getValue('token')
},
success: uploadFileRes => {
uni.hideLoading();
const {
data
} = JSON.parse(uploadFileRes.data)
this.filesList.push({
url: data.url,
name: data.fileName,
extname: 'png'
})
this.$emit('waterMark',{
url: data.url,
name: data.fileName,
extname: 'png'
});
},
fail: error => {
uni.hideLoading();
uni.showToast({
title: '上传失败!',
icon: 'error',
duration: 2000
});
}
})
}
}
}
</script>
<style scoped>
.watermark-content{width: 0;height: 0;overflow: hidden;}
.uni-file-picker__container {
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
/* #endif */
flex-wrap: wrap;
margin: -5px;
}
.rotate {
position: absolute;
transform: rotate(90deg);
}
.icon-add {
width: 50px;
height: 5px;
background-color: #f1f1f1;
border-radius: 2px;
}
</style>
upload-image.vue
javascript
<template>
<view class="uni-file-picker__container">
<view class="file-picker__box" v-for="(item,index) in filesList" :key="index" :style="boxStyle">
<view class="file-picker__box-content" :style="borderStyle">
<image class="file-image" :src="item.url" mode="aspectFill" @click.stop="prviewImage(item,index)"></image>
<view v-if="delIcon && !readonly" class="icon-del-box" @click.stop="delFile(index)">
<view class="icon-del"></view>
<view class="icon-del rotate"></view>
</view>
<view v-if="(item.progress && item.progress !== 100) ||item.progress===0 " class="file-picker__progress">
<progress class="file-picker__progress-item" :percent="item.progress === -1?0:item.progress" stroke-width="4"
:backgroundColor="item.errMsg?'#ff5a5f':'#EBEBEB'" />
</view>
<view v-if="item.errMsg" class="file-picker__mask" @click.stop="uploadFiles(item,index)">
点击重试
</view>
</view>
</view>
<view v-if="filesList.length < limit && !readonly" class="file-picker__box" :style="boxStyle">
<view class="file-picker__box-content is-add" :style="borderStyle" @click="choose">
<slot>
<view class="icon-add"></view>
<view class="icon-add rotate"></view>
</slot>
</view>
</view>
</view>
</template>
<script>
export default {
name: "uploadImage",
emits:['uploadFiles','choose','delFile'],
props: {
filesList: {
type: Array,
default () {
return []
}
},
disabled:{
type: Boolean,
default: false
},
disablePreview: {
type: Boolean,
default: false
},
limit: {
type: [Number, String],
default: 9
},
imageStyles: {
type: Object,
default () {
return {
width: 'auto',
height: 'auto',
border: {}
}
}
},
delIcon: {
type: Boolean,
default: true
},
readonly:{
type:Boolean,
default:false
}
},
computed: {
styles() {
let styles = {
width: 'auto',
height: 'auto',
border: {}
}
return Object.assign(styles, this.imageStyles)
},
boxStyle() {
const {
width = 'auto',
height = 'auto'
} = this.styles
let obj = {}
if (height === 'auto') {
if (width !== 'auto') {
obj.height = this.value2px(width)
obj['padding-top'] = 0
} else {
obj.height = 0
}
} else {
obj.height = this.value2px(height)
obj['padding-top'] = 0
}
if (width === 'auto') {
if (height !== 'auto') {
obj.width = this.value2px(height)
} else {
obj.width = '33.3%'
}
} else {
obj.width = this.value2px(width)
}
let classles = ''
for(let i in obj){
classles+= `${i}:${obj[i]};`
}
return classles
},
borderStyle() {
let {
border
} = this.styles
let obj = {}
const widthDefaultValue = 1
const radiusDefaultValue = 3
if (typeof border === 'boolean') {
obj.border = border ? '1px #eee solid' : 'none'
} else {
let width = (border && border.width) || widthDefaultValue
width = this.value2px(width)
let radius = (border && border.radius) || radiusDefaultValue
radius = this.value2px(radius)
obj = {
'border-width': width,
'border-style': (border && border.style) || 'solid',
'border-color': (border && border.color) || '#eee',
'border-radius': radius
}
}
let classles = ''
for(let i in obj){
classles+= `${i}:${obj[i]};`
}
return classles
}
},
methods: {
uploadFiles(item, index) {
this.$emit("uploadFiles", item)
},
choose() {
this.$emit("choose")
},
delFile(index) {
this.$emit('delFile', index)
},
prviewImage(img, index) {
let urls = []
if(Number(this.limit) === 1&&this.disablePreview&&!this.disabled){
this.$emit("choose")
}
if(this.disablePreview) return
this.filesList.forEach(i => {
urls.push(i.url)
})
uni.previewImage({
urls: urls,
current: index
});
},
value2px(value) {
if (typeof value === 'number') {
value += 'px'
} else {
if (value.indexOf('%') === -1) {
value = value.indexOf('px') !== -1 ? value : value + 'px'
}
}
return value
}
}
}
</script>
<style lang="scss">
.uni-file-picker__container {
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
/* #endif */
flex-wrap: wrap;
margin: -5px;
}
.file-picker__box {
position: relative;
// flex: 0 0 33.3%;
width: 33.3%;
height: 0;
padding-top: 33.33%;
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
}
.file-picker__box-content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: 5px;
border: 1px #eee solid;
border-radius: 5px;
overflow: hidden;
}
.file-picker__progress {
position: absolute;
bottom: 0;
left: 0;
right: 0;
/* border: 1px red solid; */
z-index: 2;
}
.file-picker__progress-item {
width: 100%;
}
.file-picker__mask {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
position: absolute;
right: 0;
top: 0;
bottom: 0;
left: 0;
color: #fff;
font-size: 12px;
background-color: rgba(0, 0, 0, 0.4);
}
.file-image {
width: 100%;
height: 100%;
}
.is-add {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.icon-add {
width: 50px;
height: 5px;
background-color: #f1f1f1;
border-radius: 2px;
}
.rotate {
position: absolute;
transform: rotate(90deg);
}
.icon-del-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
position: absolute;
top: 3px;
right: 3px;
height: 26px;
width: 26px;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 2;
transform: rotate(-45deg);
}
.icon-del {
width: 15px;
height: 2px;
background-color: #fff;
border-radius: 2px;
}
</style>