效果图

下载包
pnpm i tui-image-editor
pnpm i tui-color-picker
调用组件
<el-dialog v-model="imgshow" destroy-on-close width="40%" draggable align-center :show-close="true":close-on-click-modal="false"><template #header><div style="display: flex; justify-content: space-between"><div style="display: flex"><h3>图片</h3></div></div></template><div class="newBox"><!-- 图片处理框 --><SignImage v-if="cropperObj.cVisible" :dialogVisible="cropperObj.cVisible" :title="cropperObj.ctitle":imgUrl="cropperObj.previewsImgUrl" @getNewImg="cropperObj.getNewImg"@closeCropperDialog="cropperObj.closeCropperView"></SignImage></div></el-dialog>
import { SignImage } from './index';
let imgshow = ref<boolean>(false);
const cropperObj = reactive({cVisible: true, ctitle: "", previewsImgUrl: "https://img0.baidu.com/it/u=2759734465,3558448225&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1708621200&t=acc59373dca9b8658e33e14a974f6c47", openCropperView: () => {cropperObj.ctitle = "图片处理"cropperObj.cVisible = true},closeCropperView: (data) => {cropperObj.cVisible = false},getNewImg: (val: any) => {console.log("val", val);pictureAnnotationFun(val)}
})
async function pictureAnnotationFun(file: any) {const formData = new FormData()formData.append("file", file);formData.append('id', xx);formData.append('bid', xxx);try {const { code, msg, data } = await uploadimg(formData);if (code > 0) {cropperObj.cVisible = false;ElMessage({showClose: true,message: '上传成功',type: 'success',});} else {ElMessage({showClose: true,message: '上传失败' + msg,type: 'error',duration: 0,});}} catch (e) {ElMessage({showClose: true,message: '错误: ' + e.message,type: 'error',duration: 0,});}
}
封装组件代码
<template><div class="drawing-container"><!-- 绘图组件容器DOM --><div id="tui-image-editor"></div><el-button class="save" type="primary" @click="save">保存</el-button></div>
</template>
<script setup lang="ts">
import 'tui-image-editor/dist/tui-image-editor.css';
import 'tui-color-picker/dist/tui-color-picker.css';
import ImageEditor from 'tui-image-editor';
import { nextTick, onMounted, ref } from 'vue';
const locale_zh = {ZoomIn: '放大',ZoomOut: '缩小',Hand: '手掌',History: '历史',Resize: '调整宽高',Crop: '裁剪',DeleteAll: '全部删除',Delete: '删除',Undo: '撤销',Redo: '反撤销',Reset: '重置',Flip: '镜像',Rotate: '旋转',Draw: '画',Shape: '形状标注',Icon: '图标标注',Text: '文字标注',Mask: '遮罩',Filter: '滤镜',Bold: '加粗',Italic: '斜体',Underline: '下划线',Left: '左对齐',Center: '居中',Right: '右对齐',Color: '颜色','Text size': '字体大小',Custom: '自定义',Square: '正方形',Apply: '应用',Cancel: '取消','Flip X': 'X 轴','Flip Y': 'Y 轴',Range: '区间',Stroke: '描边',Fill: '填充',Circle: '圆',Triangle: '三角',Rectangle: '矩形',Free: '曲线',Straight: '直线',Arrow: '箭头','Arrow-2': '箭头2','Arrow-3': '箭头3','Star-1': '星星1','Star-2': '星星2',Polygon: '多边形',Location: '定位',Heart: '心形',Bubble: '气泡','Custom icon': '自定义图标','Load Mask Image': '加载蒙层图片',Grayscale: '灰度',Blur: '模糊',Sharpen: '锐化',Emboss: '浮雕','Remove White': '除去白色',Distance: '距离',Brightness: '亮度',Noise: '噪音','Color Filter': '彩色滤镜',Sepia: '棕色',Sepia2: '棕色2',Invert: '负片',Pixelate: '像素化',Threshold: '阈值',Tint: '色调',Multiply: '正片叠底',Blend: '混合色',Width: '宽度',Height: '高度','Lock Aspect Ratio': '锁定宽高比例',
};
const customTheme = {'common.bi.image': '', 'common.bisize.width': '0px','common.bisize.height': '0px','common.backgroundImage': 'none','common.backgroundColor': '#f3f4f6','common.border': '1px solid #333','header.backgroundImage': 'none','header.backgroundColor': '#f3f4f6','header.border': '0px','loadButton.backgroundColor': '#fff','loadButton.border': '1px solid #ddd','loadButton.color': '#222','loadButton.fontFamily': 'NotoSans, sans-serif','loadButton.fontSize': '12px','loadButton.display': 'none', 'downloadButton.backgroundColor': '#fdba3b','downloadButton.border': '1px solid #fdba3b','downloadButton.color': '#fff','downloadButton.fontFamily': 'NotoSans, sans-serif','downloadButton.fontSize': '12px','downloadButton.display': 'none', 'menu.normalIcon.color': '#8a8a8a','menu.activeIcon.color': '#555555','menu.disabledIcon.color': '#ccc','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.backgroundColor': '#1e1e1e','submenu.partition.color': '#858585','submenu.normalLabel.color': '#858585','submenu.normalLabel.fontWeight': 'lighter','submenu.activeLabel.color': '#fff','submenu.activeLabel.fontWeight': 'lighter','checkbox.border': '1px solid #ccc','checkbox.backgroundColor': '#fff','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.button.border': '1px solid #1e1e1e','colorpicker.title.color': '#fff',
};
const props = defineProps({dialogVisible: {type: Boolean,default: () => {return false;},},title: {type: String,default: '',},imgUrl: {type: String,default: '',},
});
const emit = defineEmits(['closeCropperDialog', 'getNewImg']);
const instance = ref<any>(null);onMounted(() => {nextTick(() => {init(); });
});
const closeDialog = () => {emit('closeCropperDialog');
};const init = () => {instance.value = new ImageEditor(document.querySelector('#tui-image-editor'), {includeUI: {loadImage: {path: props.imgUrl,name: 'image',},menu: ['resize', 'crop', 'rotate', 'draw', 'shape', 'icon', 'text', 'filter'], initMenu: 'draw', menuBarPosition: 'bottom', locale: locale_zh, theme: customTheme, },cssMaxWidth: 400, cssMaxHeight: 500, });document.getElementsByClassName('tui-image-editor-main')[0].style.top = '45px'; document.getElementsByClassName('tie-btn-reset tui-image-editor-item help')[0].style.display = 'none';
};
const save = async () => {const base64String = instance.value.toDataURL(); const data = window.atob(base64String.split(',')[1]);const ia = new Uint8Array(data.length);for (let i = 0; i < data.length; i++) {ia[i] = data.charCodeAt(i);}const file = new File([ia], "打标.png", { type: "image/png" });emit('getNewImg', file);};
</script><style lang="scss" scoped>
.drawing-container {width: 100%;height: 80vh;position: relative;.save {position: absolute;right: 50px;top: 15px;}
}
</style>