Hệ thống quản lý bãi đậu xe bằng JavaScript
Giả thuyết
Có một bãi đậu xe trả phí với mức phí như sau: $1 for the first hour and $0,5 cho mỗi giờ tiếp theo. Sức chứa của bãi đậu xe là 10 chỗ.
Cần thiết
Tạo hệ thống quản lý hoạt động cho Bãi đậu xe với các đặc điểm sau:
- Bất kỳ khoảng thời gian đỗ xe nào cũng được làm tròn, tính đến giờ gần nhất.
- Khi một chiếc xe mới vào bãi đỗ, hệ thống đỗ xe sẽ lấy số đăng ký của chiếc xe đó.
- Khi ô tô rời khỏi bãi đậu xe, hệ thống sẽ đưa ra một bản tóm tắt, tùy thuộc vào thời gian lưu trú. Thông tin nào bạn coi là cần thiết như một phần của bản tóm tắt?
- Bất kỳ khách hàng nào cũng có thể xem danh sách các xe đang đậu trong bãi xe tại một thời điểm nhất định.
Yêu cầu kỹ thuật
- Việc triển khai sẽ được thực hiện bằng JavaScript, tất cả dữ liệu được lưu trữ trong bộ nhớ, không cần lưu trữ vĩnh viễn.
- Viết mã sạch.
- Tạo GUI đơn giản cho phép nhập và đọc dữ liệu từ máy tính; hiển thị dữ liệu trên cùng một trang.
Giải pháp
Tôi đã sử dụng Bootstrap 4 để thẩm mỹ. Bản thân tập lệnh chỉ sử dụng JavaScript. jQuery được bao gồm cho hoạt động chính xác của 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>
Bạn có thấy bất kỳ sự cố nào với ứng dụng này, kể cả từ quan điểm bảo mật không?
Cảm ơn!
Trả lời
Thíchconst hơn letvà var. Khi bạn khai báo một biến với let, bạn đang chỉ cho bất kỳ người đọc mã nào biết rằng biến đó có thể được gán lại bất kỳ lúc nào. Khi việc gán lại không thực sự cần thiết để mã hoạt động, điều này dẫn đến chi phí nhận thức không mong muốn - đó có thể là nỗi lo thường trực trong tâm trí của một người "Tên biến này có thể được gán lại bất kỳ lúc nào, vì vậy nó đề cập đến cái gì bây giờ có thể không phải là những gì nó được chỉ định ban đầu. * Xemhttps://softwareengineering.stackexchange.com/questions/278652/how-much-should-i-be-using-let-vs-const-in-es6
Tốt nhất là nên tránh varhoàn toàn, nó có quá nhiều vấn đề đáng để sử dụng ngày nay - quan trọng nhất là nó có phạm vi chức năng không trực quan thay vì phạm vi khối.
Phần đệm chuỗi Khi bạn muốn đệm phần đầu của một chuỗi bằng một số ký tự đứng đầu để làm cho chuỗi có độ dài cụ thể (ví dụ: đây, 9và 10tới 09và 10tương ứng), phương pháp thích hợp nhất để sử dụng làpadStart
Các ký tự mẫu rất tuyệt khi bạn cần nội suy nhiều biến thành một chuỗi - chúng thường thích hợp hơn với kiểu của ' + someVar + '.
Xem xét 3 mẹo ở trên, formatDatehàm có thể được cấu trúc lại thành:
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 có thể ngắn gọn hơn nhiều mà không cần gán lại tham số:
const secondsToHours = d => Math.ceil(d / 3600);
/sẽ ép buộc không phải số thành số; bạn không cần phải gọi Numbervào dđầu tiên.
renterTableRất nhiều cải tiến có thể được thực hiện ở đây. Đầu tiên, tên hàm không hoàn toàn trực quan - một thứ được gọi là renterTable(danh từ) làm gì? Nó có phải là một biến chứa HTMLTableElement không? Nó có tạo một bảng và trả về nó không? Không, nó hiển thị bảng trong DOM với dữ liệu trong carsmảng. Thay vào đó, hãy cân nhắc gọi nó renderTablehoặc renderRenterTable(động từ).
Tạo HTML bằng cách nối đầu vào của người dùng là không an toàn Bạn có:
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;
Điều này sẽ cho phép thực thi mã tùy ý. Hãy tưởng tượng nếu ai đó nói:
Hãy thử cắm biển số sau khi đăng ký xe ô tô, bạn sẽ không tin điều gì xảy ra tiếp theo!
<img src onerror='alert("evil")'>
nơi alert("evil")có thể được thay thế bằng bất cứ thứ gì mà kẻ lừa đảo muốn. Sau đó, một số người dùng không quá thông minh có thể rơi vào tình trạng này và bị xâm phạm thông tin đăng nhập / tiền tài khoản / v.v.
Điều này có thể được khắc phục bằng cách đảm bảo licenseechỉ chứa các ký tự hợp lệ trước, ví dụ: lần lượt:
let newLicensee = document.querySelector("#carValue").value;
if (!/^[a-z\d]+$/.test(newLicensee)) {
// display error message to user: license invalid
return;
}
Nhưng đó vẫn chỉ là một công việc vá lỗi. Nếu tập lệnh được phát triển trong tương lai, tôi sẽ sợ vô tình không làm sạch đầu vào trước khi chèn nó vào DOM. Khi quá trình nhập của người dùng có liên quan, tôi muốn tránh nội suy chuỗi hoàn toàn bằng HTML và thay vào đó chèn textContentphần tử vào phần tử sau khi phần tử được đưa vào tài liệu, ví dụ:
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;
// ...
Hoặc, thậm chí tốt hơn , đối với các dự án lớn hơn, hãy xem xét sử dụng một khuôn khổ cho phép nội suy ngắn gọn đầu vào vào DOM, như React:
const CarRow = (carInfo) => (
<tr>
<td>{carInfo.licensee}</td>
<td>{carInfo.arrival}</td>
<td>{carInfo.leave}</td>
...
Tất cả những điều trên cũng áp dụng cho sumarryTable, có cùng loại khả năng lưu hóa HTML được nối
Chính tả Các vấn đề chính tả trong lập trình - tên biến được viết đúng chính tả giúp ngăn ngừa lỗi. Hãy gọi nó summaryTablethay thế. Ngoài ra, từ "người được cấp phép" đề cập đến người có giấy phép, nhưng biến của bạn đề cập đến chuỗi biển số xe. Cân nhắc gọi nó licenseIDthay vì licensee; nó sẽ ít khó hiểu hơn.
TránhinnerHTML Vì những lý do tương tự như trên, chỉ sử dụng innerHTMLkhi bạn cần chèn đánh dấu HTML. Nếu bạn chỉ muốn chèn văn bản vào một phần tử, hãy sử dụng textContent; nó sẽ an toàn hơn, nhanh hơn và không gặp vấn đề với các ký tự có ý nghĩa đặc biệt trong HTML. Điều này:
document.querySelector('#placesCount').innerHTML = totalPlaces - cars.length;
có thể
document.querySelector('#placesCount').textContent = totalPlaces - cars.length;
(Cũng có thể lưu tham chiếu đến #placesCountthay vì chọn lại nó mỗi khi hàm được gọi)
Tránh các trình xử lý nội dòng Bên trong đánh dấu hàng, bạn có:
<button data-row="${i}" onclick="showSummary(event)" data-toggle="modal" data-target="#myModal" class="btn btn-sm btn-success">Summary</button>
Trình xử lý nội tuyến có quá nhiều vấn đề đáng sử dụng trong mã hiện đại. Họ yêu cầu ô nhiễm toàn cầu và có một chuỗi phạm vi điên rồ, trong số các vấn đề khác. Đính kèm trình nghe sự kiện đúng cách bằng cách sử dụng JavaScript. Gắn trình nghe vào mỗi nút khi nút được chèn vào DOM hoặc, có thể là một tùy chọn tốt hơn, sử dụng ủy quyền sự kiện. Xem cả bình luận bên dưới:
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);
});
Thích ký hiệu dấu chấm khi có thể, nó ngắn gọn hơn và dễ đọc hơn - bạn có
document.querySelector('#placesCount').setAttribute('class', badgeClassName);
// ...
if (cars.length == totalPlaces) {
document.querySelector('#carButton').setAttribute('disabled');
} else {
document.querySelector('#carButton').removeAttribute('disabled');
}
Chúng có thể được thay đổi thành:
document.querySelector('#placesCount').className = badgeClassName;
// ...
document.querySelector('#carButton').disabled = cars.length === totalPlaces;
(hãy nhớ luôn sử dụng === thay vì == ; ==có những quy tắc kỳ lạ mà người đọc kịch bản không cần phải hiểu để nắm được logic đang được triển khai)
Cú pháp hiện đại Cú pháp hiện đại và các phương pháp hiện đại đang được sử dụng trong mã nguồn, điều này rất tốt - nó tạo ra mã rõ ràng, ngắn gọn, dễ đọc. Đối với mã sản xuất, hãy sử dụng Babel và polyfills để tự động chuyển nguồn xuống ES5 để các trình duyệt lỗi thời như IE cũng có thể chạy mã.