Update index.html
Browse files- index.html +79 -38
index.html
CHANGED
|
@@ -399,24 +399,17 @@
|
|
| 399 |
{
|
| 400 |
id: 405, q: "49 - (y-1)^2",
|
| 401 |
a: [
|
| 402 |
-
// 基本正解: (8-y)(y+6) 及其變體
|
| 403 |
"(8-y)(y+6)", "(y+6)(8-y)",
|
| 404 |
"(6+y)(8-y)", "(8-y)(6+y)",
|
| 405 |
"(-y+8)(y+6)", "(y+6)(-y+8)",
|
| 406 |
"(-y+8)(6+y)", "(6+y)(-y+8)",
|
| 407 |
-
|
| 408 |
-
// 負負得正系列: (-y-6)(y-8)
|
| 409 |
"(-y-6)(y-8)", "(y-8)(-y-6)",
|
| 410 |
"(-6-y)(y-8)", "(y-8)(-6-y)",
|
| 411 |
"(-y-6)(-8+y)", "(-8+y)(-y-6)",
|
| 412 |
-
|
| 413 |
-
// 提出負號系列 1: -(y-8)(y+6)
|
| 414 |
"-(y-8)(y+6)", "-(y+6)(y-8)",
|
| 415 |
"-(y-8)(6+y)", "-(6+y)(y-8)",
|
| 416 |
"-(-8+y)(y+6)", "-(y+6)(-8+y)",
|
| 417 |
"-(-8+y)(6+y)", "-(6+y)(-8+y)",
|
| 418 |
-
|
| 419 |
-
// 提出負號系列 2: -(8-y)(-y-6)
|
| 420 |
"-(8-y)(-y-6)", "-(-y-6)(8-y)",
|
| 421 |
"-(8-y)(-6-y)", "-(-6-y)(8-y)"
|
| 422 |
],
|
|
@@ -476,13 +469,20 @@
|
|
| 476 |
return { q: "未知題目", category: "未知", id: pid };
|
| 477 |
};
|
| 478 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 479 |
// --- Components ---
|
| 480 |
|
| 481 |
const NameModal = ({ onSubmit }) => {
|
| 482 |
const [name, setName] = useState("");
|
| 483 |
const [roomId, setRoomId] = useState("");
|
| 484 |
|
| 485 |
-
//
|
| 486 |
useEffect(() => {
|
| 487 |
const params = new URLSearchParams(window.location.search);
|
| 488 |
const roomParam = params.get('room');
|
|
@@ -544,12 +544,12 @@
|
|
| 544 |
const TeacherDashboard = ({ onClose, allData }) => {
|
| 545 |
const [activeTab, setActiveTab] = useState('room'); // room, list, analysis
|
| 546 |
const [roomCode, setRoomCode] = useState("");
|
| 547 |
-
const [manualRoomInput, setManualRoomInput] = useState("");
|
| 548 |
-
const [roomHistory, setRoomHistory] = useState([]);
|
| 549 |
const [selectedStudent, setSelectedStudent] = useState(null);
|
| 550 |
-
const [showLargeQr, setShowLargeQr] = useState(false);
|
| 551 |
|
| 552 |
-
//
|
| 553 |
useEffect(() => {
|
| 554 |
const savedHistory = localStorage.getItem('teacher_room_history');
|
| 555 |
if (savedHistory) {
|
|
@@ -559,7 +559,7 @@
|
|
| 559 |
}
|
| 560 |
}, []);
|
| 561 |
|
| 562 |
-
//
|
| 563 |
const switchRoom = (code) => {
|
| 564 |
setRoomCode(code);
|
| 565 |
setManualRoomInput(code); // 同步更新輸入框
|
|
@@ -577,14 +577,14 @@
|
|
| 577 |
switchRoom(code);
|
| 578 |
};
|
| 579 |
|
| 580 |
-
//
|
| 581 |
const handleManualJoin = () => {
|
| 582 |
if (manualRoomInput.trim()) {
|
| 583 |
switchRoom(manualRoomInput.trim());
|
| 584 |
}
|
| 585 |
};
|
| 586 |
|
| 587 |
-
//
|
| 588 |
const getQrUrl = (size = "150x150") => {
|
| 589 |
const baseUrl = window.location.origin + window.location.pathname;
|
| 590 |
const fullUrl = `${baseUrl}?room=${roomCode}`;
|
|
@@ -854,8 +854,8 @@
|
|
| 854 |
</div>
|
| 855 |
</div>
|
| 856 |
</div>
|
| 857 |
-
|
| 858 |
-
|
| 859 |
|
| 860 |
// Teacher Login Modal
|
| 861 |
const TeacherLoginModal = ({ onClose, onLogin }) => {
|
|
@@ -1034,27 +1034,39 @@
|
|
| 1034 |
const [chestOpen, setChestOpen] = useState(false);
|
| 1035 |
const [combo, setCombo] = useState(0);
|
| 1036 |
|
| 1037 |
-
//
|
| 1038 |
const [userData, setUserData] = useState(() => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1039 |
const defaultData = {
|
| 1040 |
score: 0,
|
| 1041 |
name: "",
|
| 1042 |
-
roomId:
|
| 1043 |
-
mistakes: {},
|
| 1044 |
unlockedItems: ['title_novice', 'scroll_default'],
|
| 1045 |
equippedTitle: 'title_novice',
|
| 1046 |
equippedScroll: 'scroll_default',
|
| 1047 |
completedLevels: []
|
| 1048 |
};
|
| 1049 |
-
|
| 1050 |
-
|
| 1051 |
-
|
| 1052 |
const parsed = JSON.parse(saved);
|
| 1053 |
-
|
|
|
|
|
|
|
|
|
|
| 1054 |
}
|
| 1055 |
-
} catch (e) {
|
| 1056 |
-
console.error("Load error", e);
|
| 1057 |
}
|
|
|
|
|
|
|
| 1058 |
return defaultData;
|
| 1059 |
});
|
| 1060 |
|
|
@@ -1086,21 +1098,49 @@
|
|
| 1086 |
}
|
| 1087 |
}, []);
|
| 1088 |
|
| 1089 |
-
// ★★★
|
| 1090 |
-
|
| 1091 |
-
|
| 1092 |
-
const
|
| 1093 |
|
| 1094 |
-
//
|
| 1095 |
-
if (
|
| 1096 |
-
|
| 1097 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1098 |
}
|
| 1099 |
-
|
|
|
|
| 1100 |
|
| 1101 |
-
// Save Data
|
| 1102 |
useEffect(() => {
|
| 1103 |
-
|
|
|
|
| 1104 |
}, [userData]);
|
| 1105 |
|
| 1106 |
// --- Firebase Logic ---
|
|
@@ -1347,7 +1387,8 @@
|
|
| 1347 |
return <TeacherDashboard onClose={() => setGameState('menu')} allData={leaderboardData} />;
|
| 1348 |
}
|
| 1349 |
|
| 1350 |
-
|
|
|
|
| 1351 |
if (showTeacherLogin) return <TeacherLoginModal onClose={() => setShowTeacherLogin(false)} onLogin={() => { setShowTeacherLogin(false); setGameState('teacher_dashboard'); }} />;
|
| 1352 |
|
| 1353 |
if (gameState === 'leaderboard') {
|
|
|
|
| 399 |
{
|
| 400 |
id: 405, q: "49 - (y-1)^2",
|
| 401 |
a: [
|
|
|
|
| 402 |
"(8-y)(y+6)", "(y+6)(8-y)",
|
| 403 |
"(6+y)(8-y)", "(8-y)(6+y)",
|
| 404 |
"(-y+8)(y+6)", "(y+6)(-y+8)",
|
| 405 |
"(-y+8)(6+y)", "(6+y)(-y+8)",
|
|
|
|
|
|
|
| 406 |
"(-y-6)(y-8)", "(y-8)(-y-6)",
|
| 407 |
"(-6-y)(y-8)", "(y-8)(-6-y)",
|
| 408 |
"(-y-6)(-8+y)", "(-8+y)(-y-6)",
|
|
|
|
|
|
|
| 409 |
"-(y-8)(y+6)", "-(y+6)(y-8)",
|
| 410 |
"-(y-8)(6+y)", "-(6+y)(y-8)",
|
| 411 |
"-(-8+y)(y+6)", "-(y+6)(-8+y)",
|
| 412 |
"-(-8+y)(6+y)", "-(6+y)(-8+y)",
|
|
|
|
|
|
|
| 413 |
"-(8-y)(-y-6)", "-(-y-6)(8-y)",
|
| 414 |
"-(8-y)(-6-y)", "-(-6-y)(8-y)"
|
| 415 |
],
|
|
|
|
| 469 |
return { q: "未知題目", category: "未知", id: pid };
|
| 470 |
};
|
| 471 |
|
| 472 |
+
// --- Storage Key Helper ---
|
| 473 |
+
// 新增:根據房間 ID 取得對應的 localStorage key
|
| 474 |
+
const getStorageKey = (roomId) => {
|
| 475 |
+
// 如果有房間代碼,使用專屬的 key;如果是空的(公開區),使用舊的 key 以保持相容性
|
| 476 |
+
return roomId ? `factoring_save_room_${roomId}` : 'factoring_game_data_v5';
|
| 477 |
+
};
|
| 478 |
+
|
| 479 |
// --- Components ---
|
| 480 |
|
| 481 |
const NameModal = ({ onSubmit }) => {
|
| 482 |
const [name, setName] = useState("");
|
| 483 |
const [roomId, setRoomId] = useState("");
|
| 484 |
|
| 485 |
+
// 檢查網址參數是否有 room
|
| 486 |
useEffect(() => {
|
| 487 |
const params = new URLSearchParams(window.location.search);
|
| 488 |
const roomParam = params.get('room');
|
|
|
|
| 544 |
const TeacherDashboard = ({ onClose, allData }) => {
|
| 545 |
const [activeTab, setActiveTab] = useState('room'); // room, list, analysis
|
| 546 |
const [roomCode, setRoomCode] = useState("");
|
| 547 |
+
const [manualRoomInput, setManualRoomInput] = useState("");
|
| 548 |
+
const [roomHistory, setRoomHistory] = useState([]);
|
| 549 |
const [selectedStudent, setSelectedStudent] = useState(null);
|
| 550 |
+
const [showLargeQr, setShowLargeQr] = useState(false);
|
| 551 |
|
| 552 |
+
// 載入歷史紀錄
|
| 553 |
useEffect(() => {
|
| 554 |
const savedHistory = localStorage.getItem('teacher_room_history');
|
| 555 |
if (savedHistory) {
|
|
|
|
| 559 |
}
|
| 560 |
}, []);
|
| 561 |
|
| 562 |
+
// 切換房間並保存到歷史
|
| 563 |
const switchRoom = (code) => {
|
| 564 |
setRoomCode(code);
|
| 565 |
setManualRoomInput(code); // 同步更新輸入框
|
|
|
|
| 577 |
switchRoom(code);
|
| 578 |
};
|
| 579 |
|
| 580 |
+
// 手動加入房間
|
| 581 |
const handleManualJoin = () => {
|
| 582 |
if (manualRoomInput.trim()) {
|
| 583 |
switchRoom(manualRoomInput.trim());
|
| 584 |
}
|
| 585 |
};
|
| 586 |
|
| 587 |
+
// 產生帶參數的網址與 QR Code 連結 (size 參數可調整)
|
| 588 |
const getQrUrl = (size = "150x150") => {
|
| 589 |
const baseUrl = window.location.origin + window.location.pathname;
|
| 590 |
const fullUrl = `${baseUrl}?room=${roomCode}`;
|
|
|
|
| 854 |
</div>
|
| 855 |
</div>
|
| 856 |
</div>
|
| 857 |
+
);
|
| 858 |
+
};
|
| 859 |
|
| 860 |
// Teacher Login Modal
|
| 861 |
const TeacherLoginModal = ({ onClose, onLogin }) => {
|
|
|
|
| 1034 |
const [chestOpen, setChestOpen] = useState(false);
|
| 1035 |
const [combo, setCombo] = useState(0);
|
| 1036 |
|
| 1037 |
+
// ★★★ 改良的資料初始化邏輯:支援多房間存檔 ★★★
|
| 1038 |
const [userData, setUserData] = useState(() => {
|
| 1039 |
+
// 1. 取得網址中的房間參數
|
| 1040 |
+
const params = new URLSearchParams(window.location.search);
|
| 1041 |
+
const urlRoomId = params.get('room') || "";
|
| 1042 |
+
|
| 1043 |
+
// 2. 計算該房間對應的儲存 Key
|
| 1044 |
+
const storageKey = getStorageKey(urlRoomId);
|
| 1045 |
+
const saved = localStorage.getItem(storageKey);
|
| 1046 |
+
|
| 1047 |
+
// 預設資料結構
|
| 1048 |
const defaultData = {
|
| 1049 |
score: 0,
|
| 1050 |
name: "",
|
| 1051 |
+
roomId: urlRoomId, // 初始化時就綁定正確的 roomId
|
| 1052 |
+
mistakes: {},
|
| 1053 |
unlockedItems: ['title_novice', 'scroll_default'],
|
| 1054 |
equippedTitle: 'title_novice',
|
| 1055 |
equippedScroll: 'scroll_default',
|
| 1056 |
completedLevels: []
|
| 1057 |
};
|
| 1058 |
+
|
| 1059 |
+
if (saved) {
|
| 1060 |
+
try {
|
| 1061 |
const parsed = JSON.parse(saved);
|
| 1062 |
+
// 合併以確保新舊欄位都存在,並強制更新 roomId 為當前網址的 ID
|
| 1063 |
+
return { ...defaultData, ...parsed, roomId: urlRoomId };
|
| 1064 |
+
} catch (e) {
|
| 1065 |
+
console.error("Load error", e);
|
| 1066 |
}
|
|
|
|
|
|
|
| 1067 |
}
|
| 1068 |
+
|
| 1069 |
+
// 如果沒有存檔(新房間),回傳預設值(相當於開新帳號)
|
| 1070 |
return defaultData;
|
| 1071 |
});
|
| 1072 |
|
|
|
|
| 1098 |
}
|
| 1099 |
}, []);
|
| 1100 |
|
| 1101 |
+
// ★★★ 新增:處理手動輸入房間代碼後的切換邏輯 ★★★
|
| 1102 |
+
// 這部分是為了 NameModal 提交後的行為
|
| 1103 |
+
const handleJoinRoom = (name, newRoomId) => {
|
| 1104 |
+
const targetRoomId = newRoomId.trim();
|
| 1105 |
|
| 1106 |
+
// 只有當房間代碼真的改變時,才進行切換
|
| 1107 |
+
if (targetRoomId !== userData.roomId) {
|
| 1108 |
+
const newKey = getStorageKey(targetRoomId);
|
| 1109 |
+
const saved = localStorage.getItem(newKey);
|
| 1110 |
+
let newData;
|
| 1111 |
+
|
| 1112 |
+
if (saved) {
|
| 1113 |
+
// 載入該房間的舊進度
|
| 1114 |
+
newData = JSON.parse(saved);
|
| 1115 |
+
// 確保名字更新為使用者剛輸入的
|
| 1116 |
+
newData.name = name;
|
| 1117 |
+
showNotification(`已切換至房間:${targetRoomId}`, "success");
|
| 1118 |
+
} else {
|
| 1119 |
+
// 該房間沒有紀錄,建立新帳號
|
| 1120 |
+
newData = {
|
| 1121 |
+
score: 0,
|
| 1122 |
+
name: name,
|
| 1123 |
+
roomId: targetRoomId,
|
| 1124 |
+
mistakes: {},
|
| 1125 |
+
unlockedItems: ['title_novice', 'scroll_default'],
|
| 1126 |
+
equippedTitle: 'title_novice',
|
| 1127 |
+
equippedScroll: 'scroll_default',
|
| 1128 |
+
completedLevels: []
|
| 1129 |
+
};
|
| 1130 |
+
showNotification(`已加入新房間:${targetRoomId}`, "success");
|
| 1131 |
+
}
|
| 1132 |
+
setUserData(newData);
|
| 1133 |
+
} else {
|
| 1134 |
+
// 房間沒變,只更新名字
|
| 1135 |
+
setUserData(prev => ({ ...prev, name, roomId: targetRoomId }));
|
| 1136 |
}
|
| 1137 |
+
setShowNameModal(false);
|
| 1138 |
+
};
|
| 1139 |
|
| 1140 |
+
// Save Data (Modified to use dynamic key based on current userData.roomId)
|
| 1141 |
useEffect(() => {
|
| 1142 |
+
const key = getStorageKey(userData.roomId);
|
| 1143 |
+
localStorage.setItem(key, JSON.stringify(userData));
|
| 1144 |
}, [userData]);
|
| 1145 |
|
| 1146 |
// --- Firebase Logic ---
|
|
|
|
| 1387 |
return <TeacherDashboard onClose={() => setGameState('menu')} allData={leaderboardData} />;
|
| 1388 |
}
|
| 1389 |
|
| 1390 |
+
// 使用修改後的 handleJoinRoom 替換原本的直接 setUserData
|
| 1391 |
+
if (showNameModal) return <div className="relative min-h-screen"><div className="palace-bg"></div><div className="palace-overlay"></div><NameModal onSubmit={handleJoinRoom} /></div>;
|
| 1392 |
if (showTeacherLogin) return <TeacherLoginModal onClose={() => setShowTeacherLogin(false)} onLogin={() => { setShowTeacherLogin(false); setGameState('teacher_dashboard'); }} />;
|
| 1393 |
|
| 1394 |
if (gameState === 'leaderboard') {
|