tui-image-editor中跨域问题
如何安装 tui-image-editor等就不再赘述,参考这篇博客即可,
https://blog.csdn.net/weixin_44867717/article/details/128212251
简单版参考: https://blog.csdn.net/Bonsoir777/article/details/134153807
官网: https://ui.toast.com/tui-image-editor
下面说说我碰到的问题,
跨域
如上图所示,我用的图片地址是上传到阿里云的,图片地址是跨域的,这个需要在下图数据安全中跨域设置里面配置,这样就可以访问。
如果开启了CDN,则还需要在CDN中配置,
关于CDN缓存问题
cdn是存在缓存的,如果编辑了图片,保存到数据库,图片名不改变,直接上传到阿里云,由于启用了cdn,这里面存在缓存,如果页面用的是缓存的url,还是会显示以前的图片,所以要改变图片名然后上传,这样保证路径改变了。
因为我是本地调试,所以用其他一个图片代替,

元素隐藏
如下代码所示,我选取了一些界面元素,隐藏掉。
document.querySelector('[tooltip-content="Delete"]').style.display = 'none'
document.querySelector('[tooltip-content="DeleteAll"]').style.display = 'none'
document.querySelector('[tooltip-content="添加图标"]').style.display = 'none'
document.querySelector('[tooltip-content="Mask"]').style.display = 'none'
document.querySelector('[tooltip-content="Filter"]').style.display = 'none'
document.querySelector('[tooltip-content="放大"]').style.marginTop = '40px'
在上述参考博客中,元素隐藏使用的是英文,结果无效,后来才发现,通过汉化后,选取的元素应该是写中文,
如下是我汉化的配置,就需要通过汉化来选取元素,
// 中文菜单
const localeCN = {
Crop: '裁剪',
Draw: '涂鸦',
Text: '添加文本',
Free: '任意线条',
Straight: '直线',
Icon: '添加图标',
Color: '颜色',
Range: '范围',
ZoomIn: '放大',
ZoomOut: '缩小',
Hand: '移动',
History: '历史记录',
Undo: '撤销',
Redo: '前进',
Reset: '重置',
Delete: 'Delete',
DeleteAll: 'DeleteAll',
Resize: '调整大小',
Flip: '镜像翻转',
Rotate: '旋转',
Shape: '图形',
Width: '宽',
Height: '高',
'Lock Aspect Ratio': '锁定宽高比',
Apply: '应用',
Cancel: '取消',
Custom: '自定义',
Square: '正方形',
'Flip X': 'X轴翻转',
'Flip Y': 'Y轴翻转',
Rectangle: '正方形',
Circle: '圆',
Triangle: '三角形',
Fill: '填充',
Stroke: '边框',
Bold: '粗体',
Italic: '斜体',
Underline: '下划线',
Left: '左',
Center: '中',
Right: '右',
'Text size': '字号',
}
CSS样式调整问题
如下所示 <style lang="scss"> 使用可以生效,但 <style lang="scss" scoped> 就不会生效。
<style lang="scss">
.tui-image-editor-container .tui-image-editor-main {
top: 0em !important;
}
/* 强制压缩菜单的高度 ,减少占用屏幕的空间*/
.tui-image-editor-container .tui-image-editor-submenu {
height: auto !important;
}
.tui-image-editor-container.bottom .tui-image-editor-submenu>div {
padding: 0 !important;
}
.tui-image-editor-container .tui-image-editor-help-menu.top {
background-color: white;
}
/* 顶部工具栏定位 */
.tui-image-editor-container .tui-image-editor-header {
top: -55px;
}
.tui-image-editor-container .tui-image-editor-help-menu.top {
top: -50px;
}
/* 取消超出部分隐藏,否则因为顶部工具栏已经超出去了,会显示不出来
.tui-image-editor-container {
overflow: auto;
} */
/* 顶部工具栏定位 */
.tui-image-editor-container {
overflow: visible;
}
</style>
完整组件代码
<template>
<div class="boardBox">
<!-- 用户可以通过点击大按钮来关闭弹窗 -->
<div class="closeBigBtn">
<img class="img" title="保存图片" :src="require('@/assets/monitor/save.png')"
@click="saveCanvas2Img" />
<img class="img" title="下载图片" :src="require('@/assets/monitor/download.png')"
@click="downloadCanvas2Img" />
<img class="img" title="分享图片" :src="require('@/assets/monitor/share.png')"
@click="shareCanvas2Img" />
</div>
<!-- 绘图组件容器DOM -->
<div id="tui-image-editor"></div>
</div>
</template>
<script>
import "tui-image-editor/dist/tui-image-editor.css";
import "tui-color-picker/dist/tui-color-picker.css";
import ImageEditor from "tui-image-editor";
// 中文菜单
const localeCN = {
Crop: '裁剪',
Draw: '涂鸦',
Text: '添加文本',
Free: '任意线条',
Straight: '直线',
Icon: '添加图标',
Color: '颜色',
Range: '范围',
ZoomIn: '放大',
ZoomOut: '缩小',
Hand: '移动',
History: '历史记录',
Undo: '撤销',
Redo: '前进',
Reset: '重置',
Delete: 'Delete',
DeleteAll: 'DeleteAll',
Resize: '调整大小',
Flip: '镜像翻转',
Rotate: '旋转',
Shape: '图形',
Width: '宽',
Height: '高',
'Lock Aspect Ratio': '锁定宽高比',
Apply: '应用',
Cancel: '取消',
Custom: '自定义',
Square: '正方形',
'Flip X': 'X轴翻转',
'Flip Y': 'Y轴翻转',
Rectangle: '正方形',
Circle: '圆',
Triangle: '三角形',
Fill: '填充',
Stroke: '边框',
Bold: '粗体',
Italic: '斜体',
Underline: '下划线',
Left: '左',
Center: '中',
Right: '右',
'Text size': '字号',
}
// 画布组件自定义样式
const customTheme = {
// image 坐上角度图片
'common.bi.image': '', // 在这里换上你喜欢的logo图片
'common.bisize.width': '0px',
'common.bisize.height': '0px',
'common.backgroundImage': 'none',
'common.backgroundColor': '#f3f4f6',
'common.border': '0px solid #444',
// header
'header.backgroundImage': 'none',
'header.backgroundColor': '#f3f4f6',
'header.border': '0px',
'header.display': 'none',
// load button
'loadButton.backgroundColor': '#fff',
'loadButton.border': '1px solid #ddd',
'loadButton.color': '#222',
'loadButton.fontFamily': 'NotoSans, sans-serif',
'loadButton.fontSize': '12px',
'loadButton.display': 'none', // 可以直接隐藏掉
// download button
'downloadButton.backgroundColor': '#fdba3b',
'downloadButton.border': '1px solid #fdba3b',
'downloadButton.color': '#fff',
'downloadButton.fontFamily': 'NotoSans, sans-serif',
'downloadButton.fontSize': '12px',
'downloadButton.display': 'none', // 可以直接隐藏掉
// icons default
'menu.normalIcon.color': '#8a8a8a',
'menu.activeIcon.color': '#555555',
'menu.disabledIcon.color': '#434343',
'menu.hoverIcon.color': '#e9e9e9',
'submenu.normalIcon.color': '#8a8a8a',
'submenu.activeIcon.color': '#e9e9e9',
'menu.iconSize.width': '24px',
'menu.iconSize.height': '24px',
'submenu.iconSize.width': '32px',
'submenu.iconSize.height': '32px',
// submenu primary color
'submenu.backgroundColor': '#1e1e1e',
'submenu.partition.color': '#858585',
// submenu labels
'submenu.normalLabel.color': '#858585',
'submenu.normalLabel.fontWeight': 'lighter',
'submenu.activeLabel.color': '#fff',
'submenu.activeLabel.fontWeight': 'lighter',
// checkbox style
'checkbox.border': '1px solid #ccc',
'checkbox.backgroundColor': '#fff',
// rango style
'range.pointer.color': '#fff',
'range.bar.color': '#666',
'range.subbar.color': '#d1d1d1',
'range.disabledPointer.color': '#414141',
'range.disabledBar.color': '#282828',
'range.disabledSubbar.color': '#414141',
'range.value.color': '#fff',
'range.value.fontWeight': 'lighter',
'range.value.fontSize': '11px',
'range.value.border': '1px solid #353535',
'range.value.backgroundColor': '#151515',
'range.title.color': '#fff',
'range.title.fontWeight': 'lighter',
// colorpicker style
'colorpicker.button.border': '1px solid #1e1e1e',
'colorpicker.title.color': '#fff'
}
export default {
data() {
return {
instance: null,
loading: false,
};
},
mounted() {
this.init();
},
methods: {
init() {
this.loading = true;
let image = "https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c1d7a1feb60346449c1a64893888989a~tplv-k3u1fbpfcp-watermark.image";
image = "https://img1.baidu.com/it/u=4131209051,1689521986&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500";
// image = "https://cdn.esg-cloud.com/news/08a44f8437004910874d9286f239023d.jpg?x-oss-process=style/w750"
this.instance = new ImageEditor(
document.querySelector("#tui-image-editor"),
{
includeUI: {
loadImage: {
path: image,
name: "image",
},
initMenu: "", // 默认打开的菜单项
menuBarPosition: "right", // 菜单所在的位置
// 汉化
locale: localeCN,
// 自定义样式(隐藏默认顶部栏目、按钮颜色。。。)
theme: customTheme
},
cssMaxWidth: 1000, // canvas 最大宽度
cssMaxHeight: 1000, // canvas 最大高度
}
);
document.querySelector('[tooltip-content="Delete"]').style.display = 'none'
document.querySelector('[tooltip-content="DeleteAll"]').style.display = 'none'
document.querySelector('[tooltip-content="添加图标"]').style.display = 'none'
document.querySelector('[tooltip-content="Mask"]').style.display = 'none'
document.querySelector('[tooltip-content="Filter"]').style.display = 'none'
document.querySelector('[tooltip-content="放大"]').style.marginTop = '40px'
},
/** 保存编辑后图片 */
saveCanvas2Img() {
// 调用组件官方方法,获取整个编辑后图片的base64数据
const base64String = this.instance.toDataURL()
this.$emit('updateImage',base64String)
// console.log(base64String)
//todo 传参数给后台接口
},
downloadCanvas2Img(){
const base64String = this.instance.toDataURL()
this.$emit('downloadImage',base64String)
},
shareCanvas2Img(){
}
},
};
</script>
<style lang="scss">
.tui-image-editor-container .tui-image-editor-main {
top: 0em !important;
}
/* 强制压缩菜单的高度 ,减少占用屏幕的空间*/
.tui-image-editor-container .tui-image-editor-submenu {
height: auto !important;
}
.tui-image-editor-container.bottom .tui-image-editor-submenu>div {
padding: 0 !important;
}
.tui-image-editor-container .tui-image-editor-help-menu.top {
background-color: white;
}
/* 顶部工具栏定位 */
.tui-image-editor-container .tui-image-editor-header {
top: -55px;
}
.tui-image-editor-container .tui-image-editor-help-menu.top {
top: -50px;
}
/* 取消超出部分隐藏,否则因为顶部工具栏已经超出去了,会显示不出来
.tui-image-editor-container {
overflow: auto;
} */
/* 顶部工具栏定位 */
.tui-image-editor-container {
overflow: visible;
}
</style>
<style lang="scss" scoped>
.boardBox {
// width: 100%;
// height: 80vh;
// margin-top: 0px;
height: 100%;
width: 100%;
background: #f9f9f9;
canvas {
border: 1px solid #b3b3b3;
}
}
.img{
width:30px;
cursor: pointer;
margin: 5px 10px;
}
</style>
使用如下,
<CustomModal :visible.sync="showModal">
<EditImage :imagePath="imagePath" @downloadImage="downloadImageM" @updateImage="updateImageM"></EditImage>
</CustomModal>
下载图片
有个下载图片的功能,用的这个博客的代码,不需要后端直接前端下载。
https://www.cnblogs.com/venje/p/12109626.html
downloadImageM(param) {
let base64String = param;
let needData = {
img_id: this.nowHandleImage.img_id,
base64: base64String
}
let h = JSON.stringify(needData)
//base64太大,413
// downloadImage(h);
this.download(base64String);
},
download(base64) {
let imgData = base64;
this.downloadFile(this.nowHandleImage.img_name, imgData);
},
//下载
downloadFile(fileName, content) {
let aLink = document.createElement('a');
let blob = this.base64ToBlob(content); //new Blob([content]);
let evt = document.createEvent("HTMLEvents");
evt.initEvent("click", true, true);//initEvent 不加后两个参数在FF下会报错 事件类型,是否冒泡,是否阻止浏览器的默认行为
aLink.download = fileName;
aLink.href = URL.createObjectURL(blob);
// aLink.dispatchEvent(evt);
aLink.click()
},
//base64转blob
base64ToBlob(code) {
let parts = code.split(';base64,');
let contentType = parts[0].split(':')[1];
let raw = window.atob(parts[1]);
let rawLength = raw.length;
let uInt8Array = new Uint8Array(rawLength);
for (let i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], { type: contentType });
},
更新图片
base64ToFile(base64String) {
let parts = base64String.split(';base64,');
let contentType = parts[0].split(':')[1];
let raw = window.atob(parts[1]);
let rawLength = raw.length;
let uInt8Array = new Uint8Array(rawLength);
for (let i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
let options = {
type: contentType,
endings: "native"
}
let fileName = this.nowHandleImage.img_name;
return new File([uInt8Array], fileName, options)
},
updateImageM(param) {
if(this.updateImageFlag) return;//防止按钮按多次
this.updateImageFlag = true;
let base64String = param;
// let needData = {
// img_id: this.nowHandleImage.img_id,
// base64: base64String.substr("data:image/png;base64,".length)
// }
//base64字符串太长,转成文件,否则丢失参数信息
let file = this.base64ToFile(base64String);
let formData = new FormData();
formData.append("img_id", this.nowHandleImage.img_id)
formData.append("file", file);
updateImage(formData).then(res => {
this.updateImageFlag = false;
if (res.result_code == "0") {
this.$Message.success({
content: '更新成功',
duration: 3
})
this.initData();
this.loadMore();
}
});
},
后端直接接受Multipartfile文件即可,本来想的是POST直接传base64字符串,但当使用很大的图片时,发现参数全部丢失了,只知道GET有长度限制,看来POST也有长度限制,只不过正常情况下应该不会传这么长参数。
@RequestMapping(value = "/updateImage.action")
public PageData updateImage(MultipartFile file, Integer img_id){
try {
return monitorService.updateImage(file,img_id);
} catch (Exception e) {
logger.error("获取产线列表异常", e);
return RespUtil.getErrorResp("-1", "获取产线列表异常");
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?