<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>タスク管理プロトタイプ</title>
<style>
body { font-family: sans-serif; margin: 20px; }
table { border-collapse: collapse; width: 100%; margin-top: 20px; }
th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
button { margin: 5px; }
</style>
<!-- Excelインポート用:SheetJS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
</head>
<body>
<h1>タスク管理プロトタイプ</h1>
<!-- タスクの登録エリア -->
<h2>タスク登録</h2>
<form id="taskForm">
<label>タイトル:<input type="text" id="title" required></label><br>
<label>説明:<input type="text" id="description"></label><br>
<label>ステータス:
<select id="status">
<option value="未着手">未着手</option>
<option value="進行中">進行中</option>
<option value="完了">完了</option>
</select>
</label>
<button type="submit">タスク追加</button>
</form>
<!-- Excelファイルからインポート -->
<h2>Excelインポート</h2>
<input type="file" id="excelFile" accept=".xlsx, .xls" />
<button id="importExcelBtn">Excelからインポート</button>
<!-- エクスポート -->
<h2>エクスポート</h2>
<button id="exportJsonBtn">JSON出力</button>
<button id="exportCsvBtn">CSV出力</button>
<!-- タスク一覧表示 -->
<h2>タスク一覧</h2>
<table id="taskTable">
<thead>
<tr>
<th>ID</th>
<th>タイトル</th>
<th>説明</th>
<th>ステータス</th>
</tr>
</thead>
<tbody>
<!-- タスクデータが動的に追加される -->
</tbody>
</table>
<script>
// 内部状態のタスクデータは配列で管理(シンプルな自動インクリメントID)
let tasks = [];
let nextTaskId = 1;
// タスクの追加関数
function addTask(title, description, status) {
const task = { id: nextTaskId++, title, description, status };
tasks.push(task);
renderTasks();
}
// タスク一覧をテーブルに描画
function renderTasks() {
const tbody = document.querySelector("#taskTable tbody");
tbody.innerHTML = "";
tasks.forEach(task => {
const tr = document.createElement("tr");
tr.innerHTML = `<td>${task.id}</td>
<td>${task.title}</td>
<td>${task.description}</td>
<td>${task.status}</td>`;
tbody.appendChild(tr);
});
}
// フォーム送信イベント:タスク追加
document.getElementById("taskForm").addEventListener("submit", function(event){
event.preventDefault();
const title = document.getElementById("title").value;
const description = document.getElementById("description").value;
const status = document.getElementById("status").value;
addTask(title, description, status);
this.reset();
});
// Excelからタスクデータのインポート(前提:1列目にタイトル、2列目に説明、3列目にステータス)
document.getElementById("importExcelBtn").addEventListener("click", function(){
const fileInput = document.getElementById("excelFile");
if (!fileInput.files.length) {
alert("Excelファイルを選択してください");
return;
}
const file = fileInput.files[0];
const reader = new FileReader();
reader.onload = function(e) {
const data = e.target.result;
// 拡張オプションを追加して文字化け対策
const workbook = XLSX.read(data, {
type: "array",
codepage: 65001, // UTF-8エンコーディングを指定
cellStyles: true,
cellNF: true,
cellDates: true
});
// シート1を選択してデータを取得
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
// オプション:raw: trueでセルの生の値を取得、header: 1で全体を配列として取得
const rows = XLSX.utils.sheet_to_json(worksheet, {
header: 1,
raw: false, // 文字列として値を取得
defval: "" // 未定義セルの代替値
});
console.log("インポートデータ:", rows); // デバッグ用
// ヘッダーがある場合は行番号を調整する
rows.forEach((row, index) => {
// 例:row[0]:タイトル, row[1]:説明, row[2]:ステータス
if (index === 0) return; // ヘッダー行をスキップ
if(row[0]) {
// データが文字化けしていないか確認
const title = String(row[0] || "");
const desc = String(row[1] || "");
const status = String(row[2] || "未着手");
console.log(`インポート行 ${index}:`, title, desc, status); // デバッグ用
addTask(title, desc, status);
}
});
};
reader.readAsArrayBuffer(file); // バイナリではなくArrayBufferとして読み込む
});
// JSON出力
document.getElementById("exportJsonBtn").addEventListener("click", function(){
if (tasks.length === 0) {
alert("出力するタスクがありません");
return;
}
const jsonStr = JSON.stringify(tasks, null, 2);
const blob = new Blob([jsonStr], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "tasks_" + new Date().toISOString().replace(/[:.-]/g, "") + ".json";
a.click();
URL.revokeObjectURL(url);
});
// CSV出力(日本語対応のカンマ区切り)
document.getElementById("exportCsvBtn").addEventListener("click", function(){
if (tasks.length === 0) {
alert("出力するタスクがありません");
return;
}
// ヘッダー行
let csv = "ID,タイトル,説明,ステータス\n";
// データ行(ダブルクォートでエスケープを確実に行う)
tasks.forEach(task => {
// ダブルクォート内のダブルクォートを二重にエスケープ
const title = task.title.replace(/"/g, '""');
const description = task.description.replace(/"/g, '""');
const status = task.status.replace(/"/g, '""');
csv += `${task.id},"${title}","${description}","${status}"\n`;
});
// 重要: CSVの先頭にUTF-8 BOMを追加
// これによりExcelが日本語を正しく認識できる
const bom = new Uint8Array([0xEF, 0xBB, 0xBF]);
// 文字列をUTF-8エンコードのUint8Arrayに変換
const encoder = new TextEncoder();
const csvData = encoder.encode(csv);
// BOMとCSVデータを結合
const combinedArray = new Uint8Array(bom.length + csvData.length);
combinedArray.set(bom, 0);
combinedArray.set(csvData, bom.length);
// 適切なMIMEタイプを指定
const blob = new Blob([combinedArray], { type: "text/csv;charset=utf-8" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "tasks_" + new Date().toISOString().replace(/[:.-]/g, "") + ".csv";
a.click();
URL.revokeObjectURL(url);
});
// 初期描画
renderTasks();
</script>
</body>
</html>
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme