富文本复制粘贴,水印自动扩展,其他命名优化

pull/4/head
rookie4show 1 month ago
parent a935369927
commit 37c45ef3b6
  1. 7
      src/App.vue
  2. 22
      src/components/ShikigamiProperty.vue
  3. 52
      src/components/Watermark.vue
  4. 17
      src/components/YuhunSelect.vue
  5. 156
      src/components/Yys.vue
  6. 4
      src/locales/zh.json

@ -5,8 +5,10 @@ import Watermark from './components/Watermark.vue' // 引入 Watermark 组件
<template> <template>
<main id="main-container"> <main id="main-container">
<!-- 添加 Watermark 组件 -->
<Watermark text="示例水印" font="30px Arial" color="rgba(184, 184, 184, 0.3)" angle=-20 >
<Yys /> <Yys />
<Watermark text="示例水印" font="30px Arial" color="rgba(184, 184, 184, 0.3)" angle=-20 /> <!-- 添加 Watermark 组件 --> </Watermark>
</main> </main>
</template> </template>
@ -15,7 +17,8 @@ import Watermark from './components/Watermark.vue' // 引入 Watermark 组件
#main-container { #main-container {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100vh; /* 视口高度 */ min-height: 100vh; /* 允许容器扩展 */
//position: relative;
} }
/* 如果 Yys 组件需要特定的高度或布局,可以根据需要调整 */ /* 如果 Yys 组件需要特定的高度或布局,可以根据需要调整 */

