Вынес превью на отдельную страницу
This commit is contained in:
+12
-489
@@ -1,406 +1,21 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, h } from "vue";
|
||||
import izitoast from "@/shared/izitoast";
|
||||
import { useEventListener } from "@vueuse/core";
|
||||
import MyDialog from "@/components/MyDialog.vue";
|
||||
import MyDropMenu from "@/components/MyDropMenu.vue";
|
||||
import copyToclipboard from "@/shared/copyToclipboard";
|
||||
import ContextMenu from "@imengyu/vue3-context-menu";
|
||||
import CodeView from "@/components/CodeView.vue";
|
||||
import emergencyAndNavigate from '@/shablons/emergency_and_navigate.js'
|
||||
import color from '@/shablons/color.js'
|
||||
import qr from '@/shablons/qr.js'
|
||||
import Preview from "@/components/UI/svg/Preview.vue";
|
||||
import Save from "@/components/UI/svg/Save.vue";
|
||||
import Trash from "@/components/UI/svg/Trash.vue";
|
||||
import Back from "@/components/UI/svg/Back.vue";
|
||||
import { NTooltip } from 'naive-ui'
|
||||
import { ref } from "vue";
|
||||
import DroneParts from "@/components/DroneParts.vue";
|
||||
|
||||
const numbers = ref([]); // Инициализируем массив с пустыми строками
|
||||
const clickCount = ref(1); // Счетчик нажатий
|
||||
const listOfTrips = ref([]);
|
||||
const isModalVisible = ref(false);
|
||||
const isCodeViewVisible = ref(false);
|
||||
const finallyText = ref("");
|
||||
const dropMenu = ref(null)
|
||||
const showDroneParts = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
numbers.value.push({
|
||||
variable: [],
|
||||
x: i % 10,
|
||||
y: 9 - Math.floor(i / 10),
|
||||
index: i,
|
||||
isPlatform: false,
|
||||
isQR: false,
|
||||
BgImg: 'images/ArUco-marker.jpg'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function setNumber(item) {
|
||||
let valueOfMarker = item.variable[0];
|
||||
//если в клетке нет числа, то оно поставится
|
||||
if (valueOfMarker === "") {
|
||||
item.variable.push(String(clickCount.value));
|
||||
listOfTrips.value.push(item);
|
||||
clickCount.value++;
|
||||
} else {
|
||||
//если нажимаем на ранее отмеченную клетку, то выводится ошибка
|
||||
if (item.variable.at(-1) === clickCount.value - 1) {
|
||||
izitoast.error({
|
||||
title: "Ошибка",
|
||||
message: "Нельзя выбирать один и тот же aruco маркер 2 раза подряд",
|
||||
});
|
||||
return;
|
||||
}
|
||||
listOfTrips.value.push(item);
|
||||
item.variable.push(clickCount.value);
|
||||
clickCount.value++;
|
||||
}
|
||||
}
|
||||
|
||||
function openPreview(event) {
|
||||
event.currentTarget.blur()
|
||||
generateCode()
|
||||
if (!listOfTrips.value.length) {
|
||||
izitoast.error({
|
||||
title: 'Ошибка',
|
||||
message: 'Для предпросмотра нужно создать маршрут'
|
||||
})
|
||||
return
|
||||
}
|
||||
isCodeViewVisible.value = true;
|
||||
}
|
||||
|
||||
function saveResult(event) {
|
||||
isModalVisible.value = true;
|
||||
event.currentTarget.blur()
|
||||
generateCode()
|
||||
}
|
||||
|
||||
function generateCode() {
|
||||
finallyText.value = "";
|
||||
const mainCode = ref("");
|
||||
const colorDetectClass = ref("");
|
||||
const QRDetectClass = ref("");
|
||||
const colorDetectCode = ref("");
|
||||
const QRDetectCode = ref("");
|
||||
const isUsedPlatform = listOfTrips.value.some(item => item.isPlatform);
|
||||
const isUsedQR = listOfTrips.value.some(item => item.isQR);
|
||||
|
||||
if (!listOfTrips.value.length) {
|
||||
return
|
||||
}
|
||||
if (isUsedPlatform) {
|
||||
let textOfCode = color
|
||||
textOfCode = textOfCode.split('<<separator>>')
|
||||
colorDetectClass.value = textOfCode[0]
|
||||
colorDetectCode.value += ` ${textOfCode[1].trim()}\n`;
|
||||
}
|
||||
|
||||
if (isUsedQR) {
|
||||
let textOfCode = qr
|
||||
textOfCode = textOfCode.split('<<separator>>')
|
||||
QRDetectClass.value = textOfCode[0]
|
||||
QRDetectCode.value += ` ${textOfCode[1].trim()}\n`;
|
||||
}
|
||||
|
||||
for (const item of listOfTrips.value) {
|
||||
mainCode.value += ` navigate_wait(${item.x}, ${item.y})\n`;
|
||||
if (item.isPlatform) {
|
||||
mainCode.value += colorDetectCode.value + '\n'
|
||||
}
|
||||
if (item.isQR) {
|
||||
mainCode.value += QRDetectCode.value + '\n'
|
||||
}
|
||||
}
|
||||
let textOfCode = emergencyAndNavigate
|
||||
finallyText.value = textOfCode.replace("<<main>>", mainCode.value);
|
||||
if (isUsedPlatform) {
|
||||
finallyText.value = finallyText.value.replace("<<ColorDetect>>", colorDetectClass.value);
|
||||
} else {
|
||||
finallyText.value = finallyText.value.replace("<<ColorDetect>>", '');
|
||||
}
|
||||
if (isUsedQR) {
|
||||
finallyText.value = finallyText.value.replace("<<qr_function>>", QRDetectClass.value);
|
||||
} else {
|
||||
finallyText.value = finallyText.value.replace("<<qr_function>>", '');
|
||||
}
|
||||
}
|
||||
|
||||
function cancel(event) {
|
||||
event.currentTarget.blur()
|
||||
if (clickCount.value === 1) {
|
||||
return;
|
||||
}
|
||||
if (listOfTrips.value.at(-1).variable.length === 1) {
|
||||
listOfTrips.value.at(-1).BgImg = 'images/ArUco-marker.jpg'
|
||||
}
|
||||
listOfTrips.value.at(-1).variable.pop();
|
||||
listOfTrips.value.pop();
|
||||
clickCount.value--;
|
||||
}
|
||||
|
||||
function cleanResult(event) {
|
||||
event.currentTarget.blur()
|
||||
clickCount.value = 1;
|
||||
numbers.value.forEach(item => {
|
||||
item.isPlatform = false;
|
||||
item.isQR = false;
|
||||
item.variable = [];
|
||||
item.BgImg = 'images/ArUco-marker.jpg'
|
||||
})
|
||||
listOfTrips.value = []
|
||||
izitoast.info({
|
||||
title: 'Поле очищено',
|
||||
message: 'Поле очищено. Можно строить маршрут заново',
|
||||
})
|
||||
}
|
||||
|
||||
// Обрабатываем нажатие Ctrl + Z
|
||||
const handleKeydown = (event) => {
|
||||
if (event.ctrlKey && event.code === "KeyZ" && listOfTrips.value.length) {
|
||||
event.preventDefault();
|
||||
cancel();
|
||||
} else if (event.ctrlKey && event.code === "KeyS") {
|
||||
event.preventDefault();
|
||||
saveResult();
|
||||
} else if (event.ctrlKey && event.code === "KeyN") {
|
||||
event.preventDefault();
|
||||
dropMenu.value.open();
|
||||
} else if (event.code === "Delete") {
|
||||
event.preventDefault();
|
||||
cleanResult();
|
||||
}
|
||||
};
|
||||
useEventListener("keydown", handleKeydown);
|
||||
|
||||
function copyAndToast(text) {
|
||||
copyToclipboard(text);
|
||||
izitoast.success({
|
||||
title: 'Успешно',
|
||||
message: 'Код скопирован в буфер обмена'
|
||||
})
|
||||
isModalVisible.value = false
|
||||
}
|
||||
|
||||
function handleDownloadBtn() {
|
||||
// Создание файла
|
||||
const downloadUrl = ref('');
|
||||
const blob = new Blob([finallyText.value], { type: 'text/plain' });
|
||||
downloadUrl.value = URL.createObjectURL(blob);
|
||||
// Задержка для избежания ошибок в некоторых браузерах
|
||||
setTimeout(() => {
|
||||
// Загрузка
|
||||
const link = document.createElement('a');
|
||||
link.href = downloadUrl.value;
|
||||
link.download = 'itinerary.py';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}, 0);
|
||||
|
||||
izitoast.success({
|
||||
title: 'Успешно',
|
||||
message: 'Загрузка файла уже началась'
|
||||
})
|
||||
isModalVisible.value = false
|
||||
}
|
||||
|
||||
function createPlatform(item, color) {
|
||||
if (!item.variable.length) {
|
||||
item.variable.push(clickCount.value);
|
||||
listOfTrips.value.push(item)
|
||||
clickCount.value++
|
||||
}
|
||||
item.isPlatform = true;
|
||||
item.isQR = false;
|
||||
item.BgImg = `images/${color}-platform.jpg`
|
||||
}
|
||||
|
||||
function deletePlatform(item) {
|
||||
if (item.isPlatform) {
|
||||
item.BgImg = 'images/ArUco-marker.jpg'
|
||||
item.isPlatform = false
|
||||
}
|
||||
}
|
||||
|
||||
function createQR(item) {
|
||||
if (!item.variable.length) {
|
||||
item.variable.push(clickCount.value);
|
||||
listOfTrips.value.push(item)
|
||||
clickCount.value++
|
||||
}
|
||||
item.isPlatform = false;
|
||||
item.isQR = true;
|
||||
item.BgImg = `images/qr.jpg`
|
||||
}
|
||||
|
||||
function deleteQR(item) {
|
||||
if (item.isQR) {
|
||||
item.BgImg = 'images/ArUco-marker.jpg'
|
||||
item.isQR = false
|
||||
}
|
||||
}
|
||||
|
||||
function deletePointMap(item) {
|
||||
const index = listOfTrips.value.indexOf(item);
|
||||
if (index !== -1) {
|
||||
const deletedIndex = item.variable.at(-1)
|
||||
listOfTrips.value.forEach(square => {
|
||||
square.variable.map(itemVariable => {
|
||||
if (itemVariable === deletedIndex) {
|
||||
square.variable = []
|
||||
}
|
||||
if (itemVariable > deletedIndex) {
|
||||
square.variable[square.variable.indexOf(itemVariable)]--
|
||||
}
|
||||
})
|
||||
})
|
||||
listOfTrips.value.splice(index, 1);
|
||||
clickCount.value--
|
||||
item.isQR = false
|
||||
item.isPlatform = false
|
||||
item.BgImg = 'images/ArUco-marker.jpg'
|
||||
}
|
||||
}
|
||||
|
||||
function createIcon(src) {
|
||||
return h('img', {
|
||||
src: src,
|
||||
style: {
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onContextMenu(e, item) {
|
||||
ContextMenu.showContextMenu({
|
||||
theme: 'mac dark',
|
||||
x: e.x,
|
||||
y: e.y,
|
||||
items: [
|
||||
{
|
||||
label: "Платформа",
|
||||
icon: createIcon('icons/base_square.webp'),
|
||||
children: [
|
||||
{
|
||||
label: "Белая",
|
||||
onClick: () => createPlatform(item, 'white'),
|
||||
icon: createIcon('icons/white_square.webp'),
|
||||
},
|
||||
{
|
||||
label: "Синяя",
|
||||
onClick: () => createPlatform(item, 'blue'),
|
||||
icon: createIcon('icons/blue_square.webp'),
|
||||
},
|
||||
{
|
||||
label: "Красная",
|
||||
onClick: () => createPlatform(item, 'red'),
|
||||
icon: createIcon('icons/red_square.webp'),
|
||||
},
|
||||
{
|
||||
label: "Удалить",
|
||||
onClick: () => deletePlatform(item),
|
||||
icon: h(Trash),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "QR код",
|
||||
icon: createIcon('icons/qr_code.png'),
|
||||
children: [
|
||||
{
|
||||
label: "Добавить",
|
||||
onClick: () => createQR(item),
|
||||
icon: createIcon('icons/plus.webp')
|
||||
},
|
||||
{
|
||||
label: "Удалить",
|
||||
onClick: () => deleteQR(item),
|
||||
icon: h(Trash)
|
||||
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Удалить",
|
||||
icon: h(Trash),
|
||||
onClick: () => deletePointMap(item),
|
||||
},
|
||||
]
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
<button
|
||||
v-if="!showDroneParts"
|
||||
@click="showDroneParts = true"
|
||||
@click="showDroneParts = true"
|
||||
class="show-parts-btn"
|
||||
>
|
||||
<img src="/images/drone.png" alt="menu" class="btn-icon" />
|
||||
</button>
|
||||
|
||||
<DroneParts v-if="showDroneParts" @back="showDroneParts = false" />
|
||||
<div v-if="!showDroneParts && !isCodeViewVisible" class="wraper">
|
||||
<div class="grid">
|
||||
<div class="square" v-for="(item, index) in numbers" :data-key="index" :key="index" @click="setNumber(item)"
|
||||
@contextmenu.prevent="onContextMenu($event, item)" :style="{ backgroundImage: `url(${item.BgImg})` }">
|
||||
{{ numbers[index].variable.join(", ") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<Save @click="saveResult" />
|
||||
</template>
|
||||
Сохранить
|
||||
</n-tooltip>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<Trash @click="cleanResult" />
|
||||
</template>
|
||||
Удалить
|
||||
</n-tooltip>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<Back @click="cancel" />
|
||||
</template>
|
||||
Отменить
|
||||
</n-tooltip>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<Preview @click="openPreview" />
|
||||
</template>
|
||||
Превью
|
||||
</n-tooltip>
|
||||
</div>
|
||||
<MyDropMenu ref="dropMenu" />
|
||||
<MyDialog v-model:show="isModalVisible" class="dialogWindow" :is-success=Boolean(listOfTrips.length)>
|
||||
<template #header>
|
||||
<strong v-if="listOfTrips.length">Код готов!</strong>
|
||||
<strong v-else>Код не готов!</strong>
|
||||
</template>
|
||||
<main>
|
||||
<p v-if="listOfTrips.length">Скопируйте или скачайте ваш код</p>
|
||||
<p v-else>Выстройте маршрут перед сохранением кода.</p>
|
||||
</main>
|
||||
<template #footer>
|
||||
<div v-if="listOfTrips.length">
|
||||
<button @click="isCodeViewVisible = true">Предпросмотр</button>
|
||||
<button @click="handleDownloadBtn">Скачать код</button>
|
||||
<button @click="copyAndToast(finallyText)">Скопировать код</button>
|
||||
</div>
|
||||
</template>
|
||||
</MyDialog>
|
||||
</div>
|
||||
<CodeView v-model:show="isCodeViewVisible" :code="finallyText" :show="isCodeViewVisible" />
|
||||
<router-view v-if="!showDroneParts" />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@@ -417,20 +32,16 @@ body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:root {
|
||||
--side: 75px;
|
||||
}
|
||||
|
||||
.show-parts-btn {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 9999;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
padding: 0;
|
||||
background: rgba(12, 12, 12, 0.034);
|
||||
border: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
|
||||
transition: transform 0.2s;
|
||||
@@ -439,102 +50,14 @@ body {
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
object-fit: cover;
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.show-parts-btn:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.wraper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20px 0 80px 0;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
padding: 3px;
|
||||
grid-template-columns: repeat(10, var(--side));
|
||||
gap: 5px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.square {
|
||||
width: var(--side);
|
||||
height: var(--side);
|
||||
background-size: cover;
|
||||
border: 3px solid rgb(251, 251, 251);
|
||||
cursor: pointer;
|
||||
overflow-y: scroll;
|
||||
|
||||
background-blend-mode: multiply;
|
||||
background-color: transparent;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
color: white;
|
||||
text-shadow: -1px -1px 0 black,
|
||||
1px -1px 0 black,
|
||||
-1px 1px 0 black,
|
||||
1px 1px 0 black;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.square:hover {
|
||||
background-color: rgb(166, 162, 162);
|
||||
}
|
||||
|
||||
.square:active {
|
||||
background-color: rgb(164, 162, 162);
|
||||
}
|
||||
|
||||
.square::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.square[data-key="90"] {
|
||||
background-color: rgba(0, 2, 0, 0.638);
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
background-color: #2f2f2f;
|
||||
|
||||
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(5px);
|
||||
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
|
||||
border: 2px solid rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
.actions svg {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
transition: color 0.4s;
|
||||
}
|
||||
|
||||
.actions svg:hover {
|
||||
color: rgba(20, 104, 140, 0.89);
|
||||
}
|
||||
|
||||
.actions svg:active {
|
||||
transition: color 0s;
|
||||
color: #23aae3e2;
|
||||
}
|
||||
|
||||
path {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.actions button {
|
||||
padding: 20px 35px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
+11
-14
@@ -2,29 +2,26 @@
|
||||
import CustomButton from '@/components/UI/CustomButton.vue'
|
||||
import CodeBlock from "./CodeBlock.vue";
|
||||
import MovementButtons from "./MovementButtons.vue";
|
||||
const props = defineProps({
|
||||
|
||||
defineProps({
|
||||
code: {
|
||||
required: true,
|
||||
type: String
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
defineEmits(['back'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="code-view" v-if="show">
|
||||
<CustomButton
|
||||
@click.stop="$emit('update:show', false)">
|
||||
Вернуться к сохранению сгенерированного кода
|
||||
<div class="code-view">
|
||||
<CustomButton @click="$emit('back')">
|
||||
Вернуться к маршруту
|
||||
</CustomButton>
|
||||
<CodeBlock :code="code" />
|
||||
|
||||
<CustomButton
|
||||
@click.stop="$emit('update:show', false)">
|
||||
Вернуться к сохранению сгенерированного кода
|
||||
<CustomButton @click="$emit('back')">
|
||||
Вернуться к маршруту
|
||||
</CustomButton>
|
||||
<MovementButtons/>
|
||||
</div>
|
||||
@@ -36,7 +33,7 @@ const props = defineProps({
|
||||
overflow: auto;
|
||||
color: #eeeeee;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
const finallyText = ref('')
|
||||
|
||||
export function useFlightCode() {
|
||||
return { finallyText }
|
||||
}
|
||||
+6
-31
@@ -1,37 +1,12 @@
|
||||
/**
|
||||
* This file will automatically be loaded by vite and run in the "renderer" context.
|
||||
* To learn more about the differences between the "main" and the "renderer" context in
|
||||
* Electron, visit:
|
||||
*
|
||||
* https://electronjs.org/docs/tutorial/process-model
|
||||
*
|
||||
* By default, Node.js integration in this file is disabled. When enabling Node.js integration
|
||||
* in a renderer process, please be aware of potential security implications. You can read
|
||||
* more about security risks here:
|
||||
*
|
||||
* https://electronjs.org/docs/tutorial/security
|
||||
*
|
||||
* To enable Node.js integration in this file, open up `main.js` and enable the `nodeIntegration`
|
||||
* flag:
|
||||
*
|
||||
* ```
|
||||
* // Create the browser window.
|
||||
* mainWindow = new BrowserWindow({
|
||||
* width: 800,
|
||||
* height: 600,
|
||||
* webPreferences: {
|
||||
* nodeIntegration: true
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css'
|
||||
import Prism from 'prismjs'
|
||||
import 'prismjs/themes/prism-okaidia.css'
|
||||
|
||||
|
||||
import ContextMenu from '@imengyu/vue3-context-menu'
|
||||
createApp(App).use(ContextMenu).mount('#app');
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(router)
|
||||
app.use(ContextMenu)
|
||||
app.mount('#app')
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import PlannerView from '@/views/PlannerView.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'planner',
|
||||
component: PlannerView,
|
||||
},
|
||||
{
|
||||
path: '/preview',
|
||||
name: 'preview',
|
||||
component: () => import('@/views/PreviewView.vue'),
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,488 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, h } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import izitoast from "@/shared/izitoast";
|
||||
import { useEventListener } from "@vueuse/core";
|
||||
import MyDialog from "@/components/MyDialog.vue";
|
||||
import MyDropMenu from "@/components/MyDropMenu.vue";
|
||||
import copyToclipboard from "@/shared/copyToclipboard";
|
||||
import ContextMenu from "@imengyu/vue3-context-menu";
|
||||
import emergencyAndNavigate from '@/shablons/emergency_and_navigate.js'
|
||||
import color from '@/shablons/color.js'
|
||||
import qr from '@/shablons/qr.js'
|
||||
import Preview from "@/components/UI/svg/Preview.vue";
|
||||
import Save from "@/components/UI/svg/Save.vue";
|
||||
import Trash from "@/components/UI/svg/Trash.vue";
|
||||
import Back from "@/components/UI/svg/Back.vue";
|
||||
import { NTooltip } from 'naive-ui'
|
||||
import { useFlightCode } from '@/composables/useFlightCode'
|
||||
|
||||
const router = useRouter()
|
||||
const { finallyText } = useFlightCode()
|
||||
|
||||
const numbers = ref([]);
|
||||
const clickCount = ref(1);
|
||||
const listOfTrips = ref([]);
|
||||
const isModalVisible = ref(false);
|
||||
const dropMenu = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
numbers.value.push({
|
||||
variable: [],
|
||||
x: i % 10,
|
||||
y: 9 - Math.floor(i / 10),
|
||||
index: i,
|
||||
isPlatform: false,
|
||||
isQR: false,
|
||||
BgImg: 'images/ArUco-marker.jpg'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function setNumber(item) {
|
||||
let valueOfMarker = item.variable[0];
|
||||
if (valueOfMarker === "") {
|
||||
item.variable.push(String(clickCount.value));
|
||||
listOfTrips.value.push(item);
|
||||
clickCount.value++;
|
||||
} else {
|
||||
if (item.variable.at(-1) === clickCount.value - 1) {
|
||||
izitoast.error({
|
||||
title: "Ошибка",
|
||||
message: "Нельзя выбирать один и тот же aruco маркер 2 раза подряд",
|
||||
});
|
||||
return;
|
||||
}
|
||||
listOfTrips.value.push(item);
|
||||
item.variable.push(clickCount.value);
|
||||
clickCount.value++;
|
||||
}
|
||||
}
|
||||
|
||||
function generateCode() {
|
||||
finallyText.value = "";
|
||||
const mainCode = ref("");
|
||||
const colorDetectClass = ref("");
|
||||
const QRDetectClass = ref("");
|
||||
const colorDetectCode = ref("");
|
||||
const QRDetectCode = ref("");
|
||||
const isUsedPlatform = listOfTrips.value.some(item => item.isPlatform);
|
||||
const isUsedQR = listOfTrips.value.some(item => item.isQR);
|
||||
|
||||
if (!listOfTrips.value.length) {
|
||||
return
|
||||
}
|
||||
if (isUsedPlatform) {
|
||||
let textOfCode = color
|
||||
textOfCode = textOfCode.split('<<separator>>')
|
||||
colorDetectClass.value = textOfCode[0]
|
||||
colorDetectCode.value += ` ${textOfCode[1].trim()}\n`;
|
||||
}
|
||||
|
||||
if (isUsedQR) {
|
||||
let textOfCode = qr
|
||||
textOfCode = textOfCode.split('<<separator>>')
|
||||
QRDetectClass.value = textOfCode[0]
|
||||
QRDetectCode.value += ` ${textOfCode[1].trim()}\n`;
|
||||
}
|
||||
|
||||
for (const item of listOfTrips.value) {
|
||||
mainCode.value += ` navigate_wait(${item.x}, ${item.y})\n`;
|
||||
if (item.isPlatform) {
|
||||
mainCode.value += colorDetectCode.value + '\n'
|
||||
}
|
||||
if (item.isQR) {
|
||||
mainCode.value += QRDetectCode.value + '\n'
|
||||
}
|
||||
}
|
||||
let textOfCode = emergencyAndNavigate
|
||||
finallyText.value = textOfCode.replace("<<main>>", mainCode.value);
|
||||
if (isUsedPlatform) {
|
||||
finallyText.value = finallyText.value.replace("<<ColorDetect>>", colorDetectClass.value);
|
||||
} else {
|
||||
finallyText.value = finallyText.value.replace("<<ColorDetect>>", '');
|
||||
}
|
||||
if (isUsedQR) {
|
||||
finallyText.value = finallyText.value.replace("<<qr_function>>", QRDetectClass.value);
|
||||
} else {
|
||||
finallyText.value = finallyText.value.replace("<<qr_function>>", '');
|
||||
}
|
||||
}
|
||||
|
||||
function openPreview(event) {
|
||||
event.currentTarget.blur()
|
||||
generateCode()
|
||||
if (!listOfTrips.value.length) {
|
||||
izitoast.error({
|
||||
title: 'Ошибка',
|
||||
message: 'Для предпросмотра нужно создать маршрут'
|
||||
})
|
||||
return
|
||||
}
|
||||
router.push({ name: 'preview' })
|
||||
}
|
||||
|
||||
function openPreviewFromModal() {
|
||||
isModalVisible.value = false
|
||||
router.push({ name: 'preview' })
|
||||
}
|
||||
|
||||
function saveResult(event) {
|
||||
isModalVisible.value = true;
|
||||
event.currentTarget.blur()
|
||||
generateCode()
|
||||
}
|
||||
|
||||
function cancel(event) {
|
||||
event.currentTarget.blur()
|
||||
if (clickCount.value === 1) {
|
||||
return;
|
||||
}
|
||||
if (listOfTrips.value.at(-1).variable.length === 1) {
|
||||
listOfTrips.value.at(-1).BgImg = 'images/ArUco-marker.jpg'
|
||||
}
|
||||
listOfTrips.value.at(-1).variable.pop();
|
||||
listOfTrips.value.pop();
|
||||
clickCount.value--;
|
||||
}
|
||||
|
||||
function cleanResult(event) {
|
||||
event.currentTarget.blur()
|
||||
clickCount.value = 1;
|
||||
numbers.value.forEach(item => {
|
||||
item.isPlatform = false;
|
||||
item.isQR = false;
|
||||
item.variable = [];
|
||||
item.BgImg = 'images/ArUco-marker.jpg'
|
||||
})
|
||||
listOfTrips.value = []
|
||||
izitoast.info({
|
||||
title: 'Поле очищено',
|
||||
message: 'Поле очищено. Можно строить маршрут заново',
|
||||
})
|
||||
}
|
||||
|
||||
const handleKeydown = (event) => {
|
||||
if (event.ctrlKey && event.code === "KeyZ" && listOfTrips.value.length) {
|
||||
event.preventDefault();
|
||||
cancel();
|
||||
} else if (event.ctrlKey && event.code === "KeyS") {
|
||||
event.preventDefault();
|
||||
saveResult();
|
||||
} else if (event.ctrlKey && event.code === "KeyN") {
|
||||
event.preventDefault();
|
||||
dropMenu.value.open();
|
||||
} else if (event.code === "Delete") {
|
||||
event.preventDefault();
|
||||
cleanResult();
|
||||
}
|
||||
};
|
||||
useEventListener("keydown", handleKeydown);
|
||||
|
||||
function copyAndToast(text) {
|
||||
copyToclipboard(text);
|
||||
izitoast.success({
|
||||
title: 'Успешно',
|
||||
message: 'Код скопирован в буфер обмена'
|
||||
})
|
||||
isModalVisible.value = false
|
||||
}
|
||||
|
||||
function handleDownloadBtn() {
|
||||
const downloadUrl = ref('');
|
||||
const blob = new Blob([finallyText.value], { type: 'text/plain' });
|
||||
downloadUrl.value = URL.createObjectURL(blob);
|
||||
setTimeout(() => {
|
||||
const link = document.createElement('a');
|
||||
link.href = downloadUrl.value;
|
||||
link.download = 'itinerary.py';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}, 0);
|
||||
|
||||
izitoast.success({
|
||||
title: 'Успешно',
|
||||
message: 'Загрузка файла уже началась'
|
||||
})
|
||||
isModalVisible.value = false
|
||||
}
|
||||
|
||||
function createPlatform(item, color) {
|
||||
if (!item.variable.length) {
|
||||
item.variable.push(clickCount.value);
|
||||
listOfTrips.value.push(item)
|
||||
clickCount.value++
|
||||
}
|
||||
item.isPlatform = true;
|
||||
item.isQR = false;
|
||||
item.BgImg = `images/${color}-platform.jpg`
|
||||
}
|
||||
|
||||
function deletePlatform(item) {
|
||||
if (item.isPlatform) {
|
||||
item.BgImg = 'images/ArUco-marker.jpg'
|
||||
item.isPlatform = false
|
||||
}
|
||||
}
|
||||
|
||||
function createQR(item) {
|
||||
if (!item.variable.length) {
|
||||
item.variable.push(clickCount.value);
|
||||
listOfTrips.value.push(item)
|
||||
clickCount.value++
|
||||
}
|
||||
item.isPlatform = false;
|
||||
item.isQR = true;
|
||||
item.BgImg = `images/qr.jpg`
|
||||
}
|
||||
|
||||
function deleteQR(item) {
|
||||
if (item.isQR) {
|
||||
item.BgImg = 'images/ArUco-marker.jpg'
|
||||
item.isQR = false
|
||||
}
|
||||
}
|
||||
|
||||
function deletePointMap(item) {
|
||||
const index = listOfTrips.value.indexOf(item);
|
||||
if (index !== -1) {
|
||||
const deletedIndex = item.variable.at(-1)
|
||||
listOfTrips.value.forEach(square => {
|
||||
square.variable.map(itemVariable => {
|
||||
if (itemVariable === deletedIndex) {
|
||||
square.variable = []
|
||||
}
|
||||
if (itemVariable > deletedIndex) {
|
||||
square.variable[square.variable.indexOf(itemVariable)]--
|
||||
}
|
||||
})
|
||||
})
|
||||
listOfTrips.value.splice(index, 1);
|
||||
clickCount.value--
|
||||
item.isQR = false
|
||||
item.isPlatform = false
|
||||
item.BgImg = 'images/ArUco-marker.jpg'
|
||||
}
|
||||
}
|
||||
|
||||
function createIcon(src) {
|
||||
return h('img', {
|
||||
src: src,
|
||||
style: {
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onContextMenu(e, item) {
|
||||
ContextMenu.showContextMenu({
|
||||
theme: 'mac dark',
|
||||
x: e.x,
|
||||
y: e.y,
|
||||
items: [
|
||||
{
|
||||
label: "Платформа",
|
||||
icon: createIcon('icons/base_square.webp'),
|
||||
children: [
|
||||
{
|
||||
label: "Белая",
|
||||
onClick: () => createPlatform(item, 'white'),
|
||||
icon: createIcon('icons/white_square.webp'),
|
||||
},
|
||||
{
|
||||
label: "Синяя",
|
||||
onClick: () => createPlatform(item, 'blue'),
|
||||
icon: createIcon('icons/blue_square.webp'),
|
||||
},
|
||||
{
|
||||
label: "Красная",
|
||||
onClick: () => createPlatform(item, 'red'),
|
||||
icon: createIcon('icons/red_square.webp'),
|
||||
},
|
||||
{
|
||||
label: "Удалить",
|
||||
onClick: () => deletePlatform(item),
|
||||
icon: h(Trash),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "QR код",
|
||||
icon: createIcon('icons/qr_code.png'),
|
||||
children: [
|
||||
{
|
||||
label: "Добавить",
|
||||
onClick: () => createQR(item),
|
||||
icon: createIcon('icons/plus.webp')
|
||||
},
|
||||
{
|
||||
label: "Удалить",
|
||||
onClick: () => deleteQR(item),
|
||||
icon: h(Trash)
|
||||
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Удалить",
|
||||
icon: h(Trash),
|
||||
onClick: () => deletePointMap(item),
|
||||
},
|
||||
]
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="wraper">
|
||||
<div class="grid">
|
||||
<div class="square" v-for="(item, index) in numbers" :data-key="index" :key="index" @click="setNumber(item)"
|
||||
@contextmenu.prevent="onContextMenu($event, item)" :style="{ backgroundImage: `url(${item.BgImg})` }">
|
||||
{{ numbers[index].variable.join(", ") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<Save @click="saveResult" />
|
||||
</template>
|
||||
Сохранить
|
||||
</n-tooltip>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<Trash @click="cleanResult" />
|
||||
</template>
|
||||
Удалить
|
||||
</n-tooltip>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<Back @click="cancel" />
|
||||
</template>
|
||||
Отменить
|
||||
</n-tooltip>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<Preview @click="openPreview" />
|
||||
</template>
|
||||
Превью
|
||||
</n-tooltip>
|
||||
</div>
|
||||
<MyDropMenu ref="dropMenu" />
|
||||
<MyDialog v-model:show="isModalVisible" class="dialogWindow" :is-success=Boolean(listOfTrips.length)>
|
||||
<template #header>
|
||||
<strong v-if="listOfTrips.length">Код готов!</strong>
|
||||
<strong v-else>Код не готов!</strong>
|
||||
</template>
|
||||
<main>
|
||||
<p v-if="listOfTrips.length">Скопируйте или скачайте ваш код</p>
|
||||
<p v-else>Выстройте маршрут перед сохранением кода.</p>
|
||||
</main>
|
||||
<template #footer>
|
||||
<div v-if="listOfTrips.length">
|
||||
<button @click="openPreviewFromModal">Предпросмотр</button>
|
||||
<button @click="handleDownloadBtn">Скачать код</button>
|
||||
<button @click="copyAndToast(finallyText)">Скопировать код</button>
|
||||
</div>
|
||||
</template>
|
||||
</MyDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--side: 75px;
|
||||
}
|
||||
|
||||
.wraper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20px 0 80px 0;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
padding: 3px;
|
||||
grid-template-columns: repeat(10, var(--side));
|
||||
gap: 5px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.square {
|
||||
width: var(--side);
|
||||
height: var(--side);
|
||||
background-size: cover;
|
||||
border: 3px solid rgb(251, 251, 251);
|
||||
cursor: pointer;
|
||||
overflow-y: scroll;
|
||||
|
||||
background-blend-mode: multiply;
|
||||
background-color: transparent;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
color: white;
|
||||
text-shadow: -1px -1px 0 black,
|
||||
1px -1px 0 black,
|
||||
-1px 1px 0 black,
|
||||
1px 1px 0 black;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.square:hover {
|
||||
background-color: rgb(166, 162, 162);
|
||||
}
|
||||
|
||||
.square:active {
|
||||
background-color: rgb(164, 162, 162);
|
||||
}
|
||||
|
||||
.square::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.square[data-key="90"] {
|
||||
background-color: rgba(0, 2, 0, 0.638);
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
background-color: #2f2f2f;
|
||||
|
||||
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(5px);
|
||||
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
|
||||
border: 2px solid rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
.actions svg {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
transition: color 0.4s;
|
||||
}
|
||||
|
||||
.actions svg:hover {
|
||||
color: rgba(20, 104, 140, 0.89);
|
||||
}
|
||||
|
||||
.actions svg:active {
|
||||
transition: color 0s;
|
||||
color: #23aae3e2;
|
||||
}
|
||||
|
||||
path {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.actions button {
|
||||
padding: 20px 35px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,23 @@
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import CodeView from '@/components/CodeView.vue'
|
||||
import { useFlightCode } from '@/composables/useFlightCode'
|
||||
|
||||
const router = useRouter()
|
||||
const { finallyText } = useFlightCode()
|
||||
|
||||
onMounted(() => {
|
||||
if (!finallyText.value.trim()) {
|
||||
router.replace({ name: 'planner' })
|
||||
}
|
||||
})
|
||||
|
||||
function goBack() {
|
||||
router.push({ name: 'planner' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CodeView v-if="finallyText" :code="finallyText" @back="goBack" />
|
||||
</template>
|
||||
Reference in New Issue
Block a user