forked from ttc/micro-frontend
first commit
This commit is contained in:
95
plan-app/bydep.js
Normal file
95
plan-app/bydep.js
Normal file
@@ -0,0 +1,95 @@
|
||||
const XLSX = require('xlsx');
|
||||
const path = require('path');
|
||||
|
||||
function runBydep(inputPath, outputPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!inputPath) {
|
||||
return reject(new Error('❌ ไม่พบ path ของไฟล์ Excel'));
|
||||
}
|
||||
if (!outputPath) {
|
||||
return reject(new Error('❌ ไม่พบ path สำหรับบันทึกไฟล์'));
|
||||
}
|
||||
|
||||
console.log('📂 กำลังอ่านไฟล์ (bydep):', inputPath);
|
||||
|
||||
try {
|
||||
// 📘 อ่านไฟล์ Excel
|
||||
const excel = XLSX.readFile(path.resolve(inputPath));
|
||||
const sheet = excel.Sheets[excel.SheetNames[0]];
|
||||
const data = XLSX.utils.sheet_to_json(sheet);
|
||||
|
||||
// ⚙️ ฟังก์ชันลบตัวเลขและจุดนำหน้า
|
||||
const cleanSource = (text) => {
|
||||
if (!text) return '';
|
||||
return text.toString().replace(/^\d+\.\s*/, '').trim();
|
||||
};
|
||||
|
||||
// 🧮 เตรียมข้อมูลรวมตาม "แผนกวิชา"
|
||||
const pivot = {};
|
||||
|
||||
data.forEach(row => {
|
||||
const plan = row['แผนกวิชา'] || 'ไม่ระบุแผนกวิชา';
|
||||
const source = cleanSource(row['แหล่งงบประมาณ']);
|
||||
const budget = Number(row['งบประมาณ']) || 0;
|
||||
|
||||
if (!source || !plan) return;
|
||||
|
||||
if (!pivot[plan]) pivot[plan] = {};
|
||||
if (!pivot[plan][source]) pivot[plan][source] = 0;
|
||||
|
||||
pivot[plan][source] += budget;
|
||||
});
|
||||
|
||||
// 🧾 ดึงชื่อคอลัมน์ (แหล่งงบทั้งหมด)
|
||||
const allSources = Array.from(
|
||||
new Set(Object.values(pivot).flatMap(obj => Object.keys(obj)))
|
||||
);
|
||||
|
||||
// 📊 ฟังก์ชัน format ตัวเลข
|
||||
const formatNumber = (num) =>
|
||||
num ? num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '';
|
||||
|
||||
// 🪄 สร้างตารางผลลัพธ์
|
||||
const result = [];
|
||||
let grandTotalAll = 0;
|
||||
const columnTotals = {};
|
||||
|
||||
Object.entries(pivot).forEach(([plan, sources]) => {
|
||||
const row = { 'แผนกวิชา': plan };
|
||||
let rowTotal = 0;
|
||||
|
||||
allSources.forEach(source => {
|
||||
const val = sources[source] || 0;
|
||||
row[source] = val ? formatNumber(val) : '';
|
||||
rowTotal += val;
|
||||
columnTotals[source] = (columnTotals[source] || 0) + val;
|
||||
});
|
||||
|
||||
grandTotalAll += rowTotal;
|
||||
row['รวมทั้งหมด'] = formatNumber(rowTotal);
|
||||
result.push(row);
|
||||
});
|
||||
|
||||
// ➕ เพิ่มแถวสุดท้าย “รวมทั้งหมด”
|
||||
const totalRow = { 'แผนกวิชา': 'รวมทั้งหมด' };
|
||||
allSources.forEach(source => {
|
||||
totalRow[source] = columnTotals[source] ? formatNumber(columnTotals[source]) : '';
|
||||
});
|
||||
totalRow['รวมทั้งหมด'] = formatNumber(grandTotalAll);
|
||||
result.push(totalRow);
|
||||
|
||||
const newSheet = XLSX.utils.json_to_sheet(result);
|
||||
const newBook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(newBook, newSheet, 'Summary');
|
||||
XLSX.writeFile(newBook, outputPath); // ✅ ใช้ outputPath ที่รับเข้ามา
|
||||
|
||||
console.log(`✅ สรุปผลสำเร็จ → ${outputPath}`);
|
||||
resolve(`✅ บันทึกไฟล์สำเร็จแล้ว`);
|
||||
} catch (error) {
|
||||
console.error('❌ เกิดข้อผิดพลาดใน bydep:', error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { runBydep };
|
||||
95
plan-app/bypivot.js
Normal file
95
plan-app/bypivot.js
Normal file
@@ -0,0 +1,95 @@
|
||||
const XLSX = require('xlsx');
|
||||
const path = require('path');
|
||||
|
||||
function runBypivot(inputPath, outputPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!inputPath) {
|
||||
return reject(new Error('❌ ไม่พบ path ของไฟล์ Excel'));
|
||||
}
|
||||
if (!outputPath) {
|
||||
return reject(new Error('❌ ไม่พบ path สำหรับบันทึกไฟล์'));
|
||||
}
|
||||
|
||||
console.log('📂 กำลังอ่านไฟล์ (bypivot):', inputPath);
|
||||
|
||||
try {
|
||||
// 📘 อ่านไฟล์ Excel
|
||||
const excel = XLSX.readFile(path.resolve(inputPath));
|
||||
const sheet = excel.Sheets[excel.SheetNames[0]];
|
||||
const data = XLSX.utils.sheet_to_json(sheet);
|
||||
|
||||
// ⚙️ ฟังก์ชันลบตัวเลขและจุดนำหน้า
|
||||
const cleanSource = (text) => {
|
||||
if (!text) return '';
|
||||
return text.toString().replace(/^\d+\.\s*/, '').trim();
|
||||
};
|
||||
|
||||
// 🧮 เตรียมข้อมูลรวมตาม "แผนงานในการขอซื้อของจ้าง"
|
||||
const pivot = {};
|
||||
|
||||
data.forEach(row => {
|
||||
const plan = row['แผนงานในการขอซื้อของจ้าง'] || 'ไม่ระบุแผนงาน';
|
||||
const source = cleanSource(row['แหล่งงบประมาณ']);
|
||||
const budget = Number(row['งบประมาณ']) || 0;
|
||||
|
||||
if (!source || !plan) return;
|
||||
|
||||
if (!pivot[plan]) pivot[plan] = {};
|
||||
if (!pivot[plan][source]) pivot[plan][source] = 0;
|
||||
|
||||
pivot[plan][source] += budget;
|
||||
});
|
||||
|
||||
// 🧾 ดึงชื่อคอลัมน์ (แหล่งงบทั้งหมด)
|
||||
const allSources = Array.from(
|
||||
new Set(Object.values(pivot).flatMap(obj => Object.keys(obj)))
|
||||
);
|
||||
|
||||
// 📊 ฟังก์ชัน format ตัวเลข
|
||||
const formatNumber = (num) =>
|
||||
num ? num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '';
|
||||
|
||||
// 🪄 สร้างตารางผลลัพธ์
|
||||
const result = [];
|
||||
let grandTotalAll = 0;
|
||||
const columnTotals = {};
|
||||
|
||||
Object.entries(pivot).forEach(([plan, sources]) => {
|
||||
const row = { 'แผนงานในการขอซื้อของจ้าง': plan };
|
||||
let rowTotal = 0;
|
||||
|
||||
allSources.forEach(source => {
|
||||
const val = sources[source] || 0;
|
||||
row[source] = val ? formatNumber(val) : '';
|
||||
rowTotal += val;
|
||||
columnTotals[source] = (columnTotals[source] || 0) + val;
|
||||
});
|
||||
|
||||
grandTotalAll += rowTotal;
|
||||
row['รวมทั้งหมด'] = formatNumber(rowTotal);
|
||||
result.push(row);
|
||||
});
|
||||
|
||||
// ➕ เพิ่มแถวสุดท้าย “รวมทั้งหมด”
|
||||
const totalRow = { 'แผนงานในการขอซื้อของจ้าง': 'รวมทั้งหมด' };
|
||||
allSources.forEach(source => {
|
||||
totalRow[source] = columnTotals[source] ? formatNumber(columnTotals[source]) : '';
|
||||
});
|
||||
totalRow['รวมทั้งหมด'] = formatNumber(grandTotalAll);
|
||||
result.push(totalRow);
|
||||
|
||||
const newSheet = XLSX.utils.json_to_sheet(result);
|
||||
const newBook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(newBook, newSheet, 'Summary');
|
||||
XLSX.writeFile(newBook, outputPath); // ✅ ใช้ outputPath ที่รับเข้ามา
|
||||
|
||||
console.log(`✅ สรุปผลสำเร็จ → ${outputPath}`);
|
||||
resolve(`✅ บันทึกไฟล์สำเร็จแล้ว`);
|
||||
} catch (error) {
|
||||
console.error('❌ เกิดข้อผิดพลาดใน bypivot:', error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { runBypivot };
|
||||
95
plan-app/bywork.js
Normal file
95
plan-app/bywork.js
Normal file
@@ -0,0 +1,95 @@
|
||||
const XLSX = require('xlsx');
|
||||
const path = require('path');
|
||||
|
||||
function runBywork(inputPath, outputPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!inputPath) {
|
||||
return reject(new Error('❌ ไม่พบ path ของไฟล์ Excel'));
|
||||
}
|
||||
if (!outputPath) {
|
||||
return reject(new Error('❌ ไม่พบ path สำหรับบันทึกไฟล์'));
|
||||
}
|
||||
|
||||
console.log('📂 กำลังอ่านไฟล์ (bywork):', inputPath);
|
||||
|
||||
try {
|
||||
// 📘 อ่านไฟล์ Excel
|
||||
const excel = XLSX.readFile(path.resolve(inputPath));
|
||||
const sheet = excel.Sheets[excel.SheetNames[0]];
|
||||
const data = XLSX.utils.sheet_to_json(sheet);
|
||||
|
||||
// ⚙️ ฟังก์ชันลบตัวเลขและจุดนำหน้า
|
||||
const cleanSource = (text) => {
|
||||
if (!text) return '';
|
||||
return text.toString().replace(/^\d+\.\s*/, '').trim();
|
||||
};
|
||||
|
||||
// 🧮 เตรียมข้อมูลรวมตาม "งาน"
|
||||
const pivot = {};
|
||||
|
||||
data.forEach(row => {
|
||||
const plan = row['งาน'] || 'ไม่ระบุงาน';
|
||||
const source = cleanSource(row['แหล่งงบประมาณ']);
|
||||
const budget = Number(row['งบประมาณ']) || 0;
|
||||
|
||||
if (!source || !plan) return;
|
||||
|
||||
if (!pivot[plan]) pivot[plan] = {};
|
||||
if (!pivot[plan][source]) pivot[plan][source] = 0;
|
||||
|
||||
pivot[plan][source] += budget;
|
||||
});
|
||||
|
||||
// 🧾 ดึงชื่อคอลัมน์ (แหล่งงบทั้งหมด)
|
||||
const allSources = Array.from(
|
||||
new Set(Object.values(pivot).flatMap(obj => Object.keys(obj)))
|
||||
);
|
||||
|
||||
// 📊 ฟังก์ชัน format ตัวเลข
|
||||
const formatNumber = (num) =>
|
||||
num ? num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '';
|
||||
|
||||
// 🪄 สร้างตารางผลลัพธ์
|
||||
const result = [];
|
||||
let grandTotalAll = 0;
|
||||
const columnTotals = {};
|
||||
|
||||
Object.entries(pivot).forEach(([plan, sources]) => {
|
||||
const row = { 'งาน': plan };
|
||||
let rowTotal = 0;
|
||||
|
||||
allSources.forEach(source => {
|
||||
const val = sources[source] || 0;
|
||||
row[source] = val ? formatNumber(val) : '';
|
||||
rowTotal += val;
|
||||
columnTotals[source] = (columnTotals[source] || 0) + val;
|
||||
});
|
||||
|
||||
grandTotalAll += rowTotal;
|
||||
row['รวมทั้งหมด'] = formatNumber(rowTotal);
|
||||
result.push(row);
|
||||
});
|
||||
|
||||
// ➕ เพิ่มแถวสุดท้าย “รวมทั้งหมด”
|
||||
const totalRow = { 'งาน': 'รวมทั้งหมด' };
|
||||
allSources.forEach(source => {
|
||||
totalRow[source] = columnTotals[source] ? formatNumber(columnTotals[source]) : '';
|
||||
});
|
||||
totalRow['รวมทั้งหมด'] = formatNumber(grandTotalAll);
|
||||
result.push(totalRow);
|
||||
|
||||
const newSheet = XLSX.utils.json_to_sheet(result);
|
||||
const newBook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(newBook, newSheet, 'Summary');
|
||||
XLSX.writeFile(newBook, outputPath); // ✅ ใช้ outputPath ที่รับเข้ามา
|
||||
|
||||
console.log(`✅ สรุปผลสำเร็จ → ${outputPath}`);
|
||||
resolve(`✅ บันทึกไฟล์สำเร็จแล้ว`);
|
||||
} catch (error) {
|
||||
console.error('❌ เกิดข้อผิดพลาดใน bywork:', error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { runBywork };
|
||||
253
plan-app/index.html
Normal file
253
plan-app/index.html
Normal file
@@ -0,0 +1,253 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="th">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>โปรแกรมจำแนกงบประมาณ</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root{
|
||||
--bg:#f4f7fb;
|
||||
--card:#ffffff;
|
||||
--primary:#2563eb;
|
||||
--muted:#6b7280;
|
||||
--accent:#10b981;
|
||||
--danger:#ef4444;
|
||||
--shadow: 0 6px 18px rgba(31,41,55,0.06);
|
||||
--radius:12px;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
font-family: 'Inter', system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
|
||||
background: linear-gradient(180deg,#f7fbff 0%,var(--bg) 60%);
|
||||
margin:0;
|
||||
min-height:100vh;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
padding:28px;
|
||||
color:#0f172a;
|
||||
}
|
||||
.container{
|
||||
width:100%;
|
||||
max-width:920px;
|
||||
background:var(--card);
|
||||
border-radius:var(--radius);
|
||||
box-shadow:var(--shadow);
|
||||
padding:28px;
|
||||
display:grid;
|
||||
grid-template-columns: 1fr 360px;
|
||||
gap:20px;
|
||||
align-items:start;
|
||||
}
|
||||
header{
|
||||
grid-column:1 / -1;
|
||||
display:flex;
|
||||
gap:16px;
|
||||
align-items:center;
|
||||
}
|
||||
.logo{
|
||||
width:56px;
|
||||
height:56px;
|
||||
border-radius:12px;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f3f4f6 100%);
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
color:#111827;
|
||||
font-weight:700;
|
||||
font-size:20px;
|
||||
box-shadow: 0 6px 18px rgba(15,23,42,0.06);
|
||||
border: 1px solid rgba(15,23,42,0.04);
|
||||
}
|
||||
h1{
|
||||
margin:0;
|
||||
font-size:18px;
|
||||
letter-spacing:-0.2px;
|
||||
}
|
||||
p.lead{
|
||||
margin:4px 0 0 0;
|
||||
font-size:13px;
|
||||
color:var(--muted);
|
||||
}
|
||||
|
||||
.main{
|
||||
padding-top:6px;
|
||||
}
|
||||
.step{
|
||||
background:linear-gradient(180deg, #fff, #fbfdff);
|
||||
border-radius:10px;
|
||||
padding:14px;
|
||||
margin-bottom:12px;
|
||||
border:1px solid rgba(15,23,42,0.04);
|
||||
}
|
||||
.step h3{
|
||||
margin:0 0 6px 0;
|
||||
font-size:14px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap:8px;
|
||||
}
|
||||
.file-row{
|
||||
display:flex;
|
||||
gap:10px;
|
||||
align-items:center;
|
||||
}
|
||||
input[type="text"]{
|
||||
flex:1;
|
||||
padding:10px 12px;
|
||||
border-radius:8px;
|
||||
border:1px solid rgba(15,23,42,0.06);
|
||||
background:#fbfdff;
|
||||
color:#0f172a;
|
||||
font-size:14px;
|
||||
}
|
||||
button, select{
|
||||
border:0;
|
||||
background:var(--primary);
|
||||
color:#fff;
|
||||
padding:10px 14px;
|
||||
border-radius:8px;
|
||||
cursor:pointer;
|
||||
font-weight:600;
|
||||
font-size:14px;
|
||||
transition:transform .08s ease, box-shadow .08s ease;
|
||||
box-shadow: 0 6px 14px rgba(37,99,235,0.12);
|
||||
}
|
||||
button.secondary{
|
||||
background:#fff;
|
||||
color:#0f172a;
|
||||
border:1px solid rgba(15,23,42,0.06);
|
||||
box-shadow:none;
|
||||
}
|
||||
button:active{ transform:translateY(1px) }
|
||||
select{
|
||||
width:100%;
|
||||
background:linear-gradient(180deg,#ffffff,#fbfdff);
|
||||
color:#0f172a;
|
||||
text-align:left;
|
||||
appearance:none;
|
||||
}
|
||||
.side{
|
||||
padding:12px;
|
||||
border-radius:10px;
|
||||
background:linear-gradient(180deg,#fff,#fbfdff);
|
||||
border:1px solid rgba(15,23,42,0.04);
|
||||
height:100%;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
gap:12px;
|
||||
}
|
||||
.export-btn{
|
||||
display:flex;
|
||||
gap:8px;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
padding:12px;
|
||||
font-size:16px;
|
||||
border-radius:10px;
|
||||
background:linear-gradient(90deg,var(--accent),#059669);
|
||||
box-shadow:0 10px 30px rgba(16,185,129,0.12);
|
||||
border:0;
|
||||
}
|
||||
.status{
|
||||
font-size:13px;
|
||||
color:var(--muted);
|
||||
min-height:36px;
|
||||
}
|
||||
.small{
|
||||
font-size:12px;
|
||||
color:var(--muted);
|
||||
}
|
||||
.hint{
|
||||
display:flex;
|
||||
gap:10px;
|
||||
align-items:center;
|
||||
font-size:13px;
|
||||
color:var(--muted);
|
||||
}
|
||||
footer{
|
||||
grid-column:1 / -1;
|
||||
margin-top:8px;
|
||||
text-align:right;
|
||||
font-size:12px;
|
||||
color:var(--muted);
|
||||
}
|
||||
@media (max-width:880px){
|
||||
.container{ grid-template-columns: 1fr; padding:18px; }
|
||||
.logo{ width:48px;height:48px }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container" role="application" aria-label="โปรแกรมจำแนกงบประมาณ">
|
||||
<header>
|
||||
<div class="logo" style="overflow:hidden;">
|
||||
<img src="./logo.ico" alt="BC" style="width:100%;height:100%;object-fit:contain;display:block;border-radius:inherit;">
|
||||
</div>
|
||||
<div>
|
||||
<h1>📊 โปรแกรมจำแนกงบประมาณ — รุ่นทดสอบ</h1>
|
||||
<p class="lead">อัปโหลดไฟล์ Excel แล้วเลือกวิธีการจำแนก จากนั้นกด Export เพื่อสร้างไฟล์ผลลัพธ์</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="main">
|
||||
<section class="step" aria-labelledby="step1">
|
||||
<h3 id="step1">1. เลือกไฟล์ Excel</h3>
|
||||
<div class="file-row">
|
||||
<input type="text" id="filePath" readonly placeholder="ยังไม่ได้เลือกไฟล์">
|
||||
<button id="btnSelect" class="secondary" title="เลือกไฟล์">เลือกไฟล์</button>
|
||||
</div>
|
||||
<p class="small" style="margin-top:8px">รองรับ .xlsx, .xls — สามารถลากไฟล์มาวางได้ (ถ้า renderer รองรับ)</p>
|
||||
</section>
|
||||
|
||||
<section class="step" aria-labelledby="step2">
|
||||
<h3 id="step2">2. เลือกรูปแบบการจำแนก</h3>
|
||||
<div style="display:grid;grid-template-columns:1fr;gap:8px;">
|
||||
<select id="mode" aria-label="เลือกรูปแบบการจำแนก">
|
||||
<option value="bywork.js">ตามงาน</option>
|
||||
<option value="bydep.js">ตามแผนกวิชา</option>
|
||||
<option value="bypivot.js">ตามแผนงานในการขอซื้อของจ้าง</option>
|
||||
</select>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="step" aria-labelledby="step3">
|
||||
<h3 id="step3">3. สร้างไฟล์ผลลัพธ์</h3>
|
||||
<p class="small">หลังจากกด Export ระบบจะประมวลผลและดาวน์โหลดไฟล์ Excel ที่แบ่งหมวดงบประมาณแล้ว</p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<aside class="side" aria-labelledby="actions">
|
||||
<div id="actions">
|
||||
<button id="btnRun" class="export-btn" title="Export Excel">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" style="margin-right:6px" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path d="M12 3v12" stroke="white" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.5 7.5L12 3l3.5 4.5" stroke="white" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<rect x="4" y="17" width="16" height="3" rx="1.2" fill="white"/>
|
||||
</svg>
|
||||
Export Excel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="status" id="status">สถานะ: พร้อมใช้งาน</div>
|
||||
|
||||
<div class="hint">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M12 2a10 10 0 100 20 10 10 0 000-20z" stroke="#9CA3AF" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11 10h1v5h1" stroke="#9CA3AF" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 7h.01" stroke="#9CA3AF" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<div>
|
||||
<div style="font-weight:600">คำแนะนำ</div>
|
||||
<div style="color:var(--muted);margin-top:4px">ตรวจสอบความถูกต้องของคอลัมน์ในไฟล์ก่อน Export เพื่อผลลัพธ์ที่แม่นยำ</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<footer>เวอร์ชันทดสอบ • เก็บไฟล์ต้นฉบับไว้ก่อนการใช้งาน</footer>
|
||||
</div>
|
||||
|
||||
<script src="renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
plan-app/logo.ico
Normal file
BIN
plan-app/logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 225 KiB |
67
plan-app/main.js
Normal file
67
plan-app/main.js
Normal file
@@ -0,0 +1,67 @@
|
||||
// main.js
|
||||
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
|
||||
const path = require('path');
|
||||
const { runBypivot } = require('./bypivot.js');
|
||||
const { runBydep } = require('./bydep.js');
|
||||
const { runBywork } = require('./bywork.js');
|
||||
|
||||
const scriptRunners = {
|
||||
'bypivot.js': { runner: runBypivot, defaultName: 'output_แผนงานในการขอซื้อของจ้าง.xlsx' },
|
||||
'bydep.js': { runner: runBydep, defaultName: 'output_แผนกวิชา.xlsx' },
|
||||
'bywork.js': { runner: runBywork, defaultName: 'output_งาน.xlsx' },
|
||||
};
|
||||
|
||||
function createWindow() {
|
||||
const win = new BrowserWindow({
|
||||
width: 650,
|
||||
height: 450,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
});
|
||||
|
||||
win.loadFile(path.join(__dirname, 'index.html'));
|
||||
// win.webContents.openDevTools();
|
||||
}
|
||||
|
||||
ipcMain.handle('select-file', async () => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
title: 'เลือกไฟล์ Excel',
|
||||
filters: [{ name: 'Excel Files', extensions: ['xlsx'] }],
|
||||
properties: ['openFile']
|
||||
});
|
||||
|
||||
if (result.canceled || result.filePaths.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.filePaths[0];
|
||||
});
|
||||
|
||||
// ✅ ส่วนรันสคริปต์ที่ปรับปรุงใหม่ + เพิ่ม Save Dialog
|
||||
ipcMain.handle('run-script', async (_, { script, filePath }) => {
|
||||
const scriptInfo = scriptRunners[script];
|
||||
if (!scriptInfo) {
|
||||
throw new Error(`ไม่พบสคริปต์สำหรับ: ${script}`);
|
||||
}
|
||||
|
||||
// 🚀 แสดงหน้าต่าง Save Dialog
|
||||
const saveResult = await dialog.showSaveDialog({
|
||||
title: 'เลือกตำแหน่งที่จะบันทึกไฟล์',
|
||||
defaultPath: scriptInfo.defaultName,
|
||||
filters: [{ name: 'Excel Files', extensions: ['xlsx'] }]
|
||||
});
|
||||
|
||||
// ถ้าผู้ใช้ไม่เลือกที่บันทึก ให้ยกเลิก
|
||||
if (saveResult.canceled || !saveResult.filePath) {
|
||||
return 'ยกเลิกการบันทึก'; // ส่งข้อความกลับไปบอกสถานะ
|
||||
}
|
||||
|
||||
const outputPath = saveResult.filePath;
|
||||
|
||||
// เรียกใช้ฟังก์ชันโดยตรงและส่งทั้ง input และ output path
|
||||
return scriptInfo.runner(filePath, outputPath);
|
||||
});
|
||||
|
||||
app.whenReady().then(createWindow);
|
||||
5036
plan-app/package-lock.json
generated
Normal file
5036
plan-app/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
plan-app/package.json
Normal file
30
plan-app/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "budget-classifier",
|
||||
"version": "1.0.0",
|
||||
"description": "โปรแกรมจำแนกงบประมาณ (Electron GUI)",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"build": "electron-builder"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.yourcompany.budgetclassifier",
|
||||
"productName": "Budget Classifier",
|
||||
"win": {
|
||||
"target": "nsis",
|
||||
"icon": "./logo.ico"
|
||||
},
|
||||
"files": [
|
||||
"**/*",
|
||||
"!node_modules/.cache/**/*",
|
||||
"!*.log"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^32.0.0",
|
||||
"electron-builder": "^24.13.3"
|
||||
}
|
||||
}
|
||||
32
plan-app/renderer.js
Normal file
32
plan-app/renderer.js
Normal file
@@ -0,0 +1,32 @@
|
||||
// renderer.js
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
||||
document.getElementById('btnSelect').addEventListener('click', async () => {
|
||||
console.log('🟢 clicked select'); // Debug
|
||||
const filePath = await ipcRenderer.invoke('select-file');
|
||||
console.log('📁 File selected:', filePath);
|
||||
if (filePath) document.getElementById('filePath').value = filePath;
|
||||
});
|
||||
|
||||
document.getElementById('btnRun').addEventListener('click', async () => {
|
||||
const filePath = document.getElementById('filePath').value;
|
||||
const mode = document.getElementById('mode').value;
|
||||
if (!filePath) {
|
||||
document.getElementById('status').innerText = '⚠️ กรุณาเลือกไฟล์ Excel ก่อน';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('status').innerText = 'กำลังประมวลผล...';
|
||||
try {
|
||||
const resultMessage = await ipcRenderer.invoke('run-script', { script: mode, filePath });
|
||||
|
||||
// ตรวจสอบข้อความที่ส่งกลับมาจาก main process
|
||||
if (resultMessage === 'ยกเลิกการบันทึก') {
|
||||
document.getElementById('status').innerText = 'สถานะ: ยกเลิกโดยผู้ใช้';
|
||||
} else {
|
||||
document.getElementById('status').innerText = `✅ ${resultMessage}`; // แสดงข้อความสำเร็จที่ได้รับ
|
||||
}
|
||||
} catch (err) {
|
||||
document.getElementById('status').innerText = '❌ เกิดข้อผิดพลาด: ' + err;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user