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);