ระบบจัดการที่จอดรถใน JavaScript

Oct 20 2020

สมมติฐาน

มีที่จอดรถแบบเสียค่าบริการโดยมีอัตราดังต่อไปนี้: $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">&times;</button>
      </div>
      <div id="modalBody" class="modal-body"></div>
    </div>
  </div>
</div>

คุณพบปัญหาใด ๆ กับแอปพลิเคชันนี้รวมถึงจากจุดยืนด้านความปลอดภัยหรือไม่?

ขอบคุณ!

คำตอบ

5 CertainPerformance Oct 21 2020 at 00:50

ต้องการ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 สามารถรันโค้ดได้เช่นกัน