v3: загрузка файлов (скрепка)
This commit is contained in:
parent
fb0ccc1573
commit
9b31f446bd
137
index.html
137
index.html
@ -24,6 +24,7 @@
|
|||||||
.btn-primary:hover { background: #0056b3; }
|
.btn-primary:hover { background: #0056b3; }
|
||||||
.btn-success { background: #34C759; color: white; }
|
.btn-success { background: #34C759; color: white; }
|
||||||
.btn-danger { background: #FF3B30; color: white; padding: 8px 16px; font-size: 13px; }
|
.btn-danger { background: #FF3B30; color: white; padding: 8px 16px; font-size: 13px; }
|
||||||
|
.btn-secondary { background: #8e8e93; color: white; }
|
||||||
table { width: 100%; border-collapse: collapse; margin-top: 16px; font-size: 14px; }
|
table { width: 100%; border-collapse: collapse; margin-top: 16px; font-size: 14px; }
|
||||||
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #e5e5ea; }
|
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #e5e5ea; }
|
||||||
th { background: #f5f5f7; font-weight: 600; font-size: 13px; color: #6e6e73; text-transform: uppercase; }
|
th { background: #f5f5f7; font-weight: 600; font-size: 13px; color: #6e6e73; text-transform: uppercase; }
|
||||||
@ -32,7 +33,6 @@
|
|||||||
.checkbox-group input[type="checkbox"] { width: 20px; height: 20px; margin-top: 2px; }
|
.checkbox-group input[type="checkbox"] { width: 20px; height: 20px; margin-top: 2px; }
|
||||||
.checkbox-group label { font-size: 14px; line-height: 1.4; cursor: pointer; }
|
.checkbox-group label { font-size: 14px; line-height: 1.4; cursor: pointer; }
|
||||||
.price-highlight { background: #d4edda; font-weight: 600; }
|
.price-highlight { background: #d4edda; font-weight: 600; }
|
||||||
.price-low { background: #fff3cd; }
|
|
||||||
.empty { text-align: center; padding: 40px; color: #86868b; }
|
.empty { text-align: center; padding: 40px; color: #86868b; }
|
||||||
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 16px; margin-bottom: 24px; }
|
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 16px; margin-bottom: 24px; }
|
||||||
.stat { background: white; padding: 20px; border-radius: 12px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
|
.stat { background: white; padding: 20px; border-radius: 12px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
|
||||||
@ -41,8 +41,17 @@
|
|||||||
.conclusion-box { background: #f0f8ff; border: 2px solid #007AFF; border-radius: 12px; padding: 20px; margin-top: 20px; }
|
.conclusion-box { background: #f0f8ff; border: 2px solid #007AFF; border-radius: 12px; padding: 20px; margin-top: 20px; }
|
||||||
.signature-block { margin-top: 40px; display: grid; grid-template-columns: 1fr 1fr; gap: 40px; }
|
.signature-block { margin-top: 40px; display: grid; grid-template-columns: 1fr 1fr; gap: 40px; }
|
||||||
.signature-line { border-top: 1px solid #1d1d1f; padding-top: 8px; margin-top: 40px; }
|
.signature-line { border-top: 1px solid #1d1d1f; padding-top: 8px; margin-top: 40px; }
|
||||||
|
.file-list { margin-top: 16px; }
|
||||||
|
.file-item { display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #f9f9fb; border-radius: 8px; margin-bottom: 8px; }
|
||||||
|
.file-item a { color: #007AFF; text-decoration: none; font-weight: 500; }
|
||||||
|
.file-item a:hover { text-decoration: underline; }
|
||||||
|
.file-meta { font-size: 12px; color: #86868b; margin-left: 12px; }
|
||||||
|
.upload-area { border: 2px dashed #d2d2d7; border-radius: 12px; padding: 24px; text-align: center; background: #fafafa; cursor: pointer; transition: all 0.2s; }
|
||||||
|
.upload-area:hover { border-color: #007AFF; background: #f0f8ff; }
|
||||||
|
.upload-area.dragover { border-color: #34C759; background: #e8f5e9; }
|
||||||
|
.upload-icon { font-size: 48px; margin-bottom: 12px; }
|
||||||
@media print {
|
@media print {
|
||||||
.btn, .no-print { display: none !important; }
|
.btn, .no-print, .upload-area { display: none !important; }
|
||||||
.card { box-shadow: none; border: 1px solid #ddd; }
|
.card { box-shadow: none; border: 1px solid #ddd; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -56,7 +65,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="no-print" style="text-align: right; margin-bottom: 20px;">
|
<div class="no-print" style="text-align: right; margin-bottom: 20px;">
|
||||||
<button class="btn btn-primary" onclick="window.print()">🖨 Печать / PDF</button>
|
<button class="btn btn-primary" onclick="window.print()">🖨 Печать / PDF</button>
|
||||||
<button class="btn btn-success" onclick="exportToExcel()">📥 Выгрузить в Excel</button>
|
<button class="btn btn-success" onclick="exportToExcel()">📤 Выгрузить в Excel</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Раздел 1: Входные данные -->
|
<!-- Раздел 1: Входные данные -->
|
||||||
@ -81,6 +90,18 @@
|
|||||||
<label>Техническая спецификация</label>
|
<label>Техническая спецификация</label>
|
||||||
<textarea name="techSpec" placeholder="описание товара/работы/услуги"></textarea>
|
<textarea name="techSpec" placeholder="описание товара/работы/услуги"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>📎 Прикрепить файлы (ТЗ, спецификации, письма)</label>
|
||||||
|
<div class="upload-area" id="uploadArea1" onclick="document.getElementById('fileInput1').click()">
|
||||||
|
<div class="upload-icon">📎</div>
|
||||||
|
<div>Нажмите для загрузки или перетащите файлы сюда</div>
|
||||||
|
<div style="font-size: 12px; color: #86868b; margin-top: 8px;">DOCX, XLSX, PDF до 10 МБ</div>
|
||||||
|
</div>
|
||||||
|
<input type="file" id="fileInput1" multiple accept=".doc,.docx,.xls,.xlsx,.pdf" style="display: none;" onchange="handleFiles(this.files, 'Входные данные')">
|
||||||
|
</div>
|
||||||
|
<div class="file-list" id="filesSection1"></div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Сохранить</button>
|
<button type="submit" class="btn btn-primary">Сохранить</button>
|
||||||
</form>
|
</form>
|
||||||
<div id="inputDataDisplay" style="margin-top: 20px; padding: 16px; background: #f9f9fb; border-radius: 10px; display: none;"></div>
|
<div id="inputDataDisplay" style="margin-top: 20px; padding: 16px; background: #f9f9fb; border-radius: 10px; display: none;"></div>
|
||||||
@ -117,6 +138,17 @@
|
|||||||
<label>Контактные данные (веб-сайт, телефон, адрес, email)</label>
|
<label>Контактные данные (веб-сайт, телефон, адрес, email)</label>
|
||||||
<input type="text" name="contacts" placeholder="+7 XXX XXX-XX-XX, example.kz, info@example.kz">
|
<input type="text" name="contacts" placeholder="+7 XXX XXX-XX-XX, example.kz, info@example.kz">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>📎 Прикрепить файлы (КП, уставные документы)</label>
|
||||||
|
<div class="upload-area" onclick="document.getElementById('fileInput2').click()">
|
||||||
|
<div class="upload-icon">📎</div>
|
||||||
|
<div>Нажмите для загрузки файлов</div>
|
||||||
|
</div>
|
||||||
|
<input type="file" id="fileInput2" multiple accept=".doc,.docx,.xls,.xlsx,.pdf" style="display: none;" onchange="handleFiles(this.files, 'Поставщики')">
|
||||||
|
</div>
|
||||||
|
<div class="file-list" id="filesSection2"></div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-success">Добавить поставщика</button>
|
<button type="submit" class="btn btn-success">Добавить поставщика</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@ -151,6 +183,17 @@
|
|||||||
<input type="date" name="date">
|
<input type="date" name="date">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>📎 Прикрепить файлы (КП, письма)</label>
|
||||||
|
<div class="upload-area" onclick="document.getElementById('fileInput3').click()">
|
||||||
|
<div class="upload-icon">📎</div>
|
||||||
|
<div>Нажмите для загрузки файлов</div>
|
||||||
|
</div>
|
||||||
|
<input type="file" id="fileInput3" multiple accept=".doc,.docx,.xls,.xlsx,.pdf" style="display: none;" onchange="handleFiles(this.files, 'Предложения')">
|
||||||
|
</div>
|
||||||
|
<div class="file-list" id="filesSection3"></div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-success">Добавить предложение</button>
|
<button type="submit" class="btn btn-success">Добавить предложение</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@ -168,15 +211,15 @@
|
|||||||
<h2 class="section-title"><span class="section-num">4</span>Заключение по проверке (ст. 31 Порядка закупок)</h2>
|
<h2 class="section-title"><span class="section-num">4</span>Заключение по проверке (ст. 31 Порядка закупок)</h2>
|
||||||
<div class="checkbox-group">
|
<div class="checkbox-group">
|
||||||
<input type="checkbox" id="check1">
|
<input type="checkbox" id="check1">
|
||||||
<label for="check1">Потенциальный поставщик или его субподрядчик (соисполнитель) либо участник консорциума НЕ состоит в реестре недобросовестных участников государственных закупок и (или) в реестре недобросовестных участников закупок, и (или) в перечне ненадежных потенциальных поставщиков (поставщиков) Фонда, и (или) в списке должников, в отношении которых вступило в законную силу решение суда о признании их банкротами</label>
|
<label for="check1">Потенциальный поставщик или его субподрядчик НЕ состоит в реестре недобросовестных участников</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="checkbox-group">
|
<div class="checkbox-group">
|
||||||
<input type="checkbox" id="check2">
|
<input type="checkbox" id="check2">
|
||||||
<label for="check2">Потенциальный поставщик и (или) привлекаемый им субподрядчик (соисполнитель), и (или) их руководитель, и (или) учредители (акционеры) НЕ включены в перечень организаций и лиц, связанных с финансированием терроризма и экстремизма</label>
|
<label for="check2">НЕ включены в перечень организаций, связанных с финансированием терроризма</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="checkbox-group">
|
<div class="checkbox-group">
|
||||||
<input type="checkbox" id="check3">
|
<input type="checkbox" id="check3">
|
||||||
<label for="check3">Потенциальный поставщик НЕ является юридическим лицом, местом регистрации которого является государство или территория, включенные в перечень государств с льготным налогообложением</label>
|
<label for="check3">НЕ является юридическим лицом из государства с льготным налогообложением</label>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary no-print" onclick="saveChecks()">Сохранить проверку</button>
|
<button class="btn btn-primary no-print" onclick="saveChecks()">Сохранить проверку</button>
|
||||||
</div>
|
</div>
|
||||||
@ -243,10 +286,16 @@
|
|||||||
<p style="margin-top: 16px;">«___» __________ 2026 г.</p>
|
<p style="margin-top: 16px;">«___» __________ 2026 г.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Все файлы -->
|
||||||
|
<div class="card">
|
||||||
|
<h2 class="section-title">📎 Все прикреплённые файлы</h2>
|
||||||
|
<div class="file-list" id="allFilesList"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let data = { inputData: null, suppliers: [], offers: [], checks: { c1: false, c2: false, c3: false } };
|
let data = { inputData: null, suppliers: [], offers: [], checks: { c1: false, c2: false, c3: false }, files: [] };
|
||||||
|
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
const r = await fetch('/api/data');
|
const r = await fetch('/api/data');
|
||||||
@ -259,6 +308,7 @@
|
|||||||
document.getElementById('check1').checked = data.checks.c1;
|
document.getElementById('check1').checked = data.checks.c1;
|
||||||
document.getElementById('check2').checked = data.checks.c2;
|
document.getElementById('check2').checked = data.checks.c2;
|
||||||
document.getElementById('check3').checked = data.checks.c3;
|
document.getElementById('check3').checked = data.checks.c3;
|
||||||
|
renderFiles();
|
||||||
generateConclusion();
|
generateConclusion();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,6 +373,54 @@
|
|||||||
generateConclusion(min, avg);
|
generateConclusion(min, avg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderFiles() {
|
||||||
|
const allFiles = document.getElementById('allFilesList');
|
||||||
|
const section1 = document.getElementById('filesSection1');
|
||||||
|
const section2 = document.getElementById('filesSection2');
|
||||||
|
const section3 = document.getElementById('filesSection3');
|
||||||
|
|
||||||
|
const inputFiles = data.files.filter(f => f.section === 'Входные данные');
|
||||||
|
const supplierFiles = data.files.filter(f => f.section === 'Поставщики');
|
||||||
|
const offerFiles = data.files.filter(f => f.section === 'Предложения');
|
||||||
|
|
||||||
|
function renderFileList(files, container) {
|
||||||
|
if (files.length === 0) { container.innerHTML = ''; return; }
|
||||||
|
container.innerHTML = files.map(f =>
|
||||||
|
'<div class="file-item">' +
|
||||||
|
'<a href="' + f.path + '" download>📄 ' + f.name + '</a>' +
|
||||||
|
'<span class="file-meta">' + new Date(f.date).toLocaleDateString() + '</span>' +
|
||||||
|
'<button class="btn btn-danger no-print" onclick="deleteFile(\'' + f.path.split('/').pop() + '\')">✕</button>' +
|
||||||
|
'</div>'
|
||||||
|
).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFileList(inputFiles, section1);
|
||||||
|
renderFileList(supplierFiles, section2);
|
||||||
|
renderFileList(offerFiles, section3);
|
||||||
|
renderFileList(data.files, allFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleFiles(files, section) {
|
||||||
|
if (!files.length) return;
|
||||||
|
const formData = new FormData();
|
||||||
|
for (const f of files) {
|
||||||
|
formData.append('file', f);
|
||||||
|
}
|
||||||
|
formData.append('section', section);
|
||||||
|
|
||||||
|
const r = await fetch('/api/upload', { method: 'POST', body: formData });
|
||||||
|
const result = await r.json();
|
||||||
|
if (result.ok) {
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteFile(fileName) {
|
||||||
|
if (!confirm('Удалить файл?')) return;
|
||||||
|
await fetch('/api/files/' + fileName, { method: 'DELETE' });
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
function generateConclusion(min, avg) {
|
function generateConclusion(min, avg) {
|
||||||
const minOffer = data.offers.find(o => Number(o.price) === min);
|
const minOffer = data.offers.find(o => Number(o.price) === min);
|
||||||
const s = minOffer ? data.suppliers.find(x => x.id === minOffer.supplierId) : null;
|
const s = minOffer ? data.suppliers.find(x => x.id === minOffer.supplierId) : null;
|
||||||
@ -447,6 +545,10 @@
|
|||||||
rows.push(["5. Анализ", "Средняя цена", avg.toLocaleString() + ' тенге']);
|
rows.push(["5. Анализ", "Средняя цена", avg.toLocaleString() + ' тенге']);
|
||||||
rows.push(["5. Анализ", "Количество предложений", data.offers.length]);
|
rows.push(["5. Анализ", "Количество предложений", data.offers.length]);
|
||||||
|
|
||||||
|
data.files.forEach(f => {
|
||||||
|
rows.push(["Файлы", f.name, f.section]);
|
||||||
|
});
|
||||||
|
|
||||||
const csv = '\uFEFF' + [head.join(';'), ...rows.map(r => r.map(v => '"' + String(v).replace(/"/g, '""') + '"').join(';'))].join('\n');
|
const csv = '\uFEFF' + [head.join(';'), ...rows.map(r => r.map(v => '"' + String(v).replace(/"/g, '""') + '"').join(';'))].join('\n');
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = URL.createObjectURL(new Blob([csv], { type: 'text/csv' }));
|
a.href = URL.createObjectURL(new Blob([csv], { type: 'text/csv' }));
|
||||||
@ -454,6 +556,27 @@
|
|||||||
a.click();
|
a.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uploadAreas = document.querySelectorAll('.upload-area');
|
||||||
|
uploadAreas.forEach(area => {
|
||||||
|
area.addEventListener('dragover', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
area.classList.add('dragover');
|
||||||
|
});
|
||||||
|
area.addEventListener('dragleave', () => {
|
||||||
|
area.classList.remove('dragover');
|
||||||
|
});
|
||||||
|
area.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
area.classList.remove('dragover');
|
||||||
|
const input = area.parentElement.querySelector('input[type="file"]');
|
||||||
|
if (input) {
|
||||||
|
input.files = e.dataTransfer.files;
|
||||||
|
const event = new Event('change');
|
||||||
|
input.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
loadData();
|
loadData();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
72
server.js
72
server.js
@ -1,12 +1,16 @@
|
|||||||
const http = require("http");
|
const http = require("http");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const formidable = require("formidable");
|
||||||
|
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
const DATA = path.join(__dirname, "data.json");
|
const DATA = path.join(__dirname, "data.json");
|
||||||
|
const UPLOADS = path.join(__dirname, "uploads");
|
||||||
|
|
||||||
|
if (!fs.existsSync(UPLOADS)) fs.mkdirSync(UPLOADS);
|
||||||
|
|
||||||
function readData() {
|
function readData() {
|
||||||
try { return JSON.parse(fs.readFileSync(DATA, "utf8")); } catch (_) { return { inputData: null, suppliers: [], offers: [], checks: { c1: false, c2: false, c3: false } }; }
|
try { return JSON.parse(fs.readFileSync(DATA, "utf8")); } catch (_) { return { inputData: null, suppliers: [], offers: [], checks: { c1: false, c2: false, c3: false }, files: [] }; }
|
||||||
}
|
}
|
||||||
function writeData(data) {
|
function writeData(data) {
|
||||||
fs.writeFileSync(DATA, JSON.stringify(data, null, 2));
|
fs.writeFileSync(DATA, JSON.stringify(data, null, 2));
|
||||||
@ -25,6 +29,72 @@ http.createServer(async (req, res) => {
|
|||||||
return res.end(JSON.stringify(readData()));
|
return res.end(JSON.stringify(readData()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.method === "GET" && req.url === "/api/files") {
|
||||||
|
const data = readData();
|
||||||
|
res.writeHead(200, { "Content-Type": "application/json" });
|
||||||
|
return res.end(JSON.stringify(data.files || []));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method === "POST" && req.url === "/api/upload") {
|
||||||
|
const form = formidable({ uploadDir: UPLOADS, keepExtensions: true });
|
||||||
|
form.parse(req, async (err, fields, files) => {
|
||||||
|
if (err) {
|
||||||
|
res.writeHead(500, { "Content-Type": "application/json" });
|
||||||
|
return res.end(JSON.stringify({ error: err.message }));
|
||||||
|
}
|
||||||
|
const data = readData();
|
||||||
|
const uploaded = [];
|
||||||
|
if (files.file && Array.isArray(files.file)) {
|
||||||
|
for (const f of files.file) {
|
||||||
|
const fileName = Date.now() + "_" + path.basename(f.filepath);
|
||||||
|
const newPath = path.join(UPLOADS, fileName);
|
||||||
|
fs.renameSync(f.filepath, newPath);
|
||||||
|
const fileRec = {
|
||||||
|
id: Date.now() + Math.random(),
|
||||||
|
name: f.originalFilename || fileName,
|
||||||
|
path: "/api/files/" + fileName,
|
||||||
|
section: fields.section ? fields.section[0] : "other",
|
||||||
|
size: f.size,
|
||||||
|
date: new Date().toISOString()
|
||||||
|
};
|
||||||
|
data.files.push(fileRec);
|
||||||
|
uploaded.push(fileRec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeData(data);
|
||||||
|
res.writeHead(200, { "Content-Type": "application/json" });
|
||||||
|
return res.end(JSON.stringify({ ok: true, files: uploaded }));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method === "GET" && req.url.startsWith("/api/files/")) {
|
||||||
|
const fileName = req.url.split("/")[3];
|
||||||
|
const filePath = path.join(UPLOADS, fileName);
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
const ext = path.extname(fileName).toLowerCase();
|
||||||
|
const mime = ext === ".pdf" ? "application/pdf" : ext === ".docx" ? "application/vnd.openxmlformats-officedocument.wordprocessingml.document" : ext === ".xlsx" ? "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" : "application/octet-stream";
|
||||||
|
res.writeHead(200, { "Content-Type": mime, "Content-Disposition": "attachment; filename=\"" + fileName + "\"" });
|
||||||
|
return res.end(fs.readFileSync(filePath));
|
||||||
|
}
|
||||||
|
res.writeHead(404); res.end("File not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method === "DELETE" && req.url.startsWith("/api/files/")) {
|
||||||
|
const fileName = req.url.split("/")[3];
|
||||||
|
const data = readData();
|
||||||
|
const fileRec = data.files.find(f => f.path.endsWith(fileName));
|
||||||
|
if (fileRec) {
|
||||||
|
const filePath = path.join(UPLOADS, fileName);
|
||||||
|
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
||||||
|
data.files = data.files.filter(f => f.path !== fileRec.path);
|
||||||
|
writeData(data);
|
||||||
|
}
|
||||||
|
res.writeHead(200, { "Content-Type": "application/json" });
|
||||||
|
return res.end(JSON.stringify({ ok: true }));
|
||||||
|
}
|
||||||
|
|
||||||
if (req.method === "POST" && req.url === "/api/inputData") {
|
if (req.method === "POST" && req.url === "/api/inputData") {
|
||||||
const d = await body(req);
|
const d = await body(req);
|
||||||
const data = readData();
|
const data = readData();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user