From 956b71cc4e3bb07b8e77bce06a51d0e4e98cb4d0 Mon Sep 17 00:00:00 2001 From: LHgeek <1209384461@qq.com> Date: Fri, 27 Feb 2026 21:47:17 +0800 Subject: [PATCH] feat_B by vibe coding use GLM 5 Signed-off-by: LHgeek <1209384461@qq.com> --- assets/css/style.css | 249 +++++++++++++++++++++++++++++++++++++- assets/js/script.js | 280 +++++++++++++++++++++++++++++++++++++++++-- index.html | 25 ++++ 3 files changed, 544 insertions(+), 10 deletions(-) diff --git a/assets/css/style.css b/assets/css/style.css index 2accbed..9d946c7 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -5,10 +5,13 @@ } body { - height: 100vh; + min-height: 100vh; display: flex; - justify-content: center; + justify-content: flex-start; align-items: center; + flex-direction: column; + padding: 20px; + padding-top: 60px; transition: background-color 0.3s ease, color 0.3s ease; } @@ -195,4 +198,246 @@ body.dark-mode #author { border-radius: 0; object-fit: cover; box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); +} + +.tag-filter-container { + display: flex; + flex-wrap: wrap; + gap: 10px; + justify-content: center; + margin-bottom: 20px; + padding: 15px; + max-width: 600px; +} + +.tag-btn { + padding: 8px 16px; + background-color: #3d405b; + color: white; + border: 2px solid #ef8354; + border-radius: 20px; + cursor: pointer; + font-size: 14px; + transition: all 0.3s ease; +} + +.tag-btn:hover { + background-color: #ef8354; + color: white; +} + +.tag-btn.active { + background-color: #ef8354; + color: white; + font-weight: bold; +} + +.quote-tags { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: center; + margin-top: 10px; +} + +.quote-tag { + background-color: #3d405b; + color: #ef8354; + padding: 4px 10px; + border-radius: 12px; + font-size: 12px; +} + +body.dark-mode .tag-btn { + background-color: #333; + border-color: #ef8354; +} + +body.dark-mode .tag-btn.active { + background-color: #ef8354; +} + +body.dark-mode .quote-tag { + background-color: #333; +} + +.history-panel { + background-color: #2b2e4a; + border-radius: 10px; + margin-top: 20px; + width: 100%; + max-width: 600px; + overflow: hidden; +} + +.history-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px 20px; + cursor: pointer; + color: white; + font-size: 18px; + font-weight: bold; + background-color: #3d405b; + transition: background-color 0.3s ease; +} + +.history-header:hover { + background-color: #4a4d6a; +} + +.history-header i { + transition: transform 0.3s ease; +} + +.history-header.collapsed i { + transform: rotate(-90deg); +} + +.history-content { + max-height: 300px; + overflow-y: auto; + padding: 15px; + transition: max-height 0.3s ease; +} + +.history-content.collapsed { + max-height: 0; + padding: 0 15px; + overflow: hidden; +} + +.history-item { + background-color: #3d405b; + border-radius: 8px; + padding: 12px; + margin-bottom: 10px; + color: white; +} + +.history-item-text { + font-size: 14px; + color: #ef8354; + margin-bottom: 5px; +} + +.history-item-author { + font-size: 12px; + color: #aaa; +} + +.history-item-tags { + display: flex; + flex-wrap: wrap; + gap: 5px; + margin-top: 8px; +} + +.history-item-tag { + font-size: 10px; + background-color: #2b2e4a; + color: #ef8354; + padding: 2px 8px; + border-radius: 10px; +} + +.btn-clear { + background-color: #e74c3c; + color: white; + border: none; + padding: 8px 16px; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + width: 100%; + margin-top: 10px; +} + +.btn-clear:hover { + background-color: #c0392b; +} + +.recommend-panel { + background-color: #2b2e4a; + border-radius: 10px; + margin-top: 20px; + width: 100%; + max-width: 600px; + overflow: hidden; +} + +.recommend-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px 20px; + color: white; + font-size: 18px; + font-weight: bold; + background-color: #3d405b; +} + +.recommend-hint { + font-size: 12px; + color: #aaa; + font-weight: normal; +} + +.recommend-content { + padding: 15px; + max-height: 250px; + overflow-y: auto; +} + +.recommend-item { + background-color: #3d405b; + border-radius: 8px; + padding: 12px; + margin-bottom: 10px; + color: white; + cursor: pointer; + transition: transform 0.2s ease, background-color 0.2s ease; +} + +.recommend-item:hover { + transform: translateX(5px); + background-color: #4a4d6a; +} + +.recommend-item-text { + font-size: 14px; + color: #ef8354; + margin-bottom: 5px; +} + +.recommend-item-author { + font-size: 12px; + color: #aaa; +} + +.recommend-empty { + text-align: center; + color: #aaa; + padding: 20px; + font-size: 14px; +} + +body.dark-mode .history-panel, +body.dark-mode .recommend-panel { + background-color: #1e1e1e; +} + +body.dark-mode .history-header, +body.dark-mode .recommend-header { + background-color: #333; +} + +body.dark-mode .history-item, +body.dark-mode .recommend-item { + background-color: #333; +} + +body.dark-mode .history-item-tag { + background-color: #1e1e1e; } \ No newline at end of file diff --git a/assets/js/script.js b/assets/js/script.js index 3981fc9..a55a132 100644 --- a/assets/js/script.js +++ b/assets/js/script.js @@ -1,4 +1,7 @@ const FAVORITES_KEY = "favoriteQuotes"; +const HISTORY_KEY = "quoteHistory"; +const RECENT_TAGS_KEY = "recentTags"; +const MAX_HISTORY = 20; // DOM const quoteText = document.getElementById("quote"); @@ -10,6 +13,16 @@ const copyQuoteButton = document.getElementById("copy-quote"); const favoriteIcon = document.getElementById("favorite-icon"); const authorText = document.getElementById("author"); const authorImage = document.getElementById("author-image"); +const quoteTagsContainer = document.getElementById("quote-tags"); +const tagFilterContainer = document.getElementById("tag-filter-container"); +const historyToggle = document.getElementById("history-toggle"); +const historyContent = document.getElementById("history-content"); +const historyList = document.getElementById("history-list"); +const clearHistoryBtn = document.getElementById("clear-history"); +const recommendContent = document.getElementById("recommend-content"); + +let selectedTags = new Set(); +let allTags = new Set(); // quotes array const quotes = { @@ -17,42 +30,51 @@ const quotes = { { text: "The greatest glory in living lies not in never falling, but in rising every time we fall.", author: "Nelson Mandela", + tags: ["perseverance", "success", "life"] }, { text: "The way to get started is to quit talking and begin doing.", author: "Walt Disney", + tags: ["action", "success", "beginning"] }, { - text: "Your time is limited, so don’t waste it living someone else’s life.", + text: "Your time is limited, so don't waste it living someone else's life.", author: "Steve Jobs", + tags: ["life", "time", "authenticity"] } ], funny: [ { text: "I'm not arguing, I'm just explaining why I'm right.", author: "Unknown", + tags: ["humor", "arguments", "confidence"] }, { - text: "Why don’t skeletons fight each other? They don’t have the guts.", + text: "Why don't skeletons fight each other? They don't have the guts.", author: "Unknown", + tags: ["humor", "puns", "silly"] }, { text: "My fake plants died because I did not pretend to water them.", author: "Mitch Hedberg", + tags: ["humor", "irony", "life"] } ], inspirational: [ { - text: "Life is what happens when you’re busy making other plans.", + text: "Life is what happens when you're busy making other plans.", author: "John Lennon", + tags: ["life", "planning", "mindfulness"] }, { - text: "If you look at what you have in life, you’ll always have more.", + text: "If you look at what you have in life, you'll always have more.", author: "Oprah Winfrey", + tags: ["gratitude", "life", "positivity"] }, { text: "If life were predictable it would cease to be life, and be without flavor.", author: "Eleanor Roosevelt", + tags: ["life", "adventure", "uncertainty"] } ], }; @@ -71,10 +93,16 @@ async function newQuote() { // Set the icon to orange while a new quote is generating favoriteIcon.style.color = "#ef8354"; // 주황색으로 설정 - const selectedCategory = categorySelect.value; // Get the selected category from the dropdown - const categoryQuotes = quotes[selectedCategory]; // Get quotes from the selected category - const randomIndex = Math.floor(Math.random() * categoryQuotes.length); // Get a random quote index - const selectedQuote = categoryQuotes[randomIndex]; // Get the random quote from the selected category + const filteredQuotes = getFilteredQuotes(); + + if (filteredQuotes.length === 0) { + alert("没有符合条件的名言,请重新选择主题标签"); + buttonDisabled("false"); + return; + } + + const randomIndex = Math.floor(Math.random() * filteredQuotes.length); + const selectedQuote = filteredQuotes[randomIndex]; // Enable the button after the quote is fully displayed setTimeout(() => { @@ -83,6 +111,11 @@ async function newQuote() { typeWriterEffect(selectedQuote.text, updateFavoriteIconColor); authorText.textContent = selectedQuote.author; + displayQuoteTags(selectedQuote.tags); + + saveToHistory(selectedQuote); + updateRecentTags(selectedQuote.tags); + updateRecommendations(); // Fetch images from Wikipedia const authorImageUrl = await fetchWikipediaImage(selectedQuote.author); @@ -254,6 +287,237 @@ function updateFavoriteIconColor() { // Initial icon color setup when the page loads updateFavoriteIconColor(); +function collectAllTags() { + for (const category in quotes) { + quotes[category].forEach(quote => { + if (quote.tags) { + quote.tags.forEach(tag => allTags.add(tag)); + } + }); + } +} + +function initTagButtons() { + collectAllTags(); + + const allBtn = tagFilterContainer.querySelector('[data-tag="all"]'); + allBtn.addEventListener("click", () => toggleTagFilter("all")); + + allTags.forEach(tag => { + const btn = document.createElement("button"); + btn.className = "tag-btn"; + btn.dataset.tag = tag; + btn.textContent = tag; + btn.addEventListener("click", () => toggleTagFilter(tag)); + tagFilterContainer.appendChild(btn); + }); +} + +function toggleTagFilter(tag) { + const allBtn = tagFilterContainer.querySelector('[data-tag="all"]'); + + if (tag === "all") { + selectedTags.clear(); + document.querySelectorAll(".tag-btn").forEach(btn => btn.classList.remove("active")); + allBtn.classList.add("active"); + } else { + allBtn.classList.remove("active"); + + if (selectedTags.has(tag)) { + selectedTags.delete(tag); + } else { + selectedTags.add(tag); + } + + document.querySelectorAll(".tag-btn:not([data-tag='all'])").forEach(btn => { + if (selectedTags.has(btn.dataset.tag)) { + btn.classList.add("active"); + } else { + btn.classList.remove("active"); + } + }); + + if (selectedTags.size === 0) { + allBtn.classList.add("active"); + } + } +} + +function getFilteredQuotes() { + const selectedCategory = categorySelect.value; + const categoryQuotes = quotes[selectedCategory]; + + if (selectedTags.size === 0) { + return categoryQuotes; + } + + return categoryQuotes.filter(quote => { + if (!quote.tags) return false; + return quote.tags.some(tag => selectedTags.has(tag)); + }); +} + +function displayQuoteTags(tags) { + quoteTagsContainer.innerHTML = ""; + if (tags && tags.length > 0) { + tags.forEach(tag => { + const tagSpan = document.createElement("span"); + tagSpan.className = "quote-tag"; + tagSpan.textContent = `#${tag}`; + quoteTagsContainer.appendChild(tagSpan); + }); + } +} + +initTagButtons(); + +// History Panel Functions +function initHistoryPanel() { + historyToggle.addEventListener("click", toggleHistoryPanel); + clearHistoryBtn.addEventListener("click", clearHistory); + renderHistory(); +} + +function toggleHistoryPanel() { + historyToggle.classList.toggle("collapsed"); + historyContent.classList.toggle("collapsed"); +} + +function saveToHistory(quote) { + let history = JSON.parse(localStorage.getItem(HISTORY_KEY)) || []; + + const historyItem = { + text: quote.text, + author: quote.author, + tags: quote.tags || [], + timestamp: Date.now() + }; + + history.unshift(historyItem); + + if (history.length > MAX_HISTORY) { + history = history.slice(0, MAX_HISTORY); + } + + localStorage.setItem(HISTORY_KEY, JSON.stringify(history)); + renderHistory(); +} + +function renderHistory() { + const history = JSON.parse(localStorage.getItem(HISTORY_KEY)) || []; + + if (history.length === 0) { + historyList.innerHTML = '
暂无历史记录
'; + return; + } + + historyList.innerHTML = history.map(item => ` +
+
"${item.text}"
+
— ${item.author}
+ ${item.tags && item.tags.length > 0 ? ` +
+ ${item.tags.map(tag => `#${tag}`).join('')} +
+ ` : ''} +
+ `).join(''); +} + +function clearHistory() { + if (confirm("确定要清空所有历史记录吗?")) { + localStorage.removeItem(HISTORY_KEY); + renderHistory(); + } +} + +// Recent Tags Functions +function updateRecentTags(tags) { + if (!tags || tags.length === 0) return; + + let recentTags = JSON.parse(localStorage.getItem(RECENT_TAGS_KEY)) || []; + + tags.forEach(tag => { + const existingIndex = recentTags.findIndex(t => t.name === tag); + if (existingIndex !== -1) { + recentTags[existingIndex].count++; + recentTags[existingIndex].lastUsed = Date.now(); + } else { + recentTags.push({ name: tag, count: 1, lastUsed: Date.now() }); + } + }); + + recentTags.sort((a, b) => b.count - a.count); + recentTags = recentTags.slice(0, 10); + + localStorage.setItem(RECENT_TAGS_KEY, JSON.stringify(recentTags)); +} + +function getRecentTags() { + return JSON.parse(localStorage.getItem(RECENT_TAGS_KEY)) || []; +} + +// Recommendation Functions +function updateRecommendations() { + const recentTags = getRecentTags(); + const recommendContent = document.getElementById("recommend-content"); + + if (recentTags.length === 0) { + recommendContent.innerHTML = '
开始筛选主题后,将为您推荐相关名言
'; + return; + } + + const topTags = recentTags.slice(0, 3).map(t => t.name); + const recommendations = getRecommendationsByTags(topTags); + + if (recommendations.length === 0) { + recommendContent.innerHTML = '
暂无匹配的推荐名言
'; + return; + } + + recommendContent.innerHTML = recommendations.slice(0, 5).map(quote => ` +
+
"${quote.text}"
+
— ${quote.author}
+
+ `).join(''); +} + +function getRecommendationsByTags(tags) { + const recommendations = []; + + for (const category in quotes) { + quotes[category].forEach(quote => { + if (quote.tags && quote.tags.some(tag => tags.includes(tag))) { + if (!recommendations.find(r => r.text === quote.text)) { + recommendations.push(quote); + } + } + }); + } + + return recommendations; +} + +function selectRecommendQuote(selectedText) { + for (const category in quotes) { + const found = quotes[category].find(q => q.text === selectedText); + if (found) { + quoteText.innerHTML = ""; + typeWriterEffect(found.text, updateFavoriteIconColor); + authorText.textContent = found.author; + displayQuoteTags(found.tags); + fetchWikipediaImage(found.author).then(url => { + authorImage.src = url; + }); + break; + } + } +} + +initHistoryPanel(); +updateRecommendations(); + // Wikipedia API request function async function fetchWikipediaImage(authorName) { const apiUrl = `https://en.wikipedia.org/w/api.php?action=query&format=json&origin=*&prop=pageimages&titles=${encodeURIComponent(authorName)}&pithumbsize=200`; diff --git a/index.html b/index.html index 6a1e4ef..fe57cab 100644 --- a/index.html +++ b/index.html @@ -11,6 +11,7 @@ display: flex; flex-direction: column; align-items: center; + width: 100%; } @@ -25,6 +26,10 @@ Random Quotes Generator Logo +
+ +
+
@@ -37,6 +42,7 @@
Your quote will appear here...
+
Author Image @@ -52,6 +58,25 @@
+
+
+ 历史记录 + +
+
+
+ +
+
+ +
+
+ 推荐名言 + 基于您的筛选偏好 +
+
+
+