MENU

マニュアルページの計算支援ツール最新版

バックアップとして。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>計算支援</title>
  <link rel="stylesheet" href="../style.css">
  <script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/jspdf@2.5.1/dist/jspdf.umd.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/jspdf-autotable@3.5.28/dist/jspdf.plugin.autotable.min.js"></script>
  <script src="https://◯◯◯◯◯.github.io/4masumemo/NotoSansJP-Regular-base64_2.js"></script>

  <style>
    .calculator {
      max-width: 400px;
      margin: 40px auto;
      padding: 20px;
      border: 2px solid #ccc;
      border-radius: 10px;
      background: #f9f9f9;
      text-align: center;
    }
    .calculator input {
      width: 80px;
      margin: 5px;
      padding: 8px;
      font-size: 1rem;
    }
    .calculator button {
      margin: 5px;
      padding: 8px 16px;
      font-size: 1rem;
    }
    .result {
      margin-top: 20px;
      font-size: 1.2rem;
      font-weight: bold;
    }
    .history {
      margin-top: 40px;
      max-width: 500px;
      margin-left: auto;
      margin-right: auto;
    }
    .history-entry {
      border: 1px solid #ddd;
      padding: 10px;
      margin-bottom: 10px;
      background: #fff;
      position: relative;
    }
    .history-entry textarea {
      width: 100%;
      margin-top: 8px;
      padding: 6px;
      resize: vertical;
    }
    .delete-btn {
      position: absolute;
      top: 8px;
      right: 8px;
      background-color: #ff5555;
      color: white;
      border: none;
      padding: 4px 8px;
      cursor: pointer;
      font-size: 0.8rem;
    }
  </style>
</head>
<body>
<div id="header-placeholder"></div>
<div class="page-wrapper">
  <aside id="sidebar"></aside>
  <div class="content-wrapper">
    <div class="main">
      <div class="calculator">
        <h2>計算支援ツール</h2>
        <input type="number" id="num1" placeholder="数値1">
        <input type="number" id="num2" placeholder="数値2"><br>
        <button onclick="setOperator('+')">+</button>
        <button onclick="setOperator('-')">−</button>
        <button onclick="setOperator('*')">×</button>
        <button onclick="setOperator('/')">÷</button>
        <button onclick="calculate()">=</button>
        <div class="result" id="result">答え: </div>
      </div>

      <div class="history" id="history">
        <h3 style="display:flex;justify-content:space-between;align-items:center;">
          計算履歴
        </h3>

        <div id="history-entries"></div>
        <div style="text-align:center; margin-top:10px;">
          <button onclick="clearAllHistory()" style="font-size:0.9rem;padding:4px 10px;background:#aaa;color:#fff;border:none;border-radius:4px;cursor:pointer;">
            すべて削除
          </button>
          <button onclick="exportHistoryAsHTML()" style="font-size:0.9rem;padding:4px 10px;background:#4CAF50;color:#fff;border:none;border-radius:4px;cursor:pointer; margin-left: 10px;">
            HTML出力
          </button>
          <button onclick="exportHistoryAsPDF()" style="font-size:0.9rem;padding:4px 10px;background:#2196F3;color:#fff;border:none;border-radius:4px;cursor:pointer; margin-left: 10px;">
          PDF出力
          </button>
        </div>
      </div>

    </div>
  </div>
</div>

<script src="../load_header.js"></script>
<script>

let currentOperator = null;

  window.addEventListener("DOMContentLoaded", () => {
    renderAllHistory();
  });

function calculate() {
  const num1 = parseFloat(document.getElementById("num1").value);
  const num2 = parseFloat(document.getElementById("num2").value);
  const resultBox = document.getElementById("result");

  if (!currentOperator) {
    resultBox.textContent = "演算子(+、−、×、÷)を先に選択してください";
    return;
  }

  if (isNaN(num1) || isNaN(num2)) {
    resultBox.textContent = "数値を入力してください";
    return;
  }

  let result;
  switch (currentOperator) {
    case '+': result = num1 + num2; break;
    case '-': result = num1 - num2; break;
    case '*': result = num1 * num2; break;
    case '/': result = (num2 === 0) ? "0で割れません" : (num1 / num2); break;
  }

  resultBox.textContent = "答え: " + formatNumber(result);

  // 結果を num1 にセットし、num2はリセット
  if (!isNaN(result)) {
    document.getElementById("num1").value = result;
    document.getElementById("num2").value = "";
    document.getElementById("num2").focus();
  }

  const entry = {
    id: Date.now(),
    formula: `${num1} ${currentOperator} ${num2}`,
    result: result,
    comment: ""
  };

  currentOperator = null;
  saveEntry(entry);
  renderAllHistory();
}


