ระบบจัดการที่จอดรถใน JavaScript
สมมติฐาน
มีที่จอดรถแบบเสียค่าบริการโดยมีอัตราดังต่อไปนี้: $1 for the first hour and $0.5 สำหรับทุกๆชั่วโมงต่อ ๆ ไป สามารถจอดรถได้ 10 คัน
จำเป็น
จัดทำระบบการจัดการกิจกรรมสำหรับที่จอดรถโดยมีลักษณะดังต่อไปนี้:
- ช่วงเวลาจอดจะปัดขึ้นเป็นชั่วโมงที่ใกล้ที่สุด
- เมื่อรถคันใหม่เข้าสู่ลานจอดรถระบบจอดรถจะใช้หมายเลขทะเบียนของรถ
- เมื่อรถออกจากที่จอดรถระบบจะสรุปข้อมูลโดยขึ้นอยู่กับระยะเวลาที่เข้าพัก ข้อมูลใดที่คุณคิดว่าสำคัญเป็นส่วนหนึ่งของบทสรุป
- ลูกค้าสามารถดูรายชื่อรถที่จอดในลานจอดรถได้ตามเวลาที่กำหนด
ความต้องการทางด้านเทคนิค
- การติดตั้งจะดำเนินการโดยใช้ JavaScript ซึ่งข้อมูลทั้งหมดจะถูกเก็บไว้ในหน่วยความจำโดยไม่จำเป็นต้องจัดเก็บถาวร
- เขียนรหัสที่สะอาด
- สร้าง GUI ง่ายๆที่อนุญาตให้ป้อนและอ่านข้อมูลจากคอมพิวเตอร์ แสดงข้อมูลในหน้าเดียวกัน
วิธีการแก้
ฉันใช้ Bootstrap 4 เพื่อความสวยงาม ตัวสคริปต์ใช้เฉพาะ JavaScript jQuery รวมอยู่ในการทำงานของ corect ของ Bootstrap
let cars = [];
const addCarButton = document.querySelector('#carButton');
const minLicenseeLength = 7;
const payPerHour = 0.5;
const payFirstHour = 1;
const totalPlaces = 10;
const formatDate = (date) => {
var hours = date.getHours();
var minutes = date.getMinutes();
var ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12;
hours = hours ? hours : 12;
minutes = minutes < 10 ? '0' + minutes : minutes;
var strTime = hours + ':' + minutes + ' ' + ampm;
return date.getDate() + "/" + (date.getMonth() + 1) + "/" + date.getFullYear() + " " + strTime;
}
const secondsToHours = (d) => {
d = Number(d);
let h = Math.ceil(d / 3600);
return h;
}
const renterTable = () => {
let results = '';
for (var i = 0; i < cars.length; i++) {
let licensee = cars[i].licensee;
let arrival = formatDate(cars[i].arrival);
let leave = cars[i].leave === '-' ? '-' : formatDate(cars[i].leave);
results += `<tr>
<td>${licensee}</td> <td>${arrival}</td>
<td>${leave}</td> <td>${showStatus(cars[i])}</td>
<td class="text-right">${makeBill(cars[i])}</td> <td class="text-right"> <button data-row="${i}" onclick="showSummary(event)" data-toggle="modal" data-target="#myModal" class="btn btn-sm btn-success">Summary</button>
</td>
</tr>`;
}
document.querySelector("#parking tbody").innerHTML = results;
}
const showStatus = (car) => {
return car.isParked ? "Parked" : "Has left";
}
const changeStatus = (event) => {
cars[event.target.dataset.row].isParked = false;
}
const setLeaveTime = (event) => {
cars[event.target.dataset.row].leave = new Date(Date.now());
}
const countAvailablePlaces = (event) => {
document.querySelector('#placesCount').innerHTML = totalPlaces - cars.length;
}
const setClassForBadge = () => {
let badgeClassName = cars.length == totalPlaces ? 'badge badge-danger' : 'badge badge-success';
document.querySelector('#placesCount').setAttribute('class', badgeClassName);
}
const calculateHoursBilled = (car) => {
let arrivedAt = new Date(car.arrival).getTime();
let leftAt = new Date(car.leave).getTime();
return secondsToHours((leftAt - arrivedAt) / 1000); //duration in seconds
}
const makeBill = (car) => {
let hoursBilled = calculateHoursBilled(car);
let billValue = car.isParked ? "-" : "$" + (payFirstHour + (hoursBilled - 1) * payPerHour); return billValue; } const printSummary = (event) => { let car = cars[event.target.dataset.row]; let sumarryTable = `<table class="table table-bordered m-0"> <tr> <td class="font-weight-bold">Registration number</td> <td>${car.licensee}</td>
</tr>
<tr>
<td class="font-weight-bold">Arrival</td>
<td>${formatDate(car.arrival)}</td> </tr> <tr> <td class="font-weight-bold">Departure</td> <td>${formatDate(car.leave)}</td>
</tr>
<tr>
<td class="font-weight-bold">Billable hours</td>
<td>${calculateHoursBilled(car)}</td> </tr> <tr> <td class="font-weight-bold">Bill value</td> <td>${makeBill(car)}</td>
</tr></table>`;
document.querySelector('#modalBody').innerHTML = sumarryTable;
}
const showSummary = (event) => {
changeStatus(event);
setLeaveTime(event);
renterTable();
printSummary(event);
//Free the parking place, 3 seconds after the summary is released
setTimeout(function() {
freeSpot(event);
}, 3000);
}
const addCar = () => {
let newLicensee = document.querySelector("#carValue").value;
let newCar = {
licensee: newLicensee,
arrival: new Date(),
leave: '-',
isParked: true
}
// Add new car to the cars array
document.querySelector('#message').style.display = 'none';
if (newLicensee.length >= minLicenseeLength && cars.length < totalPlaces) {
cars.unshift(newCar);
} else {
if (newLicensee.length < minLicenseeLength) {
document.querySelector('#message').style.display = 'block';
}
}
if (cars.length == totalPlaces) {
document.querySelector('#carButton').setAttribute('disabled', true);
}
setClassForBadge();
//Update places count
countAvailablePlaces(event);
// Empty text box
document.querySelector("#carValue").value = '';
// Render the table
renterTable();
}
const freeSpot = (event) => {
cars.splice(event.target.dataset.row, 1);
setClassForBadge();
if (cars.length == totalPlaces) {
document.querySelector('#carButton').setAttribute('disabled');
} else {
document.querySelector('#carButton').removeAttribute('disabled');
}
// Render Table again after delete
renterTable();
//Update places count
countAvailablePlaces(event);
}
// Add new car to the array
addCarButton.addEventListener('click', addCar);
// Render Table
renterTable();
//Show places count at page load
countAvailablePlaces(event);
#addForm {
position: relative;
}
#message {
display: none;
position: relative;
font-size: 10px;
position: absolute;
}
#placesCount {
font-size: 90%;
}
#parking th,
#parking td {
white-space: nowrap;
font-size: 14px;
}
#myModal .font-weight-bold {
font-weight: 500 !important;
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<div class="container">
<div class="card my-3">
<div class="card-header px-3 d-flex">
<h5 class="m-0">Parking management</h5>
<div class="ml-auto">
<span id="placesCount" class="badge badge-success"></span> available places
</div>
</div>
<div class="card-body p-0">
<div class="input-group p-2" id="addForm">
<input type="text" class="form-control" id="carValue" placeholder="Registration number">
<div class="input-group-append">
<button id="carButton" class="btn btn-sm btn-success">Park car</button>
</div>
<p id="message" class="text-danger m-0">Registration number invalid</p>
</div>
<div class="table-responsive">
<table id="parking" class="table table-striped m-0">
<thead>
<tr>
<th>Registration no</th>
<th>Arrival</th>
<th>Departure</th>
<th>Status</th>
<th class="text-right">Bill</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="modal" id="myModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title">Sumar Parcare</h6>
<button type="button" class="close" data-dismiss="modal">×</button>
</div>
<div id="modalBody" class="modal-body"></div>
</div>
</div>
</div>
คุณพบปัญหาใด ๆ กับแอปพลิเคชันนี้รวมถึงจากจุดยืนด้านความปลอดภัยหรือไม่?
ขอบคุณ!
คำตอบ
ต้องการconstมากกว่าและlet varเมื่อคุณประกาศตัวแปรด้วยแสดงletว่าคุณกำลังระบุให้ผู้อ่านโค้ดทราบว่าตัวแปรนั้นอาจถูกกำหนดใหม่ได้ตลอดเวลา เมื่อการกำหนดใหม่ไม่จำเป็นสำหรับรหัสในการทำงานสิ่งนี้จะส่งผลให้เกิดค่าใช้จ่ายในการรับรู้ที่ไม่พึงปรารถนา - อาจเป็นความกังวลอยู่ตลอดเวลาในใจ "ชื่อตัวแปรนี้อาจถูกกำหนดใหม่ได้ตลอดเวลาดังนั้นสิ่งที่หมายถึงตอนนี้ อาจไม่ใช่สิ่งที่กำหนดไว้ในตอนแรก * ดูhttps://softwareengineering.stackexchange.com/questions/278652/how-much-should-i-be-using-let-vs-const-in-es6
ดีที่สุดที่จะหลีกเลี่ยงvarโดยสิ้นเชิงมันมีปัญหามากมายเกินกว่าที่จะใช้งานได้ในปัจจุบัน - ที่สำคัญที่สุดคือมีขอบเขตฟังก์ชันที่ไม่ใช้งานง่ายแทนที่จะเป็นขอบเขตการบล็อก
การเว้นระยะห่างของสตริงเมื่อคุณต้องการเพิ่มจุดเริ่มต้นของสตริงด้วยอักขระนำหน้าเพื่อทำให้สตริงมีความยาวเฉพาะ (เช่นที่นี่9และ10ถึง09และ10ตามลำดับ) วิธีที่เหมาะสมที่สุดในการใช้คือpadStart
ตัวอักษรแม่แบบที่ดีเมื่อคุณจำเป็นต้องสอดแทรกหลายตัวแปรเป็นสตริง - ' + someVar + 'พวกเขากำลังมักจะดีกว่าที่จะรูปแบบของ
เมื่อพิจารณาถึงเคล็ดลับ 3 ข้อข้างต้นformatDateฟังก์ชันนี้สามารถปรับเปลี่ยนเป็น:
const formatDate = (date) => {
const hoursMilitary = date.getHours();
const minutesToDisplay = String(date.getMinutes()).padStart('0', 2);
const ampm = hoursMilitary >= 12 ? 'PM' : 'AM';
const hoursToDisplay = (hoursMilitary % 12) || 12;
const strTime = `${hoursToDisplay}:${minutesToDisplay} ${ampm}`; return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()} ${strTime}`;
}
secondsToHoursสามารถทำให้กระชับมากขึ้นและไม่ต้องกำหนดพารามิเตอร์ใหม่:
const secondsToHours = d => Math.ceil(d / 3600);
/จะบังคับให้ไม่ใช่ตัวเลขเป็นตัวเลข คุณไม่จำเป็นต้องโทรNumberในdครั้งแรก
renterTableการปรับปรุงมากมายสามารถทำได้ที่นี่ ประการแรกชื่อฟังก์ชั่นไม่ได้ใช้งานง่าย - สิ่งที่เรียกว่าrenterTable(นาม) ทำอะไร? เป็นตัวแปรที่มี HTMLTableElement หรือไม่ มันสร้างตารางและส่งคืนหรือไม่? ไม่มันแสดงผลตารางใน DOM อีกครั้งโดยให้ข้อมูลในcarsอาร์เรย์ ลองเรียกมันrenderTableหรือrenderRenterTable(กริยา) แทน
การสร้าง HTML โดยการเชื่อมต่ออินพุตของผู้ใช้ไม่ปลอดภัยคุณมี:
results += `<tr>
<td>${licensee}</td>
<td>${arrival}</td> <td>${leave}</td>
<td>${showStatus(cars[i])}</td> <td class="text-right">${makeBill(cars[i])}</td>
<td class="text-right">
<button data-row="${i}" onclick="showSummary(event)" data-toggle="modal" data-target="#myModal" class="btn btn-sm btn-success">Summary</button>
</td>
</tr>`;
}
document.querySelector("#parking tbody").innerHTML = results;
สิ่งนี้จะช่วยให้สามารถใช้รหัสได้ตามอำเภอใจ ลองนึกดูว่ามีคนพูดว่า:
ลองเสียบป้ายทะเบียนต่อไปนี้ตอนจดทะเบียนรถคุณจะไม่เชื่อว่าจะเกิดอะไรขึ้นต่อไป!
<img src onerror='alert("evil")'>
ซึ่งalert("evil")สามารถแทนที่ด้วยอะไรก็ได้ที่ฟิชเชอร์ต้องการ จากนั้นผู้ใช้ที่ไม่ฉลาดเกินไปบางคนอาจตกหลุมรักและได้รับข้อมูลการเข้าสู่ระบบ / เงินในบัญชี / ฯลฯ ที่ถูกบุกรุก
สิ่งนี้สามารถแก้ไขได้โดยตรวจสอบให้แน่ใจว่าlicenseeมีเฉพาะอักขระที่ถูกต้องก่อนเช่นเทิร์น:
let newLicensee = document.querySelector("#carValue").value;
if (!/^[a-z\d]+$/.test(newLicensee)) {
// display error message to user: license invalid
return;
}
แต่นั่นยังเป็นเพียงงานปะ หากสคริปต์ได้รับการพัฒนาในอนาคตฉันกลัวว่าจะไม่ทำความสะอาดอินพุตโดยไม่ได้ตั้งใจก่อนที่จะใส่ลงใน DOM เมื่อเกี่ยวข้องกับการป้อนข้อมูลของผู้ใช้ฉันต้องการหลีกเลี่ยงการแก้ไขสตริงด้วย HTML ทั้งหมดและแทนที่จะใส่textContentลงในองค์ประกอบหลังจากใส่องค์ประกอบลงในเอกสารเช่น:
const tr = document.querySelector("#parking tbody").appendChild(document.createElement('tr'));
tr.innerHTML = `
<td></td>
<td></td>
<td></td>
...
`;
tr.children[0].textContent = licensee;
tr.children[1].textContent = arrival;
tr.children[2].textContent = leave;
// ...
หรือที่ดีกว่านั้นสำหรับโปรเจ็กต์ขนาดใหญ่ให้พิจารณาใช้กรอบการทำงานที่ช่วยให้การแก้ไขอินพุตลงใน DOM อย่างกระชับเช่น React:
const CarRow = (carInfo) => (
<tr>
<td>{carInfo.licensee}</td>
<td>{carInfo.arrival}</td>
<td>{carInfo.leave}</td>
...
ทั้งหมดข้างต้นยังใช้กับสิ่งsumarryTableที่มีความคงทนต่อ HTML แบบเรียงต่อกันแบบเดียวกัน
การสะกดการสะกดมีความสำคัญในการเขียนโปรแกรม - การสะกดชื่อตัวแปรอย่างถูกต้องช่วยป้องกันข้อบกพร่อง เรียกมันsummaryTableแทน. นอกจากนี้คำว่า "ผู้รับใบอนุญาต" ยังหมายถึงบุคคลที่ถือใบอนุญาต แต่ตัวแปรของคุณหมายถึงสตริงป้ายทะเบียน ลองเรียกมันlicenseIDแทนlicensee; มันจะสับสนน้อยลง
หลีกเลี่ยงinnerHTMLด้วยเหตุผลที่คล้ายกันข้างต้นให้ใช้เฉพาะinnerHTMLเมื่อคุณต้องการแทรกมาร์กอัป HTML หากคุณต้องการเพียงแค่แทรกข้อความลงในองค์ประกอบให้ใช้textContent; จะปลอดภัยกว่าเร็วกว่าและไม่มีปัญหากับอักขระที่มีความหมายพิเศษใน HTML นี้:
document.querySelector('#placesCount').innerHTML = totalPlaces - cars.length;
เป็นไปได้
document.querySelector('#placesCount').textContent = totalPlaces - cars.length;
(สามารถบันทึกการอ้างอิง#placesCountแทนการเลือกใหม่ทุกครั้งที่เรียกใช้ฟังก์ชัน)
หลีกเลี่ยงตัวจัดการแบบอินไลน์ภายในมาร์กอัปแถวคุณมี:
<button data-row="${i}" onclick="showSummary(event)" data-toggle="modal" data-target="#myModal" class="btn btn-sm btn-success">Summary</button>
ตัวจัดการแบบอินไลน์มีปัญหามากเกินไปที่จะใช้ในโค้ดสมัยใหม่ พวกเขาต้องการมลพิษทั่วโลกและมีห่วงโซ่ขอบเขตที่บ้าคลั่งรวมถึงประเด็นอื่น ๆ แนบตัวฟังเหตุการณ์อย่างถูกต้องโดยใช้ JavaScript แทน แนบ Listener เข้ากับแต่ละปุ่มเมื่อใส่ปุ่มเข้าไปใน DOM หรืออาจเป็นตัวเลือกที่ดีกว่าให้ใช้การมอบหมายเหตุการณ์ ดูความคิดเห็นด้านล่างด้วย:
table.addEventListener('click', (event) => {
if (!event.target.matches('button')) {
return;
}
// I renamed this from "row" to "rowIndex" to make the variable name more precise;
// it's not an actual row, it's only an index
const { rowIndex } = event.target.dataset;
// Now that the rowIndex has been extracted, grab the car,
// then pass the car along instead of extracting it inside each function:
const car = cars[rowIndex];
changeStatus(car);
setLeaveTime(car);
renderRenterTable();
printSummary(car);
//Free the parking place, 3 seconds after the summary is released
setTimeout(function() {
freeSpot(rowIndex);
}, 3000);
});
ชอบสัญกรณ์จุดเมื่อเป็นไปได้มันกระชับและอ่านง่ายกว่า - คุณมี
document.querySelector('#placesCount').setAttribute('class', badgeClassName);
// ...
if (cars.length == totalPlaces) {
document.querySelector('#carButton').setAttribute('disabled');
} else {
document.querySelector('#carButton').removeAttribute('disabled');
}
สิ่งเหล่านี้สามารถเปลี่ยนเป็น:
document.querySelector('#placesCount').className = badgeClassName;
// ...
document.querySelector('#carButton').disabled = cars.length === totalPlaces;
(อย่าลืมมักจะใช้=== แทน == ; ==มีกฎแปลกที่ผู้อ่านสคริปต์ไม่ควรจะต้องทำความเข้าใจเพื่อที่จะเข้าใจตรรกะการดำเนินการ)
ไวยากรณ์สมัยใหม่มีการใช้ไวยากรณ์สมัยใหม่และวิธีการที่ทันสมัยในซอร์สโค้ดซึ่งเป็นสิ่งที่ดี - ทำให้รหัสที่สะอาดกระชับและอ่านได้ สำหรับรหัสการผลิตให้ใช้Babelและ polyfills เพื่อย้ายซอร์สลงไปที่ ES5 โดยอัตโนมัติเพื่อให้เบราว์เซอร์ที่ล้าสมัยเช่น IE สามารถรันโค้ดได้เช่นกัน