@ -18,15 +18,15 @@
<el-form :model="shikigami" label-width="120px"> <el-form :model="shikigami" label-width="120px">
<el-form-item label="等级要求"> <el-form-item label="等级要求">
<el-radio-group v-model="shikigami.levelRequired" class="ml-4"> <el-radio-group v-model="shikigami.levelRequired" class="ml-4">
<el-radio label="40" size="large">40</el-radio> <el-radio value="40" size="large">40</el-radio>
<el-radio label="0" size="large">献祭</el-radio> <el-radio value="0" size="large">献祭</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="技能要求"> <el-form-item label="技能要求">
<el-radio-group v-model="shikigami.skillRequiredMode" class="ml-4"> <el-radio-group v-model="shikigami.skillRequiredMode" class="ml-4">
<el-radio label="all" size="large">全满</el-radio> <el-radio value="all" size="large">全满</el-radio>
<el-radio label="111" size="large">111</el-radio> <el-radio value="111" size="large">111</el-radio>
<el-radio label="custom" size="large">自定义</el-radio> <el-radio value="custom" size="large">自定义</el-radio>
</el-radio-group> </el-radio-group>
<div v-if="shikigami.skillRequiredMode === 'custom'" style="display: flex; flex-direction: row; width: 100%;"> <div v-if="shikigami.skillRequiredMode === 'custom'" style="display: flex; flex-direction: row; width: 100%;">
<el-select v-for="(value, key, index) in shikigami.skillRequired" :placeholder="value" <el-select v-for="(value, key, index) in shikigami.skillRequired" :placeholder="value"
@ -281,13 +281,23 @@ const openYuhunSelect = (index) => {
const closeYuhunSelect = () => showYuhunSelect.value = false const closeYuhunSelect = () => showYuhunSelect.value = false
const updateYuhunSelect = (yuhun) => { const updateYuhunSelect = (yuhun, operator) => {
showYuhunSelect.value = false showYuhunSelect.value = false
//Update
if (operator == "Update") {
if (yuhunIndex.value >= 0) if (yuhunIndex.value >= 0)
shikigami.value.yuhun.yuhunSetEffect[yuhunIndex.value] = (JSON.parse(JSON.stringify(yuhun))) shikigami.value.yuhun.yuhunSetEffect[yuhunIndex.value] = (JSON.parse(JSON.stringify(yuhun)))
else else
shikigami.value.yuhun.yuhunSetEffect.push(JSON.parse(JSON.stringify(yuhun))) shikigami.value.yuhun.yuhunSetEffect.push(JSON.parse(JSON.stringify(yuhun)))
} }
//Delete
else if (operator == "Remove") {
if (yuhunIndex.value >= 0) {
// 使splice
shikigami.value.yuhun.yuhunSetEffect.splice(yuhunIndex.value, 1);
}
}
}
const cancel = () => { const cancel = () => {
emit('closeProperty') emit('closeProperty')

@ -1,5 +1,8 @@
<template> <template>
<div ref="watermarkContainer" class="watermark-container"></div> <div class="watermark-container">
<!-- 插槽内容 Yys 组件 -->
<slot></slot>
</div>
</template> </template>
<script setup> <script setup>
@ -12,7 +15,7 @@ const props = defineProps({
}, },
font: { font: {
type: String, type: String,
default: '20px Arial' default: '30px Arial'
}, },
color: { color: {
type: String, type: String,
@ -24,14 +27,11 @@ const props = defineProps({
} }
}); });
const watermarkContainer = ref(null);
onMounted(() => { onMounted(() => {
createWatermark(); createWatermark();
}); });
const createWatermark = () => { const createWatermark = () => {
const container = watermarkContainer.value;
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
@ -65,29 +65,43 @@ const createWatermark = () => {
// canvas base64 // canvas base64
const watermarkUrl = canvas.toDataURL('image/png'); const watermarkUrl = canvas.toDataURL('image/png');
// div //
const watermarkDiv = document.createElement('div'); const container = document.querySelector('.watermark-container');
watermarkDiv.style.position = 'absolute'; container.style.backgroundImage = `url(${watermarkUrl})`;
watermarkDiv.style.top = '0'; container.style.backgroundRepeat = 'repeat';
watermarkDiv.style.left = '0';
watermarkDiv.style.width = '100%'; // resize
watermarkDiv.style.height = '100%'; // const observer = new ResizeObserver(entries => {
watermarkDiv.style.backgroundImage = `url(${watermarkUrl})`; // entries.forEach(entry => {
watermarkDiv.style.backgroundRepeat = 'repeat'; // container.style.backgroundImage = `url(${watermarkUrl})`;
watermarkDiv.style.pointerEvents = 'none'; // // });
// });
// div // observer.observe(container);
container.appendChild(watermarkDiv);
}; };
</script> </script>
<style scoped> <style scoped>
.watermark-container { .watermark-container {
position: absolute; /* 使用绝对定位 */ position: relative;
width: 100%;
height: 100%;
min-height: 100vh; /* 基础高度 */
display: flex;
flex-direction: column;
}
/* 使用伪元素添加水印背景 */
.watermark-container::before {
content: '';
position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background-image: inherit;
background-repeat: repeat;
pointer-events: none; /* 确保水印不会干扰用户交互 */ pointer-events: none; /* 确保水印不会干扰用户交互 */
z-index: 9999; /* 确保水印在最顶层 */
//background-attachment: fixed; /* */
} }
</style> </style>

@ -1,12 +1,15 @@
<template> <template>
<el-dialog v-model="show" :title="t('yuhunSelect')" @close="cancel"> <el-dialog v-model="show" :title="t('yuhunSelect')" @close="cancel">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>当前选择{{ current.name }}</span> <span>当前选择{{ current.name }}</span>
<el-button type="danger" icon="Delete" round @click="remove()"></el-button>
</div>
<el-tabs v-model="activeName" type="card" class="demo-tabs" @tab-click="handleTabClick"> <el-tabs v-model="activeName" type="card" class="demo-tabs" @tab-click="handleTabClick">
<el-tab-pane v-for="type in yuhunTypes" :key="type.name" :label="type.label" :name="type.name"> <el-tab-pane v-for="type in yuhunTypes" :key="type.name" :label="type.label" :name="type.name">
<el-space wrap size="large"> <el-space wrap size="large">
<div v-for="shikigami in filterShikigamiByType(activeName)" :key="shikigami.name"> <div v-for="yuhun in filterYuhunByType(activeName)" :key="yuhun.name">
<el-button style="width: 100px; height: 100px;" @click="confirm(shikigami)"> <el-button style="width: 100px; height: 100px;" @click="confirm(yuhun)">
<img :src="shikigami.avatar" style="width: 99px; height: 99px;"> <img :src="yuhun.avatar" style="width: 99px; height: 99px;">
</el-button> </el-button>
</div> </div>
</el-space> </el-space>
@ -60,7 +63,7 @@ const handleTabClick = (tab) => {
console.log(tab.paneName); console.log(tab.paneName);
}; };
const filterShikigamiByType = (type) => { const filterYuhunByType = (type) => {
if (type.toLowerCase() === 'all') return yuhunData; if (type.toLowerCase() === 'all') return yuhunData;
return yuhunData.filter(yuhun => yuhun.type.toLowerCase() === type.toLowerCase()); return yuhunData.filter(yuhun => yuhun.type.toLowerCase() === type.toLowerCase());
}; };
@ -70,6 +73,10 @@ const cancel = () => {
}; };
const confirm = (item) => { const confirm = (item) => {
emit('updateYuhunSelect', JSON.parse(JSON.stringify(item))); emit('updateYuhunSelect', JSON.parse(JSON.stringify(item)), 'Update');
}; };
const remove = () => {
emit('updateYuhunSelect',undefined,'Remove')
}
</script> </script>

