improved reverse search
This commit is contained in:
@@ -554,321 +554,245 @@ Vue.component('radius', {
|
||||
|
||||
Vue.component('radius-ont-finder', {
|
||||
template: `
|
||||
<div class="container mt-4">
|
||||
<div v-if="step === 1" class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Schritt 1: Excel (XLSX) Upload</h4>
|
||||
<p>Bitte laden Sie eine XLSX-Datei hoch, die mindestens eine Spalte mit dem Header 'Serial' für die ONT-Seriennummern enthält. Erwartete Spalten (optional, aber zur Anzeige empfohlen): Nummer, Serial, ONT-Type, Meter, Pegel.</p>
|
||||
<input type="file" class="form-control" @change="handleFileUpload" accept=".xlsx">
|
||||
<div v-if="uploadError" class="alert alert-danger mt-3">{{ uploadError }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="step === 2" class="card mt-4">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Schritt 2: Ergebnisse</h4>
|
||||
<button class="btn btn-primary mb-3" @click="downloadResults">
|
||||
<i class="fas fa-download mr-2"></i>Ergebnisse herunterladen
|
||||
</button>
|
||||
<button class="btn btn-secondary mb-3 ml-2" @click="resetComponent">
|
||||
<i class="fas fa-redo mr-2"></i>Neue Datei hochladen
|
||||
</button>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered table-sm">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th v-for="header in originalHeaders" :key="'orig-' + header">{{ header }}</th>
|
||||
<th>Username</th>
|
||||
<th>Kundennummer</th>
|
||||
<th>Kundenname</th>
|
||||
<th>Info</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, index) in processedData" :key="index">
|
||||
<td v-for="header in originalHeaders" :key="'data-' + header + '-' + index">{{ row[header] }}</td>
|
||||
<td>{{ row.fetched_username }}</td>
|
||||
<td>{{ row.fetched_customerNumber }}</td>
|
||||
<td>{{ row.fetched_customerName }}</td>
|
||||
<td>{{ row.fetched_info }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="loading-overlay mt-4">
|
||||
<div class="d-flex justify-content-center align-items-center flex-column">
|
||||
<div class="spinner-border text-primary mb-3" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
<div class="progress w-75">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated"
|
||||
role="progressbar"
|
||||
:style="{ width: progress + '%' }"
|
||||
:aria-valuenow="progress"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
{{ Math.round(progress) }}%
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-2">Verarbeite Zeile {{ currentRow + 1 }} von {{ totalRows }}</div>
|
||||
<div v-if="currentSerial" class="text-center text-muted small mt-1">Aktuelle ONT SN: {{ currentSerial }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container mt-4">
|
||||
<div v-if="step === 1" class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Schritt 1: Excel (XLSX) Upload</h4>
|
||||
<p>Bitte laden Sie eine XLSX-Datei hoch, die mindestens eine Spalte mit dem Header 'Serial' für die ONT-Seriennummern enthält. Optional kann eine 'MAC' Spalte für eine alternative Suche verwendet werden.</p>
|
||||
<input type="file" class="form-control" @change="handleFileUpload" accept=".xlsx">
|
||||
<div v-if="uploadError" class="alert alert-danger mt-3">{{ uploadError }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="step === 2" class="card mt-4">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Schritt 2: Ergebnisse</h4>
|
||||
<button class="btn btn-primary mb-3" @click="downloadResults">
|
||||
<i class="fas fa-download mr-2"></i>Ergebnisse herunterladen
|
||||
</button>
|
||||
<button class="btn btn-secondary mb-3 ml-2" @click="resetComponent">
|
||||
<i class="fas fa-redo mr-2"></i>Neue Datei hochladen
|
||||
</button>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-bordered table-sm">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th v-for="header in originalHeaders" :key="'orig-' + header">{{ header }}</th>
|
||||
<th>Username</th>
|
||||
<th>Kundennummer</th>
|
||||
<th>Kundenname</th>
|
||||
<th>Info</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, index) in processedData" :key="index">
|
||||
<td v-for="header in originalHeaders" :key="'data-' + header + '-' + index">{{ row[header] }}</td>
|
||||
<td>{{ row.fetched_username }}</td>
|
||||
<td>{{ row.fetched_customerNumber }}</td>
|
||||
<td>{{ row.fetched_customerName }}</td>
|
||||
<td>{{ row.fetched_info }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="loading-overlay mt-4">
|
||||
<div class="d-flex justify-content-center align-items-center flex-column">
|
||||
<div class="spinner-border text-primary mb-3" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
<div class="progress w-75">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated"
|
||||
role="progressbar" :style="{ width: progress + '%' }"
|
||||
:aria-valuenow="progress" aria-valuemin="0" aria-valuemax="100">
|
||||
{{ Math.round(progress) }}%
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-2">Verarbeite Zeile {{ currentRow + 1 }} von {{ totalRows }}</div>
|
||||
<div v-if="currentSerial" class="text-center text-muted small mt-1">Aktuelle Suche: {{ currentSerial }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
|
||||
data() {
|
||||
return {
|
||||
step: 1, // 1: Upload, 2: Results
|
||||
parsedData: [], // Raw data from XLSX
|
||||
processedData: [], // Data after API calls
|
||||
originalHeaders: [], // Headers from the uploaded XLSX
|
||||
step: 1,
|
||||
parsedData: [],
|
||||
processedData: [],
|
||||
originalHeaders: [],
|
||||
loading: false,
|
||||
progress: 0,
|
||||
currentRow: 0,
|
||||
totalRows: 0,
|
||||
currentSerial: '', // Track the serial being processed for display
|
||||
uploadError: null, // To display errors during upload/parsing
|
||||
// Define the key column name expected in the XLSX for the ONT Serial Number
|
||||
serialColumnName: 'Serial', // IMPORTANT: Adjust if the header name in the XLSX is different
|
||||
// Define keys for the fetched data to avoid conflicts with original headers
|
||||
currentSerial: '',
|
||||
uploadError: null,
|
||||
serialColumnName: 'Serial',
|
||||
macColumnName: 'MAC',
|
||||
fetchedKeys: {
|
||||
username: 'fetched_username',
|
||||
customerNumber: 'fetched_customerNumber',
|
||||
customerName: 'fetched_customerName',
|
||||
info: 'fetched_info'
|
||||
},
|
||||
// Base path for the API - ensure TT_CONFIG is available globally
|
||||
apiBasePath: window.TT_CONFIG ? window.TT_CONFIG['BASE_PATH'] : '/default/path/to/api' // Provide a fallback or handle error if TT_CONFIG is missing
|
||||
apiBasePath: window.TT_CONFIG?.BASE_PATH
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Resets the component state to allow a new file upload.
|
||||
*/
|
||||
resetComponent() {
|
||||
this.step = 1;
|
||||
this.parsedData = [];
|
||||
this.processedData = [];
|
||||
this.originalHeaders = [];
|
||||
this.loading = false;
|
||||
this.progress = 0;
|
||||
this.currentRow = 0;
|
||||
this.totalRows = 0;
|
||||
this.currentSerial = '';
|
||||
this.uploadError = null;
|
||||
// Reset the file input visually (optional, requires ref)
|
||||
Object.assign(this.$data, this.$options.data.call(this));
|
||||
const input = this.$el.querySelector('input[type="file"]');
|
||||
if (input) {
|
||||
input.value = '';
|
||||
}
|
||||
if (input) input.value = '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the file input change event.
|
||||
* Loads the XLSX library if needed, reads the file,
|
||||
* parses the data, validates the 'Serial' column,
|
||||
* and triggers processing.
|
||||
* @param {Event} event - The file input change event.
|
||||
*/
|
||||
async handleFileUpload(event) {
|
||||
const file = event.target.files[0];
|
||||
this.uploadError = null; // Clear previous errors
|
||||
this.uploadError = null;
|
||||
if (!file) return;
|
||||
|
||||
this.loading = true; // Show loading indicator early
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
// Load XLSX library dynamically if not already loaded
|
||||
await this.loadXLSX();
|
||||
const data = await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => resolve(new Uint8Array(e.target.result));
|
||||
reader.onerror = () => reject(new Error("Fehler beim Lesen der Datei."));
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const data = new Uint8Array(e.target.result);
|
||||
const workbook = XLSX.read(data, { type: "array" });
|
||||
const worksheetName = workbook.SheetNames[0];
|
||||
const worksheet = workbook.Sheets[worksheetName];
|
||||
const workbook = XLSX.read(data, { type: 'array' });
|
||||
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||
this.parsedData = XLSX.utils.sheet_to_json(worksheet, { defval: "" });
|
||||
|
||||
// Parse the sheet into an array of objects, automatically detecting headers
|
||||
this.parsedData = XLSX.utils.sheet_to_json(worksheet, { defval: "" }); // Use defval to handle empty cells
|
||||
if (!this.parsedData.length) {
|
||||
throw new Error("Die hochgeladene Datei ist leer oder konnte nicht gelesen werden.");
|
||||
}
|
||||
|
||||
if (this.parsedData.length === 0) {
|
||||
this.uploadError = "Die hochgeladene Datei ist leer oder konnte nicht gelesen werden.";
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get headers from the first row of parsed data
|
||||
this.originalHeaders = Object.keys(this.parsedData[0]);
|
||||
|
||||
// --- Validation: Check if the required 'Serial' column exists ---
|
||||
if (!this.originalHeaders.includes(this.serialColumnName)) {
|
||||
this.uploadError = `Fehler: Die erforderliche Spalte '${this.serialColumnName}' wurde in der hochgeladenen Datei nicht gefunden. Gefundene Spalten: ${this.originalHeaders.join(', ')}`;
|
||||
this.loading = false;
|
||||
this.parsedData = []; // Clear data if invalid
|
||||
this.originalHeaders = [];
|
||||
// Keep step at 1 to show the error
|
||||
return;
|
||||
}
|
||||
// --- End Validation ---
|
||||
|
||||
// If validation passes, proceed to processing
|
||||
this.startProcessing();
|
||||
|
||||
} catch (parseError) {
|
||||
console.error("Error parsing XLSX file:", parseError);
|
||||
this.uploadError = `Fehler beim Verarbeiten der XLSX-Datei: ${parseError.message}`;
|
||||
this.loading = false;
|
||||
this.step = 1; // Stay on upload step
|
||||
}
|
||||
};
|
||||
reader.onerror = (err) => {
|
||||
console.error("FileReader error:", err);
|
||||
this.uploadError = "Fehler beim Lesen der Datei.";
|
||||
this.loading = false;
|
||||
this.step = 1;
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
|
||||
} catch (libLoadError) {
|
||||
console.error("Error loading XLSX library:", libLoadError);
|
||||
this.uploadError = "Fehler beim Laden der erforderlichen Bibliothek (xlsx). Bitte versuchen Sie es erneut.";
|
||||
this.originalHeaders = Object.keys(this.parsedData[0]);
|
||||
if (!this.originalHeaders.includes(this.serialColumnName)) {
|
||||
throw new Error(`Erforderliche Spalte '${this.serialColumnName}' nicht gefunden.`);
|
||||
}
|
||||
this.startProcessing();
|
||||
} catch (error) {
|
||||
console.error("File processing error:", error);
|
||||
this.uploadError = error.message;
|
||||
this.loading = false;
|
||||
this.step = 1;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Dynamically loads the SheetJS (XLSX) library if it's not already available.
|
||||
*/
|
||||
async loadXLSX() {
|
||||
if (!window.XLSX) {
|
||||
console.log("Loading XLSX library...");
|
||||
await new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.0/xlsx.full.min.js'; // Consider using a newer version if available
|
||||
script.async = true;
|
||||
script.onload = () => {
|
||||
console.log("XLSX library loaded.");
|
||||
resolve();
|
||||
};
|
||||
script.onerror = (err) => {
|
||||
console.error("Failed to load XLSX script:", err);
|
||||
reject(new Error("Could not load XLSX library"));
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
if (window.XLSX) return;
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.0/xlsx.full.min.js';
|
||||
script.async = true;
|
||||
script.onload = resolve;
|
||||
script.onerror = () => reject(new Error("Could not load XLSX library."));
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Processes the parsed data row by row, fetching details from the Radius API.
|
||||
*/
|
||||
async startProcessing() {
|
||||
this.loading = true;
|
||||
this.totalRows = this.parsedData.length;
|
||||
this.processedData = []; // Clear previous results
|
||||
this.processedData = [];
|
||||
this.progress = 0;
|
||||
this.currentRow = 0;
|
||||
this.currentSerial = '';
|
||||
|
||||
const apiUrlBase = `${this.apiBasePath}/Radius/proxyUnsecureHTTPRequestToRadius?ont_sn=`;
|
||||
const notFoundMessage = 'N/A - Keinen Benutzer mit dieser ONT SN gefunden';
|
||||
const snApiUrlBase = `${this.apiBasePath}/Radius/proxyUnsecureHTTPRequestToRadius?ont_sn=`;
|
||||
const macApiUrlBase = `${this.apiBasePath}/Radius/proxyUnsecureHTTPRequestToRadius?username=`;
|
||||
const sesApiUrlBase = `${this.apiBasePath}/Radius/proxyUnsecureHTTPRequestToRadius?action2=find_by_current_session&mac=`;
|
||||
|
||||
for (let i = 0; i < this.parsedData.length; i++) {
|
||||
const setRowStatus = (row, msg, data = {}) => {
|
||||
const defaultData = { username: `N/A - ${msg}`, customerNumber: 'N/A', customerName: 'N/A', info: 'N/A' };
|
||||
Object.keys(this.fetchedKeys).forEach(key => row[this.fetchedKeys[key]] = data[key] || defaultData[key]);
|
||||
};
|
||||
|
||||
for (const [i, row] of this.parsedData.entries()) {
|
||||
this.currentRow = i;
|
||||
const row = { ...this.parsedData[i] }; // Create a copy to avoid modifying original parsed data
|
||||
const serialNumber = row[this.serialColumnName]?.trim(); // Get serial number, trim whitespace
|
||||
|
||||
this.currentSerial = serialNumber || 'Leer'; // Update display
|
||||
this.progress = ((i + 1) / this.totalRows) * 100;
|
||||
|
||||
// Initialize fetched data fields
|
||||
row[this.fetchedKeys.username] = '';
|
||||
row[this.fetchedKeys.customerNumber] = '';
|
||||
row[this.fetchedKeys.customerName] = '';
|
||||
row[this.fetchedKeys.info] = '';
|
||||
const newRow = { ...row };
|
||||
const serialNumber = row[this.serialColumnName]?.trim();
|
||||
this.currentSerial = `SN: ${serialNumber || 'Leer'}`;
|
||||
|
||||
if (!serialNumber) {
|
||||
// Handle rows with empty serial numbers
|
||||
row[this.fetchedKeys.username] = 'N/A - Leere Seriennummer';
|
||||
row[this.fetchedKeys.customerNumber] = 'N/A';
|
||||
row[this.fetchedKeys.customerName] = 'N/A';
|
||||
row[this.fetchedKeys.info] = 'N/A';
|
||||
this.processedData.push(row);
|
||||
await this.sleep(10); // Small delay for UI update even for empty rows
|
||||
continue; // Move to the next row
|
||||
setRowStatus(newRow, 'Leere Seriennummer');
|
||||
this.processedData.push(newRow);
|
||||
this.progress = ((i + 1) / this.totalRows) * 100;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(apiUrlBase + encodeURIComponent(serialNumber));
|
||||
if (!response.ok) {
|
||||
// Handle HTTP errors (e.g., 404, 500)
|
||||
console.error(`API Error for SN ${serialNumber}: ${response.status} ${response.statusText}`);
|
||||
row[this.fetchedKeys.username] = `N/A - API Fehler (${response.status})`;
|
||||
row[this.fetchedKeys.customerNumber] = 'N/A';
|
||||
row[this.fetchedKeys.customerName] = 'N/A';
|
||||
row[this.fetchedKeys.info] = 'N/A';
|
||||
} else {
|
||||
const data = await response.json();
|
||||
let found = false;
|
||||
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
// Assuming the first result is the relevant one if multiple are returned
|
||||
const userData = data[0];
|
||||
row[this.fetchedKeys.username] = userData.username || 'N/A';
|
||||
row[this.fetchedKeys.customerNumber] = userData.customerNumber || 'N/A';
|
||||
row[this.fetchedKeys.customerName] = userData.customerName || 'N/A';
|
||||
row[this.fetchedKeys.info] = userData.info || 'N/A';
|
||||
} else {
|
||||
// Handle case where API returns success but an empty array or unexpected format
|
||||
row[this.fetchedKeys.username] = notFoundMessage;
|
||||
row[this.fetchedKeys.customerNumber] = 'N/A';
|
||||
row[this.fetchedKeys.customerName] = 'N/A';
|
||||
row[this.fetchedKeys.info] = 'N/A';
|
||||
try {
|
||||
const snResponse = await fetch(snApiUrlBase + encodeURIComponent(serialNumber));
|
||||
if (snResponse.ok) {
|
||||
const snData = await snResponse.json();
|
||||
if (snData?.length > 0) {
|
||||
setRowStatus(newRow, '', snData[0]);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error fetching data for SN ${serialNumber}:`, error);
|
||||
row[this.fetchedKeys.username] = 'N/A - Fehler bei API-Abfrage';
|
||||
row[this.fetchedKeys.customerNumber] = 'N/A';
|
||||
row[this.fetchedKeys.customerName] = 'N/A';
|
||||
row[this.fetchedKeys.info] = 'N/A';
|
||||
console.error(`Fetch error for SN ${serialNumber}:`, error);
|
||||
}
|
||||
|
||||
this.processedData.push(row);
|
||||
if (!found && this.originalHeaders.includes(this.macColumnName)) {
|
||||
const macAddress = row[this.macColumnName]?.trim();
|
||||
this.currentSerial = `MAC: ${macAddress || 'Leer'}`;
|
||||
if (macAddress && macAddress.length === 12) {
|
||||
const formattedMac = macAddress.toUpperCase().match(/.{1,2}/g).join(':');
|
||||
try {
|
||||
const sesResponse = await fetch(`${sesApiUrlBase}${encodeURIComponent(formattedMac)}`);
|
||||
if (sesResponse.ok) {
|
||||
const sesData = await sesResponse.json();
|
||||
if (sesData?.length === 0) continue;
|
||||
|
||||
// Optional small delay to prevent UI freeze on large files and allow progress update
|
||||
if (i % 20 === 0) { // Update UI roughly every 20 rows
|
||||
await this.sleep(20);
|
||||
const username = sesData[0];
|
||||
|
||||
const macResponse = await fetch(`${macApiUrlBase}${encodeURIComponent(username)}&info=&custnum=`);
|
||||
if (macResponse.ok) {
|
||||
const macData = await macResponse.json();
|
||||
if (macData?.length > 0) {
|
||||
setRowStatus(newRow, '', macData[0]);
|
||||
console.log("found via MAC:", formattedMac, macData[0]);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Fetch error for MAC ${formattedMac}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
setRowStatus(newRow, 'Keinen Benutzer gefunden');
|
||||
}
|
||||
|
||||
this.processedData.push(newRow);
|
||||
this.progress = ((i + 1) / this.totalRows) * 100;
|
||||
if ((i + 1) % 20 === 0) await this.sleep(20);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
this.step = 2; // Move to results view
|
||||
this.currentSerial = ''; // Clear serial display
|
||||
this.step = 2;
|
||||
this.currentSerial = '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates and triggers the download of an XLSX file containing the processed results.
|
||||
*/
|
||||
downloadResults() {
|
||||
if (!this.processedData.length) return;
|
||||
|
||||
try {
|
||||
// Prepare data for export: Select and order columns
|
||||
const dataToExport = this.processedData.map(row => {
|
||||
const exportRow = {};
|
||||
// Include original columns first
|
||||
this.originalHeaders.forEach(header => {
|
||||
exportRow[header] = row[header];
|
||||
});
|
||||
// Add fetched data with user-friendly headers
|
||||
this.originalHeaders.forEach(header => { exportRow[header] = row[header]; });
|
||||
exportRow['Username'] = row[this.fetchedKeys.username];
|
||||
exportRow['Kundennummer'] = row[this.fetchedKeys.customerNumber];
|
||||
exportRow['Kundenname'] = row[this.fetchedKeys.customerName];
|
||||
@@ -876,47 +800,25 @@ Vue.component('radius-ont-finder', {
|
||||
return exportRow;
|
||||
});
|
||||
|
||||
|
||||
const ws = XLSX.utils.json_to_sheet(dataToExport);
|
||||
const wb = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(wb, ws, "ONT_Finder_Results"); // Sheet name
|
||||
|
||||
// Generate filename (e.g., results_YYYYMMDD_HHMMSS.xlsx)
|
||||
XLSX.utils.book_append_sheet(wb, ws, "ONT_Finder_Results");
|
||||
const timestamp = new Date().toISOString().replace(/[-:.]/g, "").slice(0, 14);
|
||||
const filename = `ont_finder_results_${timestamp}.xlsx`;
|
||||
|
||||
XLSX.writeFile(wb, filename);
|
||||
XLSX.writeFile(wb, `ont_finder_results_${timestamp}.xlsx`);
|
||||
} catch (error) {
|
||||
console.error("Error generating results file:", error);
|
||||
alert("Fehler beim Erstellen der Excel-Datei für den Download."); // Simple alert for user feedback
|
||||
alert("Fehler beim Erstellen der Excel-Datei für den Download.");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility function to pause execution for a specified duration.
|
||||
* Useful for allowing UI updates during long loops.
|
||||
* @param {number} ms - Milliseconds to sleep.
|
||||
*/
|
||||
sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Lifecycle hook called when the component is mounted.
|
||||
* Checks if the global TT_CONFIG is available.
|
||||
*/
|
||||
mounted() {
|
||||
if (!window.TT_CONFIG || !window.TT_CONFIG['BASE_PATH']) {
|
||||
console.warn("Global TT_CONFIG or TT_CONFIG['BASE_PATH'] not found. API calls may fail. Using fallback path:", this.apiBasePath);
|
||||
// Optionally display a warning to the user
|
||||
// this.uploadError = "Konfiguration für API-Pfad nicht gefunden. Funktionalität möglicherweise beeinträchtigt.";
|
||||
} else {
|
||||
// Update apiBasePath if TT_CONFIG was found after initial data setup (less likely but safe)
|
||||
this.apiBasePath = window.TT_CONFIG['BASE_PATH'];
|
||||
if (!window.TT_CONFIG?.BASE_PATH) {
|
||||
console.warn(`Global TT_CONFIG.BASE_PATH not found. API calls will use fallback path: ${this.apiBasePath}`);
|
||||
}
|
||||
// Ensure XLSX is loaded once the component is ready, in case it's needed immediately
|
||||
// Although handleFileUpload loads it, pre-loading might be slightly smoother if needed elsewhere later
|
||||
// this.loadXLSX().catch(err => console.error("Pre-loading XLSX library failed:", err));
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user