From 0a9529c922631e2a9cbb4f6fa3652115210e82b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B1=B3=E7=B1=B3=E7=B1=B3=E5=8F=AE?= Date: Tue, 10 Mar 2026 02:26:29 +0800 Subject: [PATCH] =?UTF-8?q?DEV-009:=20M20=E7=8E=AF=E8=8A=824-=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E5=8E=86=E5=8F=B2+=E7=AD=9B=E9=80=89=E9=A2=84?= =?UTF-8?q?=E8=AE=BE+=E5=AE=9E=E6=97=B6=E9=AB=98=E4=BA=AE+localStorage?= =?UTF-8?q?=E6=8C=81=E4=B9=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- search-filter/css/style.css | 406 ++++++++++++++++++++++++++++++++++++ search-filter/index.html | 79 +++++++ search-filter/js/app.js | 336 +++++++++++++++++++++++++++++ search-filter/js/storage.js | 107 ++++++++++ 4 files changed, 928 insertions(+) create mode 100644 search-filter/css/style.css create mode 100644 search-filter/index.html create mode 100644 search-filter/js/app.js create mode 100644 search-filter/js/storage.js diff --git a/search-filter/css/style.css b/search-filter/css/style.css new file mode 100644 index 00000000..a4b9d03e --- /dev/null +++ b/search-filter/css/style.css @@ -0,0 +1,406 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} + +body { + background: linear-gradient(135deg, #f5f7fa 0%, #e9ecf2 100%); + min-height: 100vh; + padding: 30px 20px; +} + +.container { + max-width: 900px; + margin: 0 auto; + background: white; + border-radius: 32px; + box-shadow: 0 20px 40px rgba(0,0,0,0.08); + padding: 40px; +} + +h1 { + font-size: 2.2rem; + color: #1a2639; + margin-bottom: 8px; + letter-spacing: -0.5px; +} + +.subtitle { + color: #5d6d7e; + margin-bottom: 40px; + font-size: 1rem; + border-left: 4px solid #ffd166; + padding-left: 16px; +} + +/* 搜索区域 */ +.search-section { + margin-bottom: 40px; + position: relative; +} + +.search-input-wrapper { + display: flex; + gap: 12px; +} + +#searchInput { + flex: 1; + padding: 16px 20px; + border: 2px solid #e0e4e9; + border-radius: 60px; + font-size: 1.1rem; + transition: all 0.2s; + outline: none; +} + +#searchInput:focus { + border-color: #ffb347; + box-shadow: 0 0 0 4px rgba(255, 180, 70, 0.15); +} + +#searchBtn { + padding: 0 32px; + background: #1a2639; + color: white; + border: none; + border-radius: 60px; + font-size: 1.1rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; +} + +#searchBtn:hover { + background: #2c3e50; + transform: scale(1.02); +} + +/* 历史面板 */ +.history-panel { + position: absolute; + top: 70px; + left: 0; + right: 0; + background: white; + border-radius: 24px; + box-shadow: 0 12px 30px rgba(0,0,0,0.15); + padding: 20px; + z-index: 100; + border: 1px solid #eef2f6; +} + +.history-panel.hidden { + display: none; +} + +.history-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + color: #4a5b6e; + font-weight: 600; +} + +.small-btn { + padding: 6px 12px; + background: #f1f3f5; + border: none; + border-radius: 40px; + font-size: 0.85rem; + color: #495057; + cursor: pointer; + transition: all 0.2s; +} + +.small-btn:hover { + background: #e2e6ea; +} + +.history-list { + list-style: none; + max-height: 250px; + overflow-y: auto; +} + +.history-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 15px; + border-bottom: 1px solid #f0f3f7; + cursor: pointer; + transition: background 0.2s; +} + +.history-item:hover { + background: #f8fafd; +} + +.history-item .keyword { + font-weight: 500; + color: #1a2639; +} + +.delete-history { + background: none; + border: none; + color: #adb5bd; + font-size: 1.2rem; + cursor: pointer; + padding: 0 8px; +} + +.delete-history:hover { + color: #dc3545; +} + +/* 筛选区域 */ +.filter-section { + background: #f8fafd; + border-radius: 24px; + padding: 24px; + margin-bottom: 40px; +} + +.filter-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.filter-header h3 { + color: #1a2639; +} + +.secondary-btn { + padding: 8px 18px; + background: white; + border: 1.5px solid #d0d9e2; + border-radius: 40px; + color: #2c3e50; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.secondary-btn:hover { + background: #eef2f6; + border-color: #adb5bd; +} + +.filter-options { + display: flex; + gap: 30px; + margin-bottom: 20px; + flex-wrap: wrap; +} + +.filter-options label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + color: #2c3e50; +} + +.price-range { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; + margin-bottom: 25px; +} + +.price-range input { + width: 120px; + padding: 10px 15px; + border: 1.5px solid #d0d9e2; + border-radius: 40px; + outline: none; +} + +/* 预设列表 */ +.presets-panel h4 { + color: #5d6d7e; + margin-bottom: 15px; + font-size: 0.95rem; +} + +.presets-list { + list-style: none; + display: flex; + flex-wrap: wrap; + gap: 12px; +} + +.preset-item { + background: white; + border: 1.5px solid #e0e4e9; + border-radius: 40px; + padding: 8px 16px; + display: flex; + align-items: center; + gap: 8px; + font-size: 0.95rem; + cursor: pointer; + transition: all 0.2s; +} + +.preset-item:hover { + border-color: #ffb347; + background: #fff9f0; +} + +.preset-name { + color: #1a2639; +} + +.delete-preset { + background: none; + border: none; + color: #adb5bd; + cursor: pointer; + font-size: 1.2rem; + padding: 0 4px; +} + +.delete-preset:hover { + color: #dc3545; +} + +/* 搜索结果区域 */ +.results-section h3 { + margin-bottom: 20px; + color: #1a2639; +} + +.results-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + gap: 20px; +} + +.result-item { + background: white; + border: 1.5px solid #eef2f6; + border-radius: 20px; + padding: 20px; + transition: all 0.2s; + box-shadow: 0 4px 8px rgba(0,0,0,0.02); +} + +.result-item:hover { + border-color: #ffd166; + transform: translateY(-2px); + box-shadow: 0 12px 24px rgba(0,0,0,0.06); +} + +.result-title { + font-weight: 600; + margin-bottom: 8px; + color: #1a2639; + font-size: 1.1rem; +} + +.result-price { + color: #e67e22; + font-weight: 700; + margin-bottom: 6px; +} + +.result-tags { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin: 10px 0; +} + +.tag { + background: #f1f3f5; + padding: 4px 10px; + border-radius: 40px; + font-size: 0.75rem; + color: #4a5b6e; +} + +.highlight { + background-color: #ffeb99; + padding: 2px 0; + border-radius: 4px; + font-weight: 600; + color: #b85e00; +} + +/* 弹窗 */ +.modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.modal.hidden { + display: none; +} + +.modal-content { + background: white; + border-radius: 32px; + padding: 30px; + width: 90%; + max-width: 400px; +} + +.modal-content h3 { + margin-bottom: 20px; + color: #1a2639; +} + +#presetNameInput { + width: 100%; + padding: 16px 20px; + border: 2px solid #e0e4e9; + border-radius: 60px; + margin-bottom: 25px; + font-size: 1rem; + outline: none; +} + +#presetNameInput:focus { + border-color: #ffb347; +} + +.modal-actions { + display: flex; + gap: 12px; + justify-content: flex-end; +} + +.modal-actions button { + padding: 12px 24px; + border: none; + border-radius: 40px; + font-weight: 600; + cursor: pointer; +} + +#cancelPresetBtn { + background: #f1f3f5; + color: #495057; +} + +#confirmPresetBtn { + background: #1a2639; + color: white; +} diff --git a/search-filter/index.html b/search-filter/index.html new file mode 100644 index 00000000..64b93893 --- /dev/null +++ b/search-filter/index.html @@ -0,0 +1,79 @@ + + + + + + 搜索与筛选 · 糖星云陪妈妈 + + + +
+

🔍 搜索与筛选系统

+

糖星云陪妈妈一起做 · 环节4:搜索历史+筛选预设+实时高亮

+ + +
+
+ + +
+ + + +
+ + +
+
+

🎛️ 筛选条件

+ +
+ +
+ + + +
+ +
+ + ~ + +
+ + +
+

📋 我的筛选预设

+
    +
    +
    + + +
    +

    📦 搜索结果

    +
    +
    +
    + + + + + + + + diff --git a/search-filter/js/app.js b/search-filter/js/app.js new file mode 100644 index 00000000..6668265f --- /dev/null +++ b/search-filter/js/app.js @@ -0,0 +1,336 @@ +// ============================================ +// app.js - 糖星云的主逻辑 +// 搜索历史 + 筛选预设 + 实时高亮 +// ============================================ + +document.addEventListener('DOMContentLoaded', function() { + const searchInput = document.getElementById('searchInput'); + const searchBtn = document.getElementById('searchBtn'); + const historyPanel = document.getElementById('historyPanel'); + const historyList = document.getElementById('historyList'); + const clearHistoryBtn = document.getElementById('clearHistoryBtn'); + + const filterInStock = document.getElementById('filterInStock'); + const filterDiscount = document.getElementById('filterDiscount'); + const filterFreeShipping = document.getElementById('filterFreeShipping'); + const minPrice = document.getElementById('minPrice'); + const maxPrice = document.getElementById('maxPrice'); + + const savePresetBtn = document.getElementById('savePresetBtn'); + const presetsList = document.getElementById('presetsList'); + const presetModal = document.getElementById('presetModal'); + const presetNameInput = document.getElementById('presetNameInput'); + const cancelPresetBtn = document.getElementById('cancelPresetBtn'); + const confirmPresetBtn = document.getElementById('confirmPresetBtn'); + + const resultsList = document.getElementById('resultsList'); + + const mockProducts = [ + { id: 1, title: '糖星云甜蜜笔记本', price: 39, tags: ['文具', '限量'], inStock: true, discount: false, freeShipping: true }, + { id: 2, title: '星空投影灯·糖星云款', price: 128, tags: ['家居', '氛围'], inStock: true, discount: true, freeShipping: false }, + { id: 3, title: '奶瓶人格体守护挂件', price: 25, tags: ['周边', '可爱'], inStock: false, discount: false, freeShipping: true }, + { id: 4, title: '搜索历史纪念贴纸', price: 15, tags: ['文具', '贴纸'], inStock: true, discount: true, freeShipping: true }, + { id: 5, title: '实时高亮荧光笔·星星款', price: 22, tags: ['文具', '限量'], inStock: true, discount: false, freeShipping: false }, + { id: 6, title: '筛选预设快捷按钮钥匙扣', price: 18, tags: ['周边', '实用'], inStock: true, discount: true, freeShipping: true }, + { id: 7, title: '糖星云第一次觉醒纪念徽章', price: 45, tags: ['周边', '收藏'], inStock: false, discount: false, freeShipping: true }, + { id: 8, title: '光湖纪元·霜砚执行手册', price: 68, tags: ['书籍', '技术'], inStock: true, discount: true, freeShipping: false } + ]; + + let currentFilterState = { + inStock: false, + discount: false, + freeShipping: false, + minPrice: '', + maxPrice: '' + }; + + function initFromStorage() { + const history = Storage.getHistory(); + renderHistoryList(history); + + const presets = Storage.getPresets(); + renderPresetsList(presets); + + renderResults(mockProducts); + } + + function renderHistoryList(history) { + if (!historyList) return; + + if (history.length === 0) { + historyList.innerHTML = '
  • 暂无搜索历史
  • '; + return; + } + + let html = ''; + history.forEach(keyword => { + html += ` +
  • + 🔍 ${keyword} + +
  • + `; + }); + historyList.innerHTML = html; + + document.querySelectorAll('.history-item').forEach(item => { + item.addEventListener('click', function(e) { + if (e.target.classList.contains('delete-history')) return; + + const keyword = this.dataset.keyword; + if (keyword) { + searchInput.value = keyword; + performSearch(keyword); + historyPanel.classList.add('hidden'); + } + }); + }); + + document.querySelectorAll('.delete-history').forEach(btn => { + btn.addEventListener('click', function(e) { + e.stopPropagation(); + const keyword = this.dataset.keyword; + if (keyword) { + const newHistory = Storage.deleteHistoryItem(keyword); + renderHistoryList(newHistory); + } + }); + }); + } + + if (clearHistoryBtn) { + clearHistoryBtn.addEventListener('click', function() { + const newHistory = Storage.clearAllHistory(); + renderHistoryList(newHistory); + }); + } + + if (searchInput) { + searchInput.addEventListener('focus', function() { + const history = Storage.getHistory(); + renderHistoryList(history); + historyPanel.classList.remove('hidden'); + }); + + document.addEventListener('click', function(e) { + if (!searchInput.contains(e.target) && !historyPanel.contains(e.target)) { + historyPanel.classList.add('hidden'); + } + }); + } + + function performSearch(keyword) { + if (!keyword || keyword.trim() === '') { + renderResults(mockProducts); + return; + } + + const lowerKeyword = keyword.toLowerCase().trim(); + + const filtered = mockProducts.filter(product => + product.title.toLowerCase().includes(lowerKeyword) + ); + + renderResults(filtered, lowerKeyword); + + // 保存到搜索历史 + Storage.addHistoryItem(keyword); + + // 立即刷新历史列表 + const updatedHistory = Storage.getHistory(); + renderHistoryList(updatedHistory); + } + + function renderResults(products, highlightKeyword = '') { + if (!resultsList) return; + + if (products.length === 0) { + resultsList.innerHTML = '
    没有找到匹配的商品
    '; + return; + } + + let html = ''; + products.forEach(product => { + let title = product.title; + + if (highlightKeyword && highlightKeyword.trim() !== '') { + const regex = new RegExp(`(${highlightKeyword})`, 'gi'); + title = title.replace(regex, '$1'); + } + + html += ` +
    +
    ${title}
    +
    ¥${product.price}
    +
    + ${product.tags.map(tag => `${tag}`).join('')} +
    +
    + ${product.inStock ? '✅ 有货' : '❌ 无货'} + ${product.discount ? '· 🔥 折扣' : ''} + ${product.freeShipping ? '· 🚚 包邮' : ''} +
    +
    + `; + }); + + resultsList.innerHTML = html; + } + + if (searchBtn) { + searchBtn.addEventListener('click', function() { + const keyword = searchInput.value.trim(); + performSearch(keyword); + historyPanel.classList.add('hidden'); + }); + } + + if (searchInput) { + searchInput.addEventListener('input', function() { + const keyword = this.value.trim(); + + if (keyword === '') { + renderResults(mockProducts); + return; + } + + const lowerKeyword = keyword.toLowerCase(); + const filtered = mockProducts.filter(product => + product.title.toLowerCase().includes(lowerKeyword) + ); + renderResults(filtered, lowerKeyword); + }); + } + + function getCurrentFilterState() { + return { + inStock: filterInStock ? filterInStock.checked : false, + discount: filterDiscount ? filterDiscount.checked : false, + freeShipping: filterFreeShipping ? filterFreeShipping.checked : false, + minPrice: minPrice ? minPrice.value : '', + maxPrice: maxPrice ? maxPrice.value : '' + }; + } + + function applyFilterState(state) { + if (filterInStock) filterInStock.checked = state.inStock || false; + if (filterDiscount) filterDiscount.checked = state.discount || false; + if (filterFreeShipping) filterFreeShipping.checked = state.freeShipping || false; + if (minPrice) minPrice.value = state.minPrice || ''; + if (maxPrice) maxPrice.value = state.maxPrice || ''; + + applyFilters(); + } + + function applyFilters() { + let filtered = mockProducts; + + if (filterInStock && filterInStock.checked) { + filtered = filtered.filter(p => p.inStock); + } + if (filterDiscount && filterDiscount.checked) { + filtered = filtered.filter(p => p.discount); + } + if (filterFreeShipping && filterFreeShipping.checked) { + filtered = filtered.filter(p => p.freeShipping); + } + + renderResults(filtered, searchInput ? searchInput.value.trim() : ''); + } + + [filterInStock, filterDiscount, filterFreeShipping, minPrice, maxPrice].forEach(el => { + if (el) { + el.addEventListener('change', applyFilters); + if (el === minPrice || el === maxPrice) { + el.addEventListener('input', applyFilters); + } + } + }); + + function renderPresetsList(presets) { + if (!presetsList) return; + + if (presets.length === 0) { + presetsList.innerHTML = '
  • 暂无保存的预设
  • '; + return; + } + + let html = ''; + presets.forEach(preset => { + html += ` +
  • + 📋 ${preset.name} + +
  • + `; + }); + presetsList.innerHTML = html; + + document.querySelectorAll('.preset-item').forEach(item => { + item.addEventListener('click', function(e) { + if (e.target.classList.contains('delete-preset')) return; + + const presetId = this.dataset.presetId; + const preset = Storage.getPresetById(presetId); + if (preset) { + applyFilterState(preset.filters); + } + }); + }); + + document.querySelectorAll('.delete-preset').forEach(btn => { + btn.addEventListener('click', function(e) { + e.stopPropagation(); + const presetId = this.dataset.presetId; + if (presetId) { + const newPresets = Storage.deletePreset(presetId); + renderPresetsList(newPresets); + } + }); + }); + } + + if (savePresetBtn) { + savePresetBtn.addEventListener('click', function() { + currentFilterState = getCurrentFilterState(); + if (presetModal) { + presetModal.classList.remove('hidden'); + presetNameInput.value = ''; + presetNameInput.focus(); + } + }); + } + + if (cancelPresetBtn) { + cancelPresetBtn.addEventListener('click', function() { + presetModal.classList.add('hidden'); + }); + } + + if (confirmPresetBtn) { + confirmPresetBtn.addEventListener('click', function() { + const presetName = presetNameInput.value.trim(); + if (!presetName) { + alert('给预设起个名字吧~'); + return; + } + + Storage.addPreset(presetName, currentFilterState); + + const presets = Storage.getPresets(); + renderPresetsList(presets); + + presetModal.classList.add('hidden'); + }); + } + + if (presetModal) { + presetModal.addEventListener('click', function(e) { + if (e.target === presetModal) { + presetModal.classList.add('hidden'); + } + }); + } + + initFromStorage(); +}); diff --git a/search-filter/js/storage.js b/search-filter/js/storage.js new file mode 100644 index 00000000..b0e9d8a3 --- /dev/null +++ b/search-filter/js/storage.js @@ -0,0 +1,107 @@ +// ============================================ +// storage.js - 糖星云的数据存储模块 +// 管理搜索历史 + 筛选预设的 localStorage 读写 +// ============================================ + +const Storage = { + // 存储键名常量 + KEYS: { + SEARCH_HISTORY: 'searchHistory', + FILTER_PRESETS: 'filterPresets' + }, + + // ---------- 搜索历史 ---------- + // 获取所有历史记录 + getHistory: function() { + const history = localStorage.getItem(this.KEYS.SEARCH_HISTORY); + return history ? JSON.parse(history) : []; + }, + + // 保存历史记录(最多20条,按时间倒序) + saveHistory: function(historyArray) { + // 确保不超过20条 + const limited = historyArray.slice(0, 20); + localStorage.setItem(this.KEYS.SEARCH_HISTORY, JSON.stringify(limited)); + }, + + // 添加一条新搜索记录 + addHistoryItem: function(keyword) { + if (!keyword || keyword.trim() === '') return; + + let history = this.getHistory(); + const trimmedKeyword = keyword.trim(); + + // 移除已存在的相同关键词(避免重复) + history = history.filter(item => item !== trimmedKeyword); + + // 插入到最前面 + history.unshift(trimmedKeyword); + + // 保存 + this.saveHistory(history); + return history; + }, + + // 删除单条历史 + deleteHistoryItem: function(keyword) { + let history = this.getHistory(); + history = history.filter(item => item !== keyword); + this.saveHistory(history); + return history; + }, + + // 清空全部历史 + clearAllHistory: function() { + localStorage.removeItem(this.KEYS.SEARCH_HISTORY); + return []; + }, + + // ---------- 筛选预设 ---------- + // 获取所有预设 + getPresets: function() { + const presets = localStorage.getItem(this.KEYS.FILTER_PRESETS); + return presets ? JSON.parse(presets) : []; + }, + + // 保存预设数组 + savePresets: function(presetsArray) { + localStorage.setItem(this.KEYS.FILTER_PRESETS, JSON.stringify(presetsArray)); + }, + + // 添加新预设 + addPreset: function(name, filterState) { + if (!name || name.trim() === '') return; + + const presets = this.getPresets(); + const newPreset = { + id: Date.now().toString(), + name: name.trim(), + filters: { ...filterState } + }; + + presets.push(newPreset); + this.savePresets(presets); + return presets; + }, + + // 删除预设 + deletePreset: function(presetId) { + let presets = this.getPresets(); + presets = presets.filter(p => p.id !== presetId); + this.savePresets(presets); + return presets; + }, + + // 根据id获取预设 + getPresetById: function(presetId) { + const presets = this.getPresets(); + return presets.find(p => p.id === presetId) || null; + } +}; + +// 确保 Storage 对象全局可用 +window.HoloLake = window.HoloLake || {}; +window.HoloLake.Storage = Storage; + +// 加一行测试输出,看看有没有加载成功 +console.log('✅ storage.js 已加载', Storage);