@ -20,42 +20,71 @@
<template #item="{ element: group, index: groupIndex }"> <template #item="{ element: group, index: groupIndex }">
<el-row :span="24"> <el-row :span="24">
<div> <div>
<div style="display: flex; justify-content: flex-end;" data-html2canvas-ignore="true"> <div>
<div style="display: flex; justify-content: space-between;" data-html2canvas-ignore="true">
<div>
<el-button type="primary" icon="CopyDocument" @click="copy(group.shortDescription)">{{ t('Copy') }}
</el-button>
<el-button type="primary" icon="Document" @click="paste(groupIndex,'shortDescription')">{{
t('Paste')
}}
</el-button>
</div>
<div>
<el-button class="drag-handle" type="primary" icon="Rank" circle></el-button> <el-button class="drag-handle" type="primary" icon="Rank" circle></el-button>
<el-button type="danger" icon="Delete" circle @click="removeGroup(groupIndex)"></el-button>
<el-button type="primary" icon="Plus" circle @click="addGroup"></el-button> <el-button type="primary" icon="Plus" circle @click="addGroup"></el-button>
<el-button type="danger" icon="Delete" circle @click="removeGroup(groupIndex)"></el-button>
</div>
</div>
<QuillEditor v-model:content="group.shortDescription" contentType="html" theme="snow"
:toolbar="toolbarOptions"/>
</div> </div>
<!-- <div style="display: flex; justify-content: flex-end;" data-html2canvas-ignore="true">-->
<!-- <el-button class="drag-handle" type="primary" icon="Rank" circle></el-button>-->
<!-- <el-button type="danger" icon="Delete" circle @click="removeGroup(groupIndex)"></el-button>-->
<!-- <el-button type="primary" icon="Plus" circle @click="addGroup"></el-button>-->
<!-- </div>-->
<div> <div>
<draggable :list="group" item-key="name" style="display: flex; flex-direction: row; width: 20%;"> <draggable :list="group.groupInfo" item-key="name" style="display: flex; flex-direction: row; width: 20%;">
<template #item="{element : position, index:positionIndex}"> <template #item="{element : position, index:positionIndex}">
<div> <div>
<el-col> <el-col>
<el-card :body-style="{ padding: '0px' } "> <el-card :body-style="{ padding: '0px', display: 'flex', 'flex-direction': 'column', 'justify-content': 'center', 'align-items': 'center' }">
<div>
<!-- Add delete button here --> <!-- Add delete button here -->
<el-button type="danger" icon="Delete" circle @click="removeGroupElement(groupIndex, positionIndex)" data-html2canvas-ignore="true"></el-button> <el-button type="danger" icon="Delete" circle
<el-button type="primary" icon="Plus" circle @click="addGroupElement(groupIndex)" data-html2canvas-ignore="true"></el-button> @click="removeGroupElement(groupIndex, positionIndex)"
<img :src="position.avatar || '/assets/Shikigami/default.png'" class="image" data-html2canvas-ignore="true"></el-button>
<el-button type="primary" icon="Plus" circle @click="addGroupElement(groupIndex)"
data-html2canvas-ignore="true"></el-button>
</div>
<div class="avatar-wrapper">
<img :src="position.avatar || '/assets/Shikigami/default.png'"
class="avatar-image"
@click="editShikigami(groupIndex,positionIndex)"/> @click="editShikigami(groupIndex,positionIndex)"/>
</div>
<div style="padding: 14px; width: 95px"> <div style="padding: 14px; width: 95px">
<span>{{ position.name || "" }}</span> <span>{{ position.name || "" }}</span>
<div class="bottom" data-html2canvas-ignore="true"> <div class="bottom" data-html2canvas-ignore="true">
<el-button @click="editProperties(groupIndex,positionIndex)">配置属性</el-button> <el-button @click="editProperty(groupIndex,positionIndex)">{{ t('editProperties') }}
</el-button>
</div> </div>
<!-- properties--> <!-- properties-->
<!-- {"edit":true,"yuhun":{"yuhunSetEffect":[{"name":"狰","type":"attack","avatar":"/assets/Yuhun/狰.png"}],"target":"伤害输出","property2":["Attack"],"property4":["Attack"],"property6":["Crit","CritDamage"]},"levelRequired":"40","speed":"","skillRequiredMode":"all","skillRequired":["技能一","技能二","技能三"]}--> <!-- {"edit":true,"yuhun":{"yuhunSetEffect":[{"name":"狰","type":"attack","avatar":"/assets/Yuhun/狰.png"}],"target":"伤害输出","property2":["Attack"],"property4":["Attack"],"property6":["Crit","CritDamage"]},"levelRequired":"40","speed":"","skillRequiredMode":"all","skillRequired":["技能一","技能二","技能三"]}-->
<div v-if="position.properties"> <div v-if="position.properties">
<div> <div>
<span style="display: inline-block; width: 100px; height: 25px; background-color: #666; border-top-left-radius: 5px; border-top-right-radius: 5px; margin-right: 5px; color: white; text-align: center; white-space: pre-wrap "> <span
style="display: inline-block; width: 100px; height: 25px; background-color: #666; border-top-left-radius: 5px; border-top-right-radius: 5px; margin-right: 5px; color: white; text-align: center; white-space: pre-wrap ">
{{ position.properties.yuhun.yuhunSetEffect.map(item => item.name).join(' ') }} {{ position.properties.yuhun.yuhunSetEffect.map(item => item.name).join(' ') }}
</span> </span>
<span style="display: inline-block; width: 100px; height: 25px; background-color: #666; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; margin-right: 5px; color: white; text-align: center; white-space: pre-wrap "> <span
style="display: inline-block; width: 100px; height: 25px; background-color: #666; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; margin-right: 5px; color: white; text-align: center; white-space: pre-wrap ">
{{ t('yuhun_target.' + position.properties.yuhun.target) }}· {{ t('yuhun_target.' + position.properties.yuhun.target) }}·
</span> </span>
</div> </div>
<div v-for="(value, key, index) in position.properties"> <div v-for="(value, key, index) in position.properties" data-html2canvas-ignore="true">
<span>{{ key }}</span> : <span>{{ value || '-' }}</span> <span>{{ key }}</span> : <span>{{ value || '-' }}</span>
</div> </div>
</div> </div>
@ -68,7 +97,14 @@
</div> </div>
<div> <div>
<QuillEditor v-model:content="content" contentType="html" theme="snow" :toolbar="toolbarOptions"/> <div data-html2canvas-ignore="true">
<el-button type="primary" icon="CopyDocument" @click="copy(group.details)">{{ t('Copy') }}</el-button>
<el-button type="primary" icon="Document" @click="paste(groupIndex,'details')">{{
t('Paste')
}}
</el-button>
</div>
<QuillEditor v-model:content="group.details" contentType="html" theme="snow" :toolbar="toolbarOptions"/>
</div> </div>
</div> </div>
@ -84,7 +120,7 @@
<!-- 现有的代码 --> <!-- 现有的代码 -->
<div style="margin: 20px" data-html2canvas-ignore="true"> <div style="margin: 20px" data-html2canvas-ignore="true">
<!-- 触发截图的按钮 --> <!-- 触发截图的按钮 -->
<button @click="prepareCapture">生成截图</button> <el-button type="primary" @click="prepareCapture">{{ t('prepareCapture') }}</el-button>
</div> </div>
<!-- 预览弹窗 --> <!-- 预览弹窗 -->
@ -120,8 +156,16 @@ const state = reactive({
showSelectShikigami: false, showSelectShikigami: false,
showProperty: false, showProperty: false,
groups: [ groups: [
[{}, {}, {}, {}, {}], {
[{}, {}, {}, {}, {}] shortDescription: '',
groupInfo: [{}, {}, {}, {}, {}],
details: ''
},
{
shortDescription: '',
groupInfo: [{}, {}, {}, {}, {}],
details: ''
},
], ],
groupIndex: 0, groupIndex: 0,
positionIndex: 0, positionIndex: 0,
@ -130,31 +174,40 @@ const state = reactive({
previewVisible: false, // previewVisible: false, //
}); });
const clipboard = ref('');
// i18n // i18n
const {t} = useI18n() const {t} = useI18n()
//
const content = ref('<p>初始内容</p>') const copy = (str) => {
const content1 = ref('<p>初始内容1</p>') clipboard.value = str
}
const paste = (groupIndex, type) => {
if ('shortDescription' == type)
state.groups[groupIndex].shortDescription = clipboard.value
else if ('details' == type)
state.groups[groupIndex].details = clipboard.value
}
// //
const toolbarOptions = [ const toolbarOptions = [
[{'color': []}, {'background': []}], [{'color': []}, {'background': []}],
['bold', 'italic', 'underline', 'strike'], ['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'], // ['blockquote', 'code-block'],
['link', 'image', 'video', 'formula'], // ['link', 'image', 'video', 'formula'],
[{'header': 1}, {'header': 2}], [{'header': 1}, {'header': 2}],
[{'list': 'ordered'}, {'list': 'bullet'}, {'list': 'check'}], [{'list': 'ordered'}, {'list': 'bullet'}, {'list': 'check'}],
[{ 'script': 'sub'}, { 'script': 'super' }], // [{ 'script': 'sub'}, { 'script': 'super' }],
[{'indent': '-1'}, {'indent': '+1'}], [{'indent': '-1'}, {'indent': '+1'}],
[{ 'direction': 'rtl' }], // [{ 'size': ['small', false, 'large', 'huge'] }],
[{ 'size': ['small', false, 'large', 'huge'] }], // [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'header': [1, 2, 3, 4, 5, 6, false] }], // [{ 'font': [] }],
[{ 'color': [] }, { 'background': [] }],
[{ 'font': [] }],
[{'align': []}], [{'align': []}],
['clean'] [{'direction': 'rtl'}],
// ['clean']
]; ];
// //
@ -175,32 +228,32 @@ const updateShikigami = (shikigami) => {
console.log("parent====> ", shikigami); console.log("parent====> ", shikigami);
state.showSelectShikigami = false; state.showSelectShikigami = false;
const oldProperties = state.groups[state.groupIndex][state.positionIndex].properties; const oldProperties = state.groups[state.groupIndex].groupInfo[state.positionIndex].properties;
state.groups[state.groupIndex][state.positionIndex] = shikigami; state.groups[state.groupIndex].groupInfo[state.positionIndex] = shikigami;
state.groups[state.groupIndex][state.positionIndex].properties = oldProperties; state.groups[state.groupIndex].groupInfo[state.positionIndex].properties = oldProperties;
}; };
const editProperties = (groupIndex, positionIndex) => { const editProperty = (groupIndex, positionIndex) => {
state.showProperty = true; state.showProperty = true;
state.groupIndex = groupIndex; state.groupIndex = groupIndex;
state.positionIndex = positionIndex; state.positionIndex = positionIndex;
state.currentShikigami = state.groups[groupIndex][positionIndex]; state.currentShikigami = state.groups[groupIndex].groupInfo[positionIndex];
console.log("currentShikigami", JSON.stringify(state.currentShikigami)); console.log("currentShikigami", JSON.stringify(state.currentShikigami));
}; };
const closeProperty = () => { const closeProperty = () => {
state.showProperty = false; state.showProperty = false;
state.currentShikigami = state.groups[state.groupIndex][state.positionIndex]; state.currentShikigami = {};
}; };
const updateProperty = (property) => { const updateProperty = (property) => {
state.showProperty = false; state.showProperty = false;
state.currentShikigami = {}; state.currentShikigami = {};
state.groups[state.groupIndex][state.positionIndex].properties = property; state.groups[state.groupIndex].groupInfo[state.positionIndex].properties = property;
}; };
const removeGroupElement = (groupIndex, positionIndex) => { const removeGroupElement = (groupIndex, positionIndex) => {
state.groups[groupIndex].splice(positionIndex, 1); state.groups[groupIndex].groupInfo.splice(positionIndex, 1);
}; };
const removeGroup = (groupIndex) => { const removeGroup = (groupIndex) => {
@ -208,11 +261,15 @@ const removeGroup = (groupIndex) => {
}; };
const addGroup = () => { const addGroup = () => {
state.groups.push([{}, {}, {}, {}, {}]); state.groups.push({
shortDescription: '',
groupInfo: [{}, {}, {}, {}, {}],
details: ''
});
}; };
const addGroupElement = (groupIndex) => { const addGroupElement = (groupIndex) => {
state.groups[groupIndex].push({}); state.groups[groupIndex].groupInfo.push({});
}; };
const ignoreElements = (element) => { const ignoreElements = (element) => {
@ -275,4 +332,27 @@ const handleClose = (done) => {
.ql-toolbar { .ql-toolbar {
display: none; display: none;
} }
/* 正方形容器 */
.avatar-wrapper {
width: 100px; /* 正方形边长 */
height: 100px; /* 与宽度相同 */
position: relative;
overflow: hidden; /* 隐藏超出部分 */
border-radius: 50%; /* 圆形裁剪 */
//border: 2px solid #fff; /* */
box-shadow: 0 2px 8px rgba(0,0,0,0.1); /* 可选:添加阴影 */
}
/* 图片样式 */
.avatar-image {
width: 100%;
height: 100%;
object-fit: cover; /* 关键属性:保持比例填充容器 */
object-position: center; /* 居中显示 */
display: block;
cursor: pointer;
}
</style> </style>

@ -1,4 +1,8 @@
{ {
"Copy": "复制",
"Paste": "粘贴",
"editProperties": "配置属性",
"prepareCapture": "生成截图",
"yuhun_target": { "yuhun_target": {
"1": "伤害", "1": "伤害",
"2": "命中", "2": "命中",

Loading…
Cancel
Save