function renderAllHistory() {
  const container = document.getElementById("history-entries");
  container.innerHTML = "";

  const historyArray = JSON.parse(localStorage.getItem("calcHistory")) || [];

historyArray.slice().reverse().forEach(entry => {
  const div = document.createElement("div");
  div.className = "history-entry";
  div.dataset.id = entry.id;

  div.innerHTML = `
    <button class="delete-btn" onclick="deleteEntry(${entry.id})">削除</button>
    <div>計算式: ${entry.formula} = ${formatNumber(entry.result)}</div>
    <textarea placeholder="コメントを入力..." rows="2">${entry.comment || ""}</textarea>
  `;

  const textarea = div.querySelector("textarea");
  textarea.addEventListener("input", () => {
    updateComment(entry.id, textarea.value);
  });

  container.appendChild(div);
});

  // ← ここが「履歴出力後」です!
  // SortableJS によるドラッグ操作の有効化
  Sortable.create(container, {
    animation: 150,
    onEnd: saveSortedOrder
  });
}

  function saveEntry(entry) {
    const historyArray = JSON.parse(localStorage.getItem("calcHistory")) || [];
    historyArray.push(entry);
    localStorage.setItem("calcHistory", JSON.stringify(historyArray));
  }

  function updateComment(id, newComment) {
    let historyArray = JSON.parse(localStorage.getItem("calcHistory")) || [];
    historyArray = historyArray.map(entry =>
      entry.id === id ? { ...entry, comment: newComment } : entry
    );
    localStorage.setItem("calcHistory", JSON.stringify(historyArray));
  }

  function deleteEntry(id) {
    let historyArray = JSON.parse(localStorage.getItem("calcHistory")) || [];
    historyArray = historyArray.filter(entry => entry.id !== id);
    localStorage.setItem("calcHistory", JSON.stringify(historyArray));
    renderAllHistory();
  }

  function clearAllHistory() {
    if (confirm("すべての履歴を削除しますか?")) {
      localStorage.removeItem("calcHistory");
      renderAllHistory();
    }
  }

function saveSortedOrder() {
  const container = document.getElementById("history-entries");
  const newOrder = Array.from(container.children).map(div => parseInt(div.dataset.id));
  let historyArray = JSON.parse(localStorage.getItem("calcHistory")) || [];

  const reordered = newOrder.reverse().map(id => historyArray.find(entry => entry.id === id));
  localStorage.setItem("calcHistory", JSON.stringify(reordered));
}

function setOperator(op) {
  const num1 = parseFloat(document.getElementById("num1").value);
  const num2 = parseFloat(document.getElementById("num2").value);
  const resultBox = document.getElementById("result");

  // 両方の数値が入力されているなら即座に計算
  if (!isNaN(num1) && !isNaN(num2)) {
    currentOperator = op;
    calculate(); // 演算子を記憶してから計算
    return;
  }

  // 数値1だけ入力されている場合は演算子だけ記憶
  if (!isNaN(num1) && isNaN(num2)) {
    currentOperator = op;
    resultBox.textContent = `演算子「${op}」を記憶しました。数値2を入力してください`;
    return;
  }

  // それ以外(num1すらない)
  resultBox.textContent = "数値1を入力してください";
}

function exportHistoryAsHTML() {
  const historyArray = JSON.parse(localStorage.getItem("calcHistory")) || [];

  if (historyArray.length === 0) {
    alert("履歴がありません。");
    return;
  }

  let html = `<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>計算履歴</title>
  <style>
    body { font-family: sans-serif; line-height: 1.6; padding: 20px; }
    .entry { margin-bottom: 20px; border-bottom: 1px solid #ccc; padding-bottom: 10px; }
    .formula { font-weight: bold; }
    .comment { color: #555; }
  </style>
</head>
<body>
  <h1>計算履歴</h1>\n`;

  historyArray.forEach(entry => {
    html += `<div class="entry">
      <div>計算式: ${entry.formula} = ${formatNumber(entry.result)}</div>\n`;
    if (entry.comment && entry.comment.trim() !== "") {
      html += `<div class="comment">コメント:${entry.comment}</div>\n`;
    }
    html += `</div>\n`;
  });

  html += "</body>\n</html>";

  const blob = new Blob([html], { type: "text/html" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = "計算履歴.html";
  a.click();
  URL.revokeObjectURL(url);
}

async function exportHistoryAsPDF() {
  const { jsPDF } = window.jspdf;
  const doc = new jsPDF({ unit: 'pt', format: 'a4' });

  // ✅ 日本語フォントの登録
  doc.addFileToVFS("NotoSansJP-Regular.ttf", window.NotoSansJP_Base64);
  doc.addFont("NotoSansJP-Regular.ttf", "NotoSansJP", "normal");
  doc.setFont("NotoSansJP");

  doc.setFontSize(16);
  doc.text("計算履歴", 40, 40);

  const historyArray = JSON.parse(localStorage.getItem("calcHistory")) || [];
  if (historyArray.length === 0) {
    alert("履歴がありません。");
    return;
  }

  const rows = historyArray.map(entry => [
    entry.formula,
    formatNumber(entry.result),
    entry.comment || ""
  ]);

  doc.autoTable({
    head: [["計算式", "答え", "コメント"]],
    body: rows,
    startY: 60,
    styles: {
      font: "NotoSansJP",
      fontSize: 10,
      cellPadding: 8,
      lineColor: [0, 0, 0],
      lineWidth: 0.2,
      valign: 'top'
    },
    headStyles: {
      fillColor: [33, 150, 243],
      textColor: [255, 255, 255],
      halign: 'center'
    },
    columnStyles: {
      0: { cellWidth: doc.internal.pageSize.getWidth() / 3 - 20 },
      1: { cellWidth: doc.internal.pageSize.getWidth() / 6, halign: 'right' },  // 答え(右揃えにする!)
      2: { cellWidth: doc.internal.pageSize.getWidth() / 2 - 40 }
    },
    margin: { left: 40, right: 40 }
  });

  doc.save("計算履歴.pdf");
}

function formatNumber(val) {
  // 数値なら3桁区切り、それ以外("0で割れません"など)はそのまま
  return (typeof val === 'number')
    ? val.toLocaleString('ja-JP')
    : val;
}

</script>
</body>
</html>
よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

目次