407 lines
18 KiB
Vue
407 lines
18 KiB
Vue
<script setup>
|
|
import Editor from "../Components/Editor.vue";
|
|
import Sections from "../Layouts/Sections.vue";
|
|
import Card from "../Components/Card/Card.vue";
|
|
import {computed, ref, watch} from "vue";
|
|
import Button from "../Components/Button/Button.vue";
|
|
import ListStrate from "../Components/List/ListStrate.vue";
|
|
import Input from "../Components/Input/Input.vue";
|
|
import {now} from "@vueuse/core";
|
|
import Select from "../Components/Select/Select.vue";
|
|
import {Link, router} from "@inertiajs/vue3";
|
|
import CardBack from "../Components/Card/CardBack.vue";
|
|
|
|
const props = defineProps({
|
|
template: Object
|
|
})
|
|
|
|
const editor = ref()
|
|
|
|
const content = ref(props.template?.content ?? [])
|
|
const zoom = 1
|
|
const zoom_min = 0.10
|
|
const zoom_max = 5.0
|
|
const page_format_mm = [210, 297]
|
|
const page_margins = "2cm 1.5cm 2cm 3cm"
|
|
const display = "grid" // ["grid", "vertical", "horizontal"]
|
|
const mounted = false // will be true after this component is mounted
|
|
const undo_count = -1 // contains the number of times user can undo (= current position in content_history)
|
|
const content_history = [] // contains the content states for undo/redo operations
|
|
const variables = ref(props.template?.variables_config ?? {})
|
|
const variablesItems = [
|
|
{
|
|
key: 'Поле ввода',
|
|
value: 'text'
|
|
},
|
|
{
|
|
key: 'Поле выбора',
|
|
value: 'select'
|
|
}
|
|
]
|
|
|
|
const elementInfo = ref({})
|
|
const activeElement = ref()
|
|
const activeElements = ref()
|
|
const currentTextStyle = ref('')
|
|
|
|
const getCurrentTextStyle = (style) => {
|
|
currentTextStyle.value = style
|
|
}
|
|
|
|
const mappedTags = {
|
|
'P': 'Текст',
|
|
'H1': 'Заголовок 1',
|
|
'H2': 'Заголовок 2',
|
|
'VAR': 'Переменная'
|
|
}
|
|
|
|
const formatAlignLeft = () => {
|
|
document.execCommand("justifyLeft")
|
|
}
|
|
|
|
const formatAlignCenter = () => {
|
|
document.execCommand("justifyCenter")
|
|
}
|
|
|
|
const formatAlignRight = () => {
|
|
document.execCommand("justifyRight")
|
|
}
|
|
|
|
const formatAlignJustify = () => {
|
|
document.execCommand("justifyFull")
|
|
}
|
|
|
|
const formatTextBold = () => {
|
|
document.execCommand("bold")
|
|
}
|
|
const formatTextItalic = () => {
|
|
document.execCommand("italic")
|
|
}
|
|
const formatTextUnderline = () => {
|
|
document.execCommand("underline")
|
|
}
|
|
const formatTextStrikethrough = () => {
|
|
document.execCommand("strikethrough")
|
|
}
|
|
|
|
const formatFirstLine = () => {
|
|
if (activeElement.value.style.textIndent) {
|
|
activeElement.value.style.textIndent = ''
|
|
return
|
|
} else if (activeElement.value.parentElement.tagName === 'P' &&
|
|
activeElement.value.parentElement.style.textIndent) {
|
|
activeElement.value.parentElement.style.textIndent = ''
|
|
return
|
|
}
|
|
|
|
if (activeElement.value.tagName === 'P') {
|
|
activeElement.value.style.textIndent = '1.25cm'
|
|
} else if (activeElement.value.parentElement.tagName === 'P') {
|
|
activeElement.value.parentElement.style.textIndent = '1.25cm'
|
|
}
|
|
}
|
|
const clearBackground = () => {
|
|
activeElement.value.style.background = ''
|
|
}
|
|
|
|
const isVariable = computed(() => {
|
|
return activeElement.value.getAttribute('brs-variable')
|
|
})
|
|
const createVariable = () => {
|
|
const selection = window.getSelection();
|
|
|
|
// Проверяем, есть ли выделение
|
|
if (selection.rangeCount === 0) return;
|
|
|
|
const range = selection.getRangeAt(0);
|
|
const selectedText = range.toString();
|
|
|
|
// Проверяем, что текст действительно выделен
|
|
if (!selectedText) return;
|
|
|
|
if (isVariable.value === 'true') {
|
|
const elementId = activeElement.value.getAttribute('brs-element-id')
|
|
delete variables.value[elementId]
|
|
activeElement.value.removeAttribute('brs-variable')
|
|
activeElement.value.removeAttribute('brs-type')
|
|
activeElement.value.removeAttribute('brs-element-id')
|
|
} else {
|
|
// Создаем span элемент
|
|
const span = document.createElement('span');
|
|
const spanId = now().toString()
|
|
span.textContent = selectedText;
|
|
span.setAttribute('brs-variable', 'true');
|
|
span.setAttribute('brs-type', 'text');
|
|
span.setAttribute('brs-element-id', spanId);
|
|
|
|
// Удаляем выделенный текст и вставляем span
|
|
range.deleteContents();
|
|
range.insertNode(span);
|
|
|
|
variables.value = {
|
|
...variables.value,
|
|
[spanId]: {
|
|
name: selectedText,
|
|
type: 'text',
|
|
label: selectedText
|
|
}
|
|
}
|
|
}
|
|
|
|
// Очищаем выделение
|
|
selection.removeAllRanges();
|
|
|
|
console.log('Создан span для текста:', selectedText);
|
|
}
|
|
|
|
watch(activeElement, (element) => {
|
|
if (!element) return
|
|
|
|
elementInfo.value = {}
|
|
|
|
if (element.getAttribute('brs-variable') === 'true') {
|
|
const elementId = element.getAttribute('brs-element-id')
|
|
elementInfo.value.id = elementId
|
|
elementInfo.value.element = 'VAR'
|
|
elementInfo.value.name = mappedTags[elementInfo.value.element]
|
|
elementInfo.value.type = element.getAttribute('brs-type')
|
|
elementInfo.value.value = ''
|
|
return
|
|
}
|
|
|
|
elementInfo.value.name = mappedTags[element.tagName]
|
|
elementInfo.value.element = element.tagName
|
|
})
|
|
|
|
const updateVariableType = (elementId, type) => {
|
|
const variableKeys = Object.keys(variables.value[elementId])
|
|
if (variableKeys.includes('values')) {
|
|
if (type !== 'select') {
|
|
delete variables.value[elementId].values
|
|
delete variables.value[elementId].value
|
|
}
|
|
} else if (type === 'select'){
|
|
variables.value[elementId].values = []
|
|
}
|
|
}
|
|
|
|
const updateVariableValue = (elementId, value) => {
|
|
const variableType = variables.value[elementId].type
|
|
if (variableType === 'select') {
|
|
variables.value[elementId].value = value
|
|
variables.value[elementId].values = value.split(',').map(item => item.trim());
|
|
}
|
|
}
|
|
|
|
const saveTemplate = () => {
|
|
const data = {
|
|
...props.template,
|
|
content: content.value,
|
|
variables_config: variables.value
|
|
}
|
|
|
|
router.post('/editor', data)
|
|
}
|
|
|
|
const documentPrint = () => {
|
|
window.print()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Sections>
|
|
<template #leftbar>
|
|
<Card>
|
|
<template #footer>
|
|
<CardBack :tag="Link" href="/" />
|
|
</template>
|
|
</Card>
|
|
</template>
|
|
<Card>
|
|
<template #header>
|
|
<div class="flex flex-row gap-x-3">
|
|
<div class="flex flex-row gap-x-1">
|
|
<Button icon @click="formatAlignLeft">
|
|
<template #icon>
|
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
viewBox="0 0 24 24">
|
|
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path d="M4 6h16"></path>
|
|
<path d="M4 12h10"></path>
|
|
<path d="M4 18h14"></path>
|
|
</g>
|
|
</svg>
|
|
</template>
|
|
</Button>
|
|
<Button icon @click="formatAlignCenter">
|
|
<template #icon>
|
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
viewBox="0 0 24 24">
|
|
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path d="M4 6h16"></path>
|
|
<path d="M8 12h8"></path>
|
|
<path d="M6 18h12"></path>
|
|
</g>
|
|
</svg>
|
|
</template>
|
|
</Button>
|
|
<Button icon @click="formatAlignRight">
|
|
<template #icon>
|
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
viewBox="0 0 24 24">
|
|
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path d="M4 6h16"></path>
|
|
<path d="M10 12h10"></path>
|
|
<path d="M6 18h14"></path>
|
|
</g>
|
|
</svg>
|
|
</template>
|
|
</Button>
|
|
<Button icon @click="formatAlignJustify">
|
|
<template #icon>
|
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
viewBox="0 0 24 24">
|
|
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path d="M4 6h16"></path>
|
|
<path d="M4 12h16"></path>
|
|
<path d="M4 18h12"></path>
|
|
</g>
|
|
</svg>
|
|
</template>
|
|
</Button>
|
|
</div>
|
|
<div class="flex flex-row gap-x-1">
|
|
<Button icon @click="formatTextBold">
|
|
<template #icon>
|
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
viewBox="0 0 24 24">
|
|
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path d="M7 5h6a3.5 3.5 0 0 1 0 7H7z"></path>
|
|
<path d="M13 12h1a3.5 3.5 0 0 1 0 7H7v-7"></path>
|
|
</g>
|
|
</svg>
|
|
</template>
|
|
</Button>
|
|
<Button icon @click="formatTextItalic">
|
|
<template #icon>
|
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
viewBox="0 0 24 24">
|
|
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path d="M11 5h6"></path>
|
|
<path d="M7 19h6"></path>
|
|
<path d="M14 5l-4 14"></path>
|
|
</g>
|
|
</svg>
|
|
</template>
|
|
</Button>
|
|
<Button icon @click="formatTextUnderline">
|
|
<template #icon>
|
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
viewBox="0 0 24 24">
|
|
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path d="M7 5v5a5 5 0 0 0 10 0V5"></path>
|
|
<path d="M5 19h14"></path>
|
|
</g>
|
|
</svg>
|
|
</template>
|
|
</Button>
|
|
<Button icon @click="formatTextStrikethrough">
|
|
<template #icon>
|
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
viewBox="0 0 24 24">
|
|
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path d="M5 12h14"></path>
|
|
<path
|
|
d="M16 6.5A4 2 0 0 0 12 5h-1a3.5 3.5 0 0 0 0 7h2a3.5 3.5 0 0 1 0 7h-1.5a4 2 0 0 1-4-1.5"></path>
|
|
</g>
|
|
</svg>
|
|
</template>
|
|
</Button>
|
|
</div>
|
|
<div class="flex flex-row gap-x-1">
|
|
<Button icon @click="formatFirstLine">
|
|
<template #icon>
|
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6H9"></path><path d="M20 12h-7"></path><path d="M20 18H9"></path><path d="M4 8l4 4l-4 4"></path></g></svg>
|
|
</template>
|
|
</Button>
|
|
<Button icon @click="documentPrint">
|
|
<template #icon>
|
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 17h2a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h2"></path><path d="M17 9V5a2 2 0 0 0-2-2H9a2 2 0 0 0-2 2v4"></path><rect x="7" y="13" width="10" height="8" rx="2"></rect></g></svg>
|
|
</template>
|
|
</Button>
|
|
<Button icon @click="createVariable">
|
|
<template #icon>
|
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 4C2.5 9 2.5 14 5 20M19 4c2.5 5 2.5 10 0 16M9 9h1c1 0 1 1 2.016 3.527C13 15 13 16 14 16h1"></path><path d="M8 16c1.5 0 3-2 4-3.5S14.5 9 16 9"></path></g></svg>
|
|
</template>
|
|
</Button>
|
|
<Button icon @click="clearBackground">
|
|
<template #icon>
|
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
viewBox="0 0 24 24">
|
|
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
|
stroke-linejoin="round">
|
|
<path d="M5 12h14"></path>
|
|
<path
|
|
d="M16 6.5A4 2 0 0 0 12 5h-1a3.5 3.5 0 0 0 0 7h2a3.5 3.5 0 0 1 0 7h-1.5a4 2 0 0 1-4-1.5"></path>
|
|
</g>
|
|
</svg>
|
|
</template>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<Editor id="editor"
|
|
ref="editor"
|
|
v-model="content"
|
|
:zoom="zoom"
|
|
:page_format_mm="page_format_mm"
|
|
:page_margins="page_margins"
|
|
:display="display"
|
|
@update:current-style="getCurrentTextStyle"
|
|
v-model:active-element="activeElement"
|
|
v-model:active-elements="activeElements"
|
|
/>
|
|
</Card>
|
|
<template #rightbar>
|
|
<Card>
|
|
<template #header>
|
|
<span>
|
|
{{ elementInfo.name ?? 'Нет активного элемента' }}
|
|
</span>
|
|
</template>
|
|
<template v-if="elementInfo.name">
|
|
<ListStrate header="Параметры">
|
|
<div class="flex flex-col gap-y-1">
|
|
<Input v-if="elementInfo?.id" label="Идентификатор" v-model:value="elementInfo.id" disabled />
|
|
<!-- <Input v-if="elementInfo?.type" label="Тип" v-model:value="elementInfo.type" disabled />-->
|
|
<Input v-if="variables[elementInfo.id].label" label="Наименование" v-model:value="variables[elementInfo.id].label" />
|
|
</div>
|
|
</ListStrate>
|
|
<ListStrate v-if="elementInfo.element === 'VAR'" header="Заполнение">
|
|
<div class="flex flex-col gap-y-1">
|
|
<Select label="Тип" v-model:value="variables[elementInfo.id].type" @update:value="value => updateVariableType(elementInfo.id, value)" :options="variablesItems" />
|
|
<Input label="Значения" v-if="variables[elementInfo.id].type === 'select'" v-model:value="variables[elementInfo.id].value" @update:value="value => updateVariableValue(elementInfo.id, value)" />
|
|
</div>
|
|
</ListStrate>
|
|
<Button v-if="elementInfo.element === 'VAR'" block>
|
|
Создать раздел
|
|
</Button>
|
|
</template>
|
|
<template #footer>
|
|
<Button block @click="saveTemplate">
|
|
Сохранить
|
|
</Button>
|
|
</template>
|
|
</Card>
|
|
</template>
|
|
</Sections>
|
|
</template>
|