Добавил теханализ

This commit is contained in:
2025-11-27 11:36:00 +03:00
parent efb99ea8d5
commit 810d3a8f7f
10 changed files with 859 additions and 16 deletions

View File

@@ -49,10 +49,13 @@
<i class="bi bi-trash"></i> Удалить
</button>
{% endif %}
<button type="button" class="btn btn-outline-primary btn-sm" title="Показать на карте"
<button type="button" class="btn btn-primary btn-sm" title="Показать на карте"
onclick="showSelectedOnMap()">
<i class="bi bi-map"></i> Карта
</button>
<a href="{% url 'mainapp:tech_analyze_entry' %}" class="btn btn-info btn-sm" title="Тех. анализ">
<i class="bi bi-clipboard-data"></i> Тех. анализ
</a>
</div>
<!-- Items per page select moved here -->

View File

@@ -80,7 +80,7 @@
</a>
{% endif %}
<a href="{% url 'mainapp:data_entry' %}" class="btn btn-info btn-sm" title="Ввод данных точек спутников">
<i class="bi bi-keyboard"></i> Ввод данных
Передача точек
</a>
<a href="{% url 'mainapp:load_excel_data' %}" class="btn btn-primary btn-sm" title="Загрузка данных из Excel">
<i class="bi bi-file-earmark-excel"></i> Excel
@@ -94,7 +94,7 @@
<i class="bi bi-trash"></i> Удалить
</button>
{% endif %}
<button type="button" class="btn btn-outline-primary btn-sm" title="Показать на карте"
<button type="button" class="btn btn-primary btn-sm" title="Показать на карте"
onclick="showSelectedOnMap()">
<i class="bi bi-map"></i> Карта
</button>

View File

