mirror of
https://github.com/Powerful-517/yys-editor.git
synced 2026-03-06 07:25:27 +00:00
支持水印排列
御魂2,4,6属性简称 修正截图大小 式神,御魂信息更新
This commit is contained in:
@@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<div class="toolbar">
|
||||
<div data-html2canvas-ignore="true">
|
||||
<el-button icon="Upload" type="primary" @click="prepareCapture">{{ t('import') }}</el-button>
|
||||
<el-button icon="Download" type="primary" @click="prepareCapture">{{ t('export') }}</el-button>
|
||||
<div>
|
||||
<el-button icon="Upload" type="primary" @click="handleImport">{{ t('import') }}</el-button>
|
||||
<el-button icon="Download" type="primary" @click="handleExport">{{ t('export') }}</el-button>
|
||||
<el-button icon="Share" type="primary" @click="prepareCapture">{{ t('prepareCapture') }}</el-button>
|
||||
<el-button icon="Setting" type="primary" @click="prepareCapture">{{ t('setWatermark') }}</el-button>
|
||||
<el-button icon="Setting" type="primary" @click="state.showWatermarkDialog = true">{{
|
||||
t('setWatermark')
|
||||
}}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 预览弹窗 -->
|
||||
<el-dialog id="preview-container" v-model="state.previewVisible" width="80%" :before-close="handleClose">
|
||||
<el-dialog id="preview-container" v-model="state.previewVisible" width="80%" height="80%" :before-close="handleClose">
|
||||
<div style="max-height: 500px; overflow-y: auto;">
|
||||
<Watermark text="示例水印" font="30px Arial" color="rgba(184, 184, 184, 0.3)" angle=-20>
|
||||
|
||||
<img v-if="state.previewImage" :src="state.previewImage" alt="Preview" style="width: 100%; display: block;"/>
|
||||
</Watermark>
|
||||
<img v-if="state.previewImage" :src="state.previewImage" alt="Preview" style="width: 100%; display: block;"/>
|
||||
</div>
|
||||
<!-- <img v-if="state.previewImage" :src="state.previewImage" alt="Preview" style="width: 100%; height: auto;" />-->
|
||||
<span slot="footer" class="dialog-footer">
|
||||
@@ -21,77 +21,212 @@
|
||||
<el-button type="primary" @click="downloadImage">下 载</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 水印设置弹窗 -->
|
||||
<el-dialog v-model="state.showWatermarkDialog" title="设置水印" width="30%">
|
||||
<el-form>
|
||||
<el-form-item label="水印文字">
|
||||
<el-input v-model="watermark.text"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="字体大小">
|
||||
<el-input-number v-model="watermark.fontSize" :min="10" :max="100"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="颜色">
|
||||
<el-color-picker v-model="watermark.color"></el-color-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="水印行数">
|
||||
<el-input-number v-model="watermark.rows" :min="1" :max="10"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="水印列数">
|
||||
<el-input-number v-model="watermark.cols" :min="1" :max="10"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="角度">
|
||||
<el-input-number v-model="watermark.angle" :min="-90" :max="90"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="state.showWatermarkDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="applyWatermarkSettings">确认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {inject, nextTick} from 'vue';
|
||||
import {ref, reactive} from 'vue';
|
||||
import html2canvas from "html2canvas";
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import Watermark from './Watermark.vue' // 引入 Watermark 组件
|
||||
import {useI18n} from 'vue-i18n';
|
||||
|
||||
// 获取当前的 i18n 实例
|
||||
const {t} = useI18n()
|
||||
|
||||
// 注入水印控制方法
|
||||
const watermarkControl = inject('watermarkControl');
|
||||
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '默认标题'
|
||||
}
|
||||
})
|
||||
|
||||
import {ref, reactive, toRefs} from 'vue';
|
||||
const {t} = useI18n();
|
||||
const emit = defineEmits(['handleExport', 'handleImport'])
|
||||
|
||||
// 定义响应式数据
|
||||
const state = reactive({
|
||||
previewImage: null, // 用于存储预览图像的数据URL
|
||||
previewVisible: false, // 控制预览弹窗的显示状态
|
||||
showWatermarkDialog: false, // 控制水印设置弹窗的显示状态
|
||||
});
|
||||
|
||||
const handleExport = () => {
|
||||
emit('handleExport');
|
||||
};
|
||||
|
||||
const handleImport = () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
input.onchange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) emit('handleImport', file);
|
||||
};
|
||||
input.click();
|
||||
};
|
||||
|
||||
|
||||
const watermark = reactive({
|
||||
text: '示例水印',
|
||||
fontSize: 30,
|
||||
color: 'rgba(184, 184, 184, 0.3)',
|
||||
angle: -20,
|
||||
rows: 1, // 新增行数
|
||||
cols: 1 // 新增列数
|
||||
});
|
||||
|
||||
const applyWatermarkSettings = () => {
|
||||
state.showWatermarkDialog = false;
|
||||
};
|
||||
|
||||
// 计算视觉总高度
|
||||
function calculateVisualHeight(selector) {
|
||||
// 1. 获取所有目标元素
|
||||
const elements = Array.from(document.querySelectorAll(selector));
|
||||
|
||||
// 2. 获取元素位置信息并排序
|
||||
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');
|
||||
}
|
||||
};
|
||||
|
||||
const prepareCapture = async () => {
|
||||
state.previewVisible = true; // 显示预览弹窗
|
||||
state.previewVisible = true;
|
||||
|
||||
// 创建临时样式
|
||||
const style = document.createElement('style')
|
||||
style.id = 'capture-style'
|
||||
style.textContent = `
|
||||
.ql-container.ql-snow {
|
||||
border: none !important;
|
||||
}
|
||||
`
|
||||
document.head.appendChild(style)
|
||||
// 捕获页面元素并生成图片
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `.ql-container.ql-snow { border: none !important; }`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
try {
|
||||
const element = document.querySelector('#main-container'); // 替换为要捕获的元素选择器
|
||||
const element = document.querySelector('#main-container');
|
||||
let totalHeight = calculateVisualHeight('[data-html2canvas-ignore]') + calculateVisualHeight('.ql-toolbar');
|
||||
console.log('所有携带指定属性的元素高度之和:', totalHeight);
|
||||
if (!element) {
|
||||
console.error('Element with ID "main-container" not found.');
|
||||
state.previewVisible = false;
|
||||
console.error('Element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 生成原始截图
|
||||
const canvas = await html2canvas(element, {
|
||||
ignoreElements: ignoreElements,
|
||||
height: element.scrollHeight,
|
||||
}
|
||||
);
|
||||
state.previewImage = canvas.toDataURL();
|
||||
if (!state.previewImage) {
|
||||
console.error('Failed to generate image data URL.');
|
||||
state.previewVisible = false;
|
||||
state.previewVisible = false;
|
||||
ignoreElements: ignoreElements,
|
||||
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(); // 恢复状态
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore(); // 恢复原始状态
|
||||
// 3. 存储带水印的图片
|
||||
state.previewImage = watermarkedCanvas.toDataURL();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to capture screenshot', error);
|
||||
state.previewVisible = false;
|
||||
console.error('Capture failed', error);
|
||||
} finally {
|
||||
// 清除临时样式
|
||||
document.getElementById('capture-style')?.remove()
|
||||
document.head.removeChild(style);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -119,7 +254,7 @@ const handleClose = (done) => {
|
||||
right: 0;
|
||||
height: 48px;
|
||||
background: #f8f8f8;
|
||||
//border-bottom: 1px solid #eee; display: flex;
|
||||
//border-bottom: 1px solid #eee; display: flex;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
z-index: 100;
|
||||
|
||||
Reference in New Issue
Block a user