截图继承logic-flow插件,水印支持

This commit is contained in:
2025-12-26 16:19:18 +08:00
parent 28b3818011
commit 2fb7cc2818
2 changed files with 82 additions and 43 deletions

View File

@@ -77,15 +77,13 @@
</template>
<script setup lang="ts">
import {ref, reactive, onMounted} from 'vue';
import {useI18n} from 'vue-i18n';
import { reactive, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import updateLogs from "../data/updateLog.json"
import {useFilesStore} from "@/ts/useStore";
import {ElMessageBox} from "element-plus";
import {useGlobalMessage} from "@/ts/useGlobalMessage";
import { useFilesStore } from "@/ts/useStore";
import { ElMessageBox } from "element-plus";
import { useGlobalMessage } from "@/ts/useGlobalMessage";
import { getLogicFlowInstance } from "@/ts/useLogicFlow";
// import { useScreenshot } from '@/ts/useScreenshot';
import { getCurrentInstance } from 'vue';
const filesStore = useFilesStore();
const { showMessage } = useGlobalMessage();
@@ -252,44 +250,85 @@ const applyWatermarkSettings = () => {
};
// 获取 App 根实例,便于跨组件获取 flowEditorRef
const appInstance = getCurrentInstance();
const addWatermarkToImage = (base64: string) => {
const rows = Math.max(1, Number(watermark.rows) || 1);
const cols = Math.max(1, Number(watermark.cols) || 1);
const angle = (Number(watermark.angle) * Math.PI) / 180;
// const { captureFlow, dataUrl } = useScreenshot();
return new Promise<string>((resolve, reject) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
if (!ctx) {
reject(new Error('无法创建画布上下文'));
return;
}
ctx.drawImage(img, 0, 0);
ctx.font = `${watermark.fontSize}px sans-serif`;
ctx.fillStyle = watermark.color;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const rowStep = canvas.height / rows;
const colStep = canvas.width / cols;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
const x = (c + 0.5) * colStep;
const y = (r + 0.5) * rowStep;
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle);
ctx.fillText(watermark.text, 0, 0);
ctx.restore();
}
}
resolve(canvas.toDataURL('image/png'));
};
img.onerror = () => reject(new Error('快照加载失败'));
img.src = base64;
});
};
const captureLogicFlowSnapshot = async () => {
const logicFlowInstance = getLogicFlowInstance() as any;
if (!logicFlowInstance || typeof logicFlowInstance.getSnapshotBase64 !== 'function') {
showMessage('error', '未找到 LogicFlow 实例,无法截图');
return null;
}
const snapshotResult = await logicFlowInstance.getSnapshotBase64(
undefined,
undefined,
{
fileType: 'png',
backgroundColor: '#ffffff',
partial: false,
padding: 20,
},
);
const base64 = typeof snapshotResult === 'string' ? snapshotResult : snapshotResult?.data;
if (!base64) {
showMessage('error', '未获取到截图数据');
return null;
}
return addWatermarkToImage(base64);
};
const prepareCapture = async () => {
// 获取 FlowEditor 实例
// 这里假设 App.vue 已将 flowEditorRef 作为全局 property 或 provide
// 或者你可以通过 window.__VUE_DEVTOOLS_GLOBAL_HOOK__.$vm0.$refs.flowEditorRef 方式调试
let flowEditor = null;
try {
// 通过 DOM 查找
const flowEditorDom = document.querySelector('#main-container .flow-editor');
if (!flowEditorDom) {
showMessage('error', '未找到流程图编辑器');
return;
}
// 通过 ref 获取 vueflow 根元素
const vueflowRoot = flowEditorDom.querySelector('.vue-flow');
if (!vueflowRoot || !(vueflowRoot instanceof HTMLElement)) {
showMessage('error', '未找到 VueFlow 画布');
return;
}
state.previewVisible = true;
// 截图
const img = await captureFlow(vueflowRoot as HTMLElement, {
type: 'png',
shouldDownload: false,
watermark: {
text: watermark.text,
fontSize: watermark.fontSize,
color: watermark.color,
angle: watermark.angle,
rows: watermark.rows,
cols: watermark.cols,
},
});
const img = await captureLogicFlowSnapshot();
if (!img) return;
state.previewImage = img;
state.previewVisible = true;
} catch (e) {
showMessage('error', '截图失败: ' + (e?.message || e));
}
@@ -335,4 +374,4 @@ const handleClose = (done) => {
display: flex;
gap: 8px;
}
</style>
</style>

View File

@@ -13,7 +13,7 @@
import { ref, watch, onMounted, onBeforeUnmount, defineExpose } from 'vue';
import LogicFlow, { EventType } from '@logicflow/core';
import '@logicflow/core/lib/style/index.css';
import { Menu,Label } from "@logicflow/extension";
import { Menu, Label, Snapshot } from "@logicflow/extension";
import "@logicflow/extension/lib/style/index.css";
import '@logicflow/core/es/index.css';
import '@logicflow/extension/es/index.css';
@@ -68,7 +68,7 @@ onMounted(() => {
allowResize: true,
allowRotate: true,
overlapMode:-1,
plugins: [Menu,Label],
plugins: [Menu, Label, Snapshot],
pluginsOptions: {
label: {
isMultiple: true,