@@ -0,0 +1,343 @@
{% extends "mainapp/base.html" %}
{% load static %}
{% block title %}Тех. анализ - Ввод данных{% endblock %}
{% block extra_css %}
<link href="https://unpkg.com/tabulator-tables@6.2.5/dist/css/tabulator_bootstrap5.min.css" rel="stylesheet">
<style>
.data-entry-container {
padding: 20px;
}
.form-section {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.table-section {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
#tech-analyze-table {
margin-top: 20px;
font-size: 12px;
}
#tech-analyze-table .tabulator-header {
font-size: 12px;
white-space: normal;
word-wrap: break-word;
}
#tech-analyze-table .tabulator-header .tabulator-col {
white-space: normal;
word-wrap: break-word;
height: auto;
min-height: 40px;
}
#tech-analyze-table .tabulator-header .tabulator-col-content {
white-space: normal;
word-wrap: break-word;
padding: 6px 4px;
}
#tech-analyze-table .tabulator-cell {
font-size: 12px;
padding: 6px 4px;
}
.btn-group-custom {
margin-top: 15px;
}
</style>
{% endblock %}
{% block content %}
<div class="data-entry-container">
<h2>Тех. анализ - Ввод данных</h2>
<div class="form-section">
<div class="row">
<div class="col-md-4 mb-3">
<label for="satellite-select" class="form-label">Спутник <span class="text-danger">*</span></label>
<select id="satellite-select" class="form-select">
<option value="">Выберите спутник</option>
{% for satellite in satellites %}
<option value="{{ satellite.id }}">{{ satellite.name }}</option>
{% endfor %}
</select>
</div>
<!-- <div class="col-md-8 mb-3">
<div class="alert alert-info mb-0">
<i class="bi bi-info-circle"></i>
<strong>Инструкция:</strong>
<ul class="mb-0 mt-2" style="font-size: 0.9em;">
<li><strong>Порядок столбцов в Excel:</strong> Имя, Частота МГц, Полоса МГц, Сим. скорость БОД, Модуляция, Стандарт, Примечание</li>
<li><strong>Поляризация извлекается автоматически</strong> из имени (например: "Сигнал 11500 МГц L" → "Левая")</li>
<li>Поддерживаемые буквы: L=Левая, R=Правая, H=Горизонтальная, V=Вертикальная</li>
<li>Скопируйте данные из Excel и вставьте в таблицу (Ctrl+V)</li>
<li>Используйте стрелки, Tab, Enter для навигации и редактирования</li>
</ul>
</div>
</div> -->
</div>
</div>
<div class="table-section">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5>Таблица данных <span id="row-count" class="badge bg-primary">0</span></h5>
</div>
<div class="btn-group-custom">
<button id="add-row" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Добавить строку
</button>
<button id="delete-selected" class="btn btn-warning ms-2">
<i class="bi bi-trash"></i> Удалить выбранные
</button>
<button id="save-data" class="btn btn-success ms-2">
<i class="bi bi-save"></i> Сохранить
</button>
<button id="clear-table" class="btn btn-danger ms-2">
<i class="bi bi-x-circle"></i> Очистить таблицу
</button>
</div>
</div>
<div id="tech-analyze-table"></div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="https://unpkg.com/tabulator-tables@6.2.5/dist/js/tabulator.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize Tabulator
const table = new Tabulator("#tech-analyze-table", {
layout: "fitDataStretch",
height: "500px",
placeholder: "Нет данных. Скопируйте данные из Excel и вставьте в таблицу (Ctrl+V).",
headerWordWrap: true,
clipboard: true,
clipboardPasteAction: "replace",
clipboardPasteParser: function(clipboard) {
// Парсим данные из буфера обмена
const rows = clipboard.split('\n').filter(row => row.trim() !== '');
const data = [];
// Функция для извлечения поляризации из имени
function extractPolarization(name) {
if (!name) return '';
// Маппинг букв на полные названия
const polarizationMap = {
'L': 'Левая',
'R': 'Правая',
'H': 'Горизонтальная',
'V': 'Вертикальная'
};
// Ищем паттерн "МГц X" где X - буква поляризации
const match = name.match(/МГц\s+([LRHV])/i);
if (match) {
const letter = match[1].toUpperCase();
return polarizationMap[letter] || '';
}
// Альтернативный паттерн: просто последняя буква L/R/H/V
const lastChar = name.trim().slice(-1).toUpperCase();
if (polarizationMap[lastChar]) {
return polarizationMap[lastChar];
}
return '';
}
rows.forEach(row => {
// Разделяем по табуляции (стандартный разделитель Excel)
const cells = row.split('\t');
const name = cells[0] || '';
const polarization = extractPolarization(name);
// Создаем объект с правильными полями (новый порядок без поляризации в начале)
const rowData = {
name: name,
frequency: cells[1] || '',
freq_range: cells[2] || '',
bod_velocity: cells[3] || '',
modulation: cells[4] || '',
standard: cells[5] || '',
note: cells[6] || '',
polarization: polarization // Автоматически извлеченная поляризация
};
data.push(rowData);
});
return data;
},
columns: [
{
formatter: "rowSelection",
titleFormatter: "rowSelection",
hozAlign: "center",
headerSort: false,
width: 40,
clipboard: false,
cellClick: function(e, cell) {
cell.getRow().toggleSelect();
}
},
{title: "Имя", field: "name", minWidth: 150, widthGrow: 2, editor: "input"},
{title: "Частота, МГц", field: "frequency", minWidth: 100, widthGrow: 1, editor: "input"},
{title: "Полоса, МГц", field: "freq_range", minWidth: 100, widthGrow: 1, editor: "input"},
{title: "Символьная скорость, БОД", field: "bod_velocity", minWidth: 150, widthGrow: 1.5, editor: "input"},
{title: "Вид модуляции", field: "modulation", minWidth: 120, widthGrow: 1.2, editor: "input"},
{title: "Стандарт", field: "standard", minWidth: 100, widthGrow: 1, editor: "input"},
{title: "Примечание", field: "note", minWidth: 150, widthGrow: 2, editor: "input"},
{title: "Поляризация", field: "polarization", minWidth: 100, widthGrow: 1, editor: "input"},
],
data: [],
});
// Update row count
function updateRowCount() {
const count = table.getDataCount();
document.getElementById('row-count').textContent = count;
}
// Listen to table events
table.on("rowAdded", updateRowCount);
table.on("dataChanged", updateRowCount);
table.on("rowDeleted", updateRowCount);
// Add row button
document.getElementById('add-row').addEventListener('click', function() {
table.addRow({
name: '',
frequency: '',
freq_range: '',
bod_velocity: '',
modulation: '',
standard: '',
note: '',
polarization: ''
});
});
// Delete selected rows
document.getElementById('delete-selected').addEventListener('click', function() {
const selectedRows = table.getSelectedRows();
if (selectedRows.length === 0) {
alert('Выберите строки для удаления');
return;
}
if (confirm(`Удалить ${selectedRows.length} строк(и)?`)) {
selectedRows.forEach(row => row.delete());
}
});
// Save data
document.getElementById('save-data').addEventListener('click', async function() {
const satelliteId = document.getElementById('satellite-select').value;
if (!satelliteId) {
alert('Пожалуйста, выберите спутник');
return;
}
const data = table.getData();
if (data.length === 0) {
alert('Нет данных для сохранения');
return;
}
// Validate that all rows have names
const emptyNames = data.filter(row => !row.name || row.name.trim() === '');
if (emptyNames.length > 0) {
alert('Все строки должны иметь имя');
return;
}
// Disable button while saving
this.disabled = true;
this.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Сохранение...';
try {
const response = await fetch('/tech-analyze/save/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
},
body: JSON.stringify({
satellite_id: satelliteId,
rows: data
})
});
const result = await response.json();
if (result.success) {
let message = `Успешно сохранено!\n`;
message += `Создано: ${result.created}\n`;
message += `Обновлено: ${result.updated}\n`;
message += `Всего: ${result.total}`;
if (result.errors && result.errors.length > 0) {
message += `\n\nОшибки:\n${result.errors.join('\n')}`;
}
alert(message);
// Clear table after successful save
if (!result.errors || result.errors.length === 0) {
table.clearData();
}
} else {
alert('Ошибка: ' + (result.error || 'Неизвестная ошибка'));
}
} catch (error) {
console.error('Error:', error);
alert('Произошла ошибка при сохранении данных');
} finally {
// Re-enable button
this.disabled = false;
this.innerHTML = '<i class="bi bi-save"></i> Сохранить';
}
});
// Clear table
document.getElementById('clear-table').addEventListener('click', function() {
if (confirm('Вы уверены, что хотите очистить таблицу?')) {
table.clearData();
updateRowCount();
}
});
// Helper function to get CSRF token
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
// Initialize row count
updateRowCount();
});
</script>
{% endblock %}