feat: 演唱會搜尋清單
This commit is contained in:
+51
-2
@@ -97,15 +97,62 @@
|
|||||||
box-shadow: 0 0 10px rgba(255, 204, 0, 0.35);
|
box-shadow: 0 0 10px rgba(255, 204, 0, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
#concert-search-result {
|
.concert-search-result {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.concert-search-summary {
|
||||||
|
margin: 0 0 12px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.concert-search-hits {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
max-width: 720px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.concert-search-hits li {
|
||||||
|
padding: 10px 14px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background: rgba(255, 248, 223, 0.1);
|
||||||
|
border: 1px solid rgba(255, 204, 0, 0.35);
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.concert-search-hits .hit-track {
|
||||||
|
display: block;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ffdd00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.concert-search-hits .hit-meta {
|
||||||
|
display: block;
|
||||||
|
margin-top: 4px;
|
||||||
|
color: rgba(255, 255, 255, 0.78);
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.concert-search-hits .hit-more {
|
||||||
|
text-align: center;
|
||||||
|
border-style: dashed;
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-item.is-search-hit .track {
|
||||||
|
color: #ffdd00;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -128,7 +175,9 @@
|
|||||||
<input type="text" class="search-input" id="concert-member" value="" placeholder="成員或段落(可空白)">
|
<input type="text" class="search-input" id="concert-member" value="" placeholder="成員或段落(可空白)">
|
||||||
<button type="button" class="concert-search-btn" id="concert-search-btn">查詢</button>
|
<button type="button" class="concert-search-btn" id="concert-search-btn">查詢</button>
|
||||||
</div>
|
</div>
|
||||||
<span id="concert-search-result">輸入後按「查詢」,會顯示比對結果。</span>
|
<div id="concert-search-result" class="concert-search-result" aria-live="polite">
|
||||||
|
<p class="concert-search-summary">輸入後按「查詢」,會在下方列出比對到的曲目。</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="concert-container">
|
<div class="concert-container">
|
||||||
|
|||||||
+89
-29
@@ -2,59 +2,119 @@
|
|||||||
let myQuery = document.querySelector("#concert-query");
|
let myQuery = document.querySelector("#concert-query");
|
||||||
let myMember = document.querySelector("#concert-member");
|
let myMember = document.querySelector("#concert-member");
|
||||||
let myBtn = document.querySelector("#concert-search-btn");
|
let myBtn = document.querySelector("#concert-search-btn");
|
||||||
let mySpan = document.querySelector("#concert-search-result");
|
let myResult = document.querySelector("#concert-search-result");
|
||||||
|
|
||||||
console.log("myQuery", myQuery);
|
const SEARCH_LIST_MAX = 12;
|
||||||
console.log("myMember", myMember);
|
|
||||||
console.log("myBtn", myBtn);
|
|
||||||
console.log("mySpan", mySpan);
|
|
||||||
|
|
||||||
function countMatchingTracks(keyword, member) {
|
function getTrackContext(trackEl) {
|
||||||
|
const venueBlock = trackEl.closest(".track-list")?.parentElement;
|
||||||
|
const tourCard = trackEl.closest(".con-1, .con-2, .con-3, .con-4");
|
||||||
|
return {
|
||||||
|
trackName: trackEl.textContent.trim(),
|
||||||
|
tourTitle: tourCard?.querySelector(".title")?.textContent.trim().replace(/\s+/g, " ") || "",
|
||||||
|
location: venueBlock?.querySelector(".location")?.textContent.trim() || ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function findMatchingTracks(keyword, member) {
|
||||||
const tracks = document.querySelectorAll(".track-item .track");
|
const tracks = document.querySelectorAll(".track-item .track");
|
||||||
const k = keyword.trim().toLowerCase();
|
const k = keyword.trim().toLowerCase();
|
||||||
const m = member.trim().toLowerCase();
|
const m = member.trim().toLowerCase();
|
||||||
let count = 0;
|
const hits = [];
|
||||||
|
|
||||||
tracks.forEach(function (trackEl) {
|
tracks.forEach(function (trackEl) {
|
||||||
const text = trackEl.textContent.toLowerCase();
|
const text = trackEl.textContent.toLowerCase();
|
||||||
const matchKeyword = !k || text.includes(k);
|
const matchKeyword = !k || text.includes(k);
|
||||||
const matchMember = !m || text.includes(m);
|
const matchMember = !m || text.includes(m);
|
||||||
if (matchKeyword && matchMember) {
|
if (matchKeyword && matchMember) {
|
||||||
count += 1;
|
hits.push({
|
||||||
|
el: trackEl.closest(".track-item"),
|
||||||
|
...getTrackContext(trackEl)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return count;
|
return hits;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSearchHighlights() {
|
||||||
|
document.querySelectorAll(".track-item.is-search-hit").forEach(function (item) {
|
||||||
|
item.classList.remove("is-search-hit");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSearchResults(getQuery, getMember, hits) {
|
||||||
|
if (!myResult) return;
|
||||||
|
|
||||||
|
clearSearchHighlights();
|
||||||
|
|
||||||
|
const summary = document.createElement("p");
|
||||||
|
summary.className = "concert-search-summary";
|
||||||
|
|
||||||
|
if (!hits.length) {
|
||||||
|
summary.textContent = `關鍵字「${getQuery || "(未填)"}」${getMember.trim() ? `、成員「${getMember}」` : ""}:沒有比對到曲目,請換字再試。`;
|
||||||
|
myResult.replaceChildren(summary);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getMember.trim()) {
|
||||||
|
summary.textContent = `關鍵字「${getQuery || "(未填)"}」、成員「${getMember}」:共 ${hits.length} 筆,以下列出前 ${Math.min(hits.length, SEARCH_LIST_MAX)} 筆。`;
|
||||||
|
} else {
|
||||||
|
summary.textContent = `關鍵字「${getQuery}」:共 ${hits.length} 筆,以下列出前 ${Math.min(hits.length, SEARCH_LIST_MAX)} 筆。`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = document.createElement("ul");
|
||||||
|
list.className = "concert-search-hits";
|
||||||
|
|
||||||
|
hits.slice(0, SEARCH_LIST_MAX).forEach(function (hit) {
|
||||||
|
if (hit.el) hit.el.classList.add("is-search-hit");
|
||||||
|
|
||||||
|
const li = document.createElement("li");
|
||||||
|
const trackLine = document.createElement("span");
|
||||||
|
trackLine.className = "hit-track";
|
||||||
|
trackLine.textContent = hit.trackName;
|
||||||
|
|
||||||
|
const metaLine = document.createElement("span");
|
||||||
|
metaLine.className = "hit-meta";
|
||||||
|
metaLine.textContent = [hit.tourTitle, hit.location].filter(Boolean).join(" · ");
|
||||||
|
|
||||||
|
li.appendChild(trackLine);
|
||||||
|
if (metaLine.textContent) li.appendChild(metaLine);
|
||||||
|
list.appendChild(li);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hits.length > SEARCH_LIST_MAX) {
|
||||||
|
const more = document.createElement("li");
|
||||||
|
more.className = "hit-more";
|
||||||
|
more.textContent = `另有 ${hits.length - SEARCH_LIST_MAX} 筆未顯示,請縮小關鍵字。`;
|
||||||
|
list.appendChild(more);
|
||||||
|
}
|
||||||
|
|
||||||
|
myResult.replaceChildren(summary, list);
|
||||||
|
|
||||||
|
const firstHit = hits[0]?.el;
|
||||||
|
if (firstHit) {
|
||||||
|
firstHit.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2.action
|
// 2.action
|
||||||
if (myBtn && mySpan) {
|
if (myBtn && myResult) {
|
||||||
myBtn.addEventListener("click", function () {
|
myBtn.addEventListener("click", function () {
|
||||||
console.log("myBtn", myBtn);
|
|
||||||
|
|
||||||
// 1.抓 input
|
|
||||||
let getQuery = myQuery ? myQuery.value : "";
|
let getQuery = myQuery ? myQuery.value : "";
|
||||||
let getMember = myMember ? myMember.value : "";
|
let getMember = myMember ? myMember.value : "";
|
||||||
console.log("getQuery", getQuery);
|
|
||||||
console.log("getMember", getMember);
|
|
||||||
|
|
||||||
if (!getQuery.trim() && !getMember.trim()) {
|
if (!getQuery.trim() && !getMember.trim()) {
|
||||||
mySpan.innerText = "請至少輸入曲目/場次,或成員其中一項。";
|
myResult.replaceChildren();
|
||||||
|
const summary = document.createElement("p");
|
||||||
|
summary.className = "concert-search-summary";
|
||||||
|
summary.textContent = "請至少輸入曲目/場次,或成員其中一項。";
|
||||||
|
myResult.appendChild(summary);
|
||||||
|
clearSearchHighlights();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const count = countMatchingTracks(getQuery, getMember);
|
const hits = findMatchingTracks(getQuery, getMember);
|
||||||
|
renderSearchResults(getQuery, getMember, hits);
|
||||||
// 2.組成 text
|
|
||||||
let tmpText;
|
|
||||||
if (getMember.trim()) {
|
|
||||||
tmpText = `關鍵字「${getQuery || "(未填)"}」、成員「${getMember}」:在巡迴歌單中共 ${count} 筆曲目。`;
|
|
||||||
} else {
|
|
||||||
tmpText = `關鍵字「${getQuery}」:在巡迴歌單中共 ${count} 筆曲目。`;
|
|
||||||
}
|
|
||||||
console.log("tmpText", tmpText);
|
|
||||||
|
|
||||||
// 3.顯示在 span
|
|
||||||
mySpan.innerText = tmpText;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user