代码同步

This commit is contained in:
2025-07-27 16:01:06 +08:00
parent 676abf241d
commit b904b257e5
3 changed files with 1908 additions and 2247 deletions

3959
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,10 @@
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"@logicflow/core": "^2.0.16", "@logicflow/core": "^2.0.16",
"@logicflow/engine": "^0.1.1",
"@logicflow/extension": "^2.0.21", "@logicflow/extension": "^2.0.21",
"@logicflow/vue-node-registry": "^1.0.18",
"@tailwindcss/postcss": "^4.1.11",
"@vueup/vue-quill": "^1.2.0", "@vueup/vue-quill": "^1.2.0",
"element-plus": "^2.9.1", "element-plus": "^2.9.1",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",

View File

@@ -77,12 +77,13 @@
<script setup lang="ts"> <script setup lang="ts">
import {ref, reactive, onMounted} from 'vue'; import {ref, reactive, onMounted} from 'vue';
import html2canvas from "html2canvas";
import {useI18n} from 'vue-i18n'; import {useI18n} from 'vue-i18n';
import updateLogs from "../data/updateLog.json" import updateLogs from "../data/updateLog.json"
import {useFilesStore} from "@/ts/useStore"; import {useFilesStore} from "@/ts/useStore";
import {ElMessageBox} from "element-plus"; import {ElMessageBox} from "element-plus";
import {useGlobalMessage} from "@/ts/useGlobalMessage"; import {useGlobalMessage} from "@/ts/useGlobalMessage";
// import { useScreenshot } from '@/ts/useScreenshot';
import { getCurrentInstance } from 'vue';
const filesStore = useFilesStore(); const filesStore = useFilesStore();
const { showMessage } = useGlobalMessage(); const { showMessage } = useGlobalMessage();
@@ -210,162 +211,46 @@ const applyWatermarkSettings = () => {
}; };
// 计算视觉总高度 // 获取 App 根实例,便于跨组件获取 flowEditorRef
function calculateVisualHeight(selector) { const appInstance = getCurrentInstance();
// 1. 获取所有目标元素
const elements = Array.from(document.querySelectorAll(selector));
// 2. 获取元素位置信息并排序 // const { captureFlow, dataUrl } = useScreenshot();
const rects = elements.map(el => {
const rect = el.getBoundingClientRect();
return {
el,
top: rect.top,
bottom: rect.bottom,
height: rect.height
};
}).sort((a, b) => a.top - b.top); // 按垂直位置排序
// 3. 动态分组同行元素
const rows = [];
rects.forEach(rect => {
let placed = false;
// 尝试将元素加入已有行
for (const row of rows) {
if (
rect.top < row.bottom && // 元素顶部在行底部上方
rect.bottom > row.top // 元素底部在行顶部下方
) {
row.elements.push(rect);
row.bottom = Math.max(row.bottom, rect.bottom); // 扩展行底部
row.maxHeight = Math.max(row.maxHeight, rect.height);
placed = true;
break;
}
}
// 未加入则创建新行
if (!placed) {
rows.push({
elements: [rect],
top: rect.top,
bottom: rect.bottom,
maxHeight: rect.height
});
}
});
// 4. 累加每行最大高度
return rows.reduce((sum, row) => sum + row.maxHeight, 0);
}
const ignoreElements = (element) => {
return element.classList.contains('ql-toolbar') || element.classList.contains('el-tabs__header');
};
const prepareCapture = async () => { const prepareCapture = async () => {
state.previewVisible = true; // 获取 FlowEditor 实例
// 这里假设 App.vue 已将 flowEditorRef 作为全局 property 或 provide
// 创建临时样式 // 或者你可以通过 window.__VUE_DEVTOOLS_GLOBAL_HOOK__.$vm0.$refs.flowEditorRef 方式调试
const style = document.createElement('style'); let flowEditor = null;
style.textContent = `
.ql-container.ql-snow {
border: none !important;
}
#main-container {
position: relative;
height: 100%;
overflow-y: auto;
min-height: 100vh;
display: inline-block;
max-width: 100%;
}`;
document.head.appendChild(style);
// 获取目标元素
const element = document.querySelector('#main-container');
if (!element) {
console.error('Element not found');
return;
}
// 保存原始 overflow 样式
const originalOverflow = element.style.overflow;
try { try {
// 临时隐藏 overflow 样式 // 通过 DOM 查找
element.style.overflow = 'visible'; const flowEditorDom = document.querySelector('#main-container .flow-editor');
if (!flowEditorDom) {
// 计算需要忽略的元素高度 showMessage('error', '未找到流程图编辑器');
let totalHeight = calculateVisualHeight('[data-html2canvas-ignore]') + calculateVisualHeight('.ql-toolbar'); return;
console.log('所有携带指定属性的元素高度之和:', totalHeight);
console.log('主元素宽度', element.scrollWidth);
console.log('主元素高度', element.scrollHeight);
// 1. 生成原始截图
const canvas = await html2canvas(element, {
ignoreElements: ignoreElements,
scrollX: 0,
scrollY: 0,
width: element.scrollWidth,
height: element.scrollHeight - totalHeight,
});
// 2. 创建新Canvas添加水印
const watermarkedCanvas = document.createElement('canvas');
const ctx = watermarkedCanvas.getContext('2d');
// 设置新Canvas尺寸
watermarkedCanvas.width = canvas.width;
watermarkedCanvas.height = canvas.height;
// 绘制原始截图
ctx.drawImage(canvas, 0, 0);
// 添加水印
ctx.font = `${watermark.fontSize}px Arial`;
ctx.fillStyle = watermark.color;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// 计算每个水印的位置间隔
const colSpace = watermarkedCanvas.width / (watermark.cols + 1);
const rowSpace = watermarkedCanvas.height / (watermark.rows + 1);
// 保存原始画布状态
ctx.save();
// 循环绘制多个水印
for (let row = 1; row <= watermark.rows; row++) {
for (let col = 1; col <= watermark.cols; col++) {
ctx.save(); // 保存当前状态
const x = col * colSpace;
const y = row * rowSpace;
// 移动到目标位置并旋转
ctx.translate(x, y);
ctx.rotate((watermark.angle * Math.PI) / 180);
// 绘制水印文字
ctx.fillText(watermark.text, 0, 0);
ctx.restore(); // 恢复状态
}
} }
// 通过 ref 获取 vueflow 根元素
ctx.restore(); // 恢复原始状态 const vueflowRoot = flowEditorDom.querySelector('.vue-flow');
if (!vueflowRoot || !(vueflowRoot instanceof HTMLElement)) {
// 3. 存储带水印的图片 showMessage('error', '未找到 VueFlow 画布');
state.previewImage = watermarkedCanvas.toDataURL(); return;
} catch (error) { }
console.error('Capture failed', error); state.previewVisible = true;
} finally { // 截图
// 恢复原始 overflow 样式 const img = await captureFlow(vueflowRoot as HTMLElement, {
element.style.overflow = originalOverflow; type: 'png',
shouldDownload: false,
// 移除临时样式 watermark: {
document.head.removeChild(style); text: watermark.text,
fontSize: watermark.fontSize,
color: watermark.color,
angle: watermark.angle,
rows: watermark.rows,
cols: watermark.cols,
},
});
state.previewImage = img;
} catch (e) {
showMessage('error', '截图失败: ' + (e?.message || e));
} }
}; };
@@ -373,9 +258,9 @@ const downloadImage = () => {
if (state.previewImage) { if (state.previewImage) {
const link = document.createElement('a'); const link = document.createElement('a');
link.href = state.previewImage; link.href = state.previewImage;
link.download = 'screenshot.png'; // 设置下载的文件名 link.download = 'screenshot.png';
link.click(); link.click();
state.previewVisible = false; // 关闭预览弹窗 state.previewVisible = false;
} }
}; };