Commit a2fd1661b182e9d6142a435536f8ed5e2568f6ec

Authored by tangwang
1 parent b50d11cd

前端支持sugg,对接到另外单独的sugg服务 curl "http://localhost:5003/suggest?query=测试&lang=zh&limit=10"

Showing 1 changed file with 209 additions and 3 deletions   Show diff stats
frontend/index.html
... ... @@ -5,6 +5,44 @@
5 5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 6 <title>Product Search - for Shoplazza</title>
7 7 <link rel="stylesheet" href="/static/css/style.css">
  8 + <style>
  9 + .suggestions-list {
  10 + position: absolute;
  11 + top: 100%;
  12 + left: 0;
  13 + right: 0;
  14 + background: white;
  15 + border: 1px solid #ddd;
  16 + border-radius: 4px;
  17 + box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  18 + max-height: 300px;
  19 + overflow-y: auto;
  20 + z-index: 1000;
  21 + margin-top: 2px;
  22 + }
  23 + .suggestion-item {
  24 + padding: 10px 15px;
  25 + cursor: pointer;
  26 + border-bottom: 1px solid #f0f0f0;
  27 + transition: background-color 0.2s;
  28 + }
  29 + .suggestion-item:hover,
  30 + .suggestion-item.highlighted {
  31 + background-color: #f5f5f5;
  32 + }
  33 + .suggestion-item:last-child {
  34 + border-bottom: none;
  35 + }
  36 + .suggestion-text {
  37 + font-size: 14px;
  38 + color: #333;
  39 + }
  40 + .suggestion-meta {
  41 + font-size: 12px;
  42 + color: #999;
  43 + margin-top: 2px;
  44 + }
  45 + </style>
8 46 </head>
9 47 <body>
10 48 <div class="page-container">
... ... @@ -29,8 +67,11 @@
29 67 <label for="skuFilterDimension">sku_filter_dimension:</label>
30 68 <input type="text" id="skuFilterDimension" placeholder="SKU筛选维度" value="color">
31 69 </div>
32   - <input type="text" id="searchInput" placeholder="输入搜索关键词... (支持中文、英文、俄文)"
33   - onkeypress="handleKeyPress(event)">
  70 + <div class="search-input-wrapper" style="position: relative;">
  71 + <input type="text" id="searchInput" placeholder="输入搜索关键词... (支持中文、英文、俄文)"
  72 + onkeypress="handleKeyPress(event)" oninput="handleSearchInput(event)">
  73 + <div id="suggestionsList" class="suggestions-list" style="display: none;"></div>
  74 + </div>
34 75 <button onclick="performSearch()" class="search-btn">Search</button>
35 76 </div>
36 77  
... ... @@ -142,6 +183,171 @@
142 183 <p>SearchEngine © 2025 | API: <span id="apiUrl">Loading...</span></p>
143 184 </footer>
144 185  
145   - <script src="/static/js/app.js?v=3.1"></script>
  186 + <script src="/static/js/app.js?v=3.2"></script>
  187 + <script>
  188 + // 自动补全功能
  189 + const SUGGEST_API = 'http://120.76.41.98:5003/suggest';
  190 + let debounceTimer = null;
  191 + let currentSuggestions = [];
  192 + let selectedIndex = -1;
  193 + let abortController = null;
  194 +
  195 + // 防抖函数
  196 + function debounce(func, wait) {
  197 + return function(...args) {
  198 + clearTimeout(debounceTimer);
  199 + debounceTimer = setTimeout(() => func.apply(this, args), wait);
  200 + };
  201 + }
  202 +
  203 + // 获取建议
  204 + async function fetchSuggestions(query) {
  205 + if (!query || query.trim().length === 0) {
  206 + hideSuggestions();
  207 + return;
  208 + }
  209 +
  210 + // 取消之前的请求
  211 + if (abortController) {
  212 + abortController.abort();
  213 + }
  214 + abortController = new AbortController();
  215 +
  216 + try {
  217 + const url = new URL(SUGGEST_API);
  218 + url.searchParams.set('query', query);
  219 + url.searchParams.set('lang', 'zh');
  220 + url.searchParams.set('limit', '40');
  221 +
  222 + const response = await fetch(url.toString(), {
  223 + signal: abortController.signal,
  224 + headers: {
  225 + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
  226 + 'Referer': window.location.origin + '/'
  227 + }
  228 + });
  229 +
  230 + if (!response.ok) {
  231 + throw new Error('请求失败');
  232 + }
  233 +
  234 + const data = await response.json();
  235 + if (data.status === 'success' && data.suggestions) {
  236 + currentSuggestions = data.suggestions;
  237 + showSuggestions(data.suggestions);
  238 + } else {
  239 + hideSuggestions();
  240 + }
  241 + } catch (error) {
  242 + if (error.name !== 'AbortError') {
  243 + console.error('获取建议失败:', error);
  244 + hideSuggestions();
  245 + }
  246 + }
  247 + }
  248 +
  249 + // 显示建议列表
  250 + function showSuggestions(suggestions) {
  251 + const list = document.getElementById('suggestionsList');
  252 + if (!suggestions || suggestions.length === 0) {
  253 + hideSuggestions();
  254 + return;
  255 + }
  256 +
  257 + list.innerHTML = '';
  258 + suggestions.forEach((item, index) => {
  259 + const div = document.createElement('div');
  260 + div.className = 'suggestion-item';
  261 + div.dataset.index = index;
  262 + div.innerHTML = `
  263 + <div class="suggestion-text">${escapeHtml(item.canon)}</div>
  264 + <div class="suggestion-meta">${item.entry_type} | 频次: ${item.canon_freq}</div>
  265 + `;
  266 + div.onclick = () => selectSuggestion(item.canon);
  267 + div.onmouseenter = () => {
  268 + selectedIndex = index;
  269 + updateHighlight();
  270 + };
  271 + list.appendChild(div);
  272 + });
  273 +
  274 + list.style.display = 'block';
  275 + selectedIndex = -1;
  276 + }
  277 +
  278 + // 隐藏建议列表
  279 + function hideSuggestions() {
  280 + const list = document.getElementById('suggestionsList');
  281 + list.style.display = 'none';
  282 + currentSuggestions = [];
  283 + selectedIndex = -1;
  284 + }
  285 +
  286 + // 选择建议
  287 + function selectSuggestion(text) {
  288 + const input = document.getElementById('searchInput');
  289 + input.value = text;
  290 + hideSuggestions();
  291 + performSearch();
  292 + }
  293 +
  294 + // 更新高亮
  295 + function updateHighlight() {
  296 + const items = document.querySelectorAll('.suggestion-item');
  297 + items.forEach((item, index) => {
  298 + if (index === selectedIndex) {
  299 + item.classList.add('highlighted');
  300 + } else {
  301 + item.classList.remove('highlighted');
  302 + }
  303 + });
  304 + }
  305 +
  306 + // HTML转义
  307 + function escapeHtml(text) {
  308 + const div = document.createElement('div');
  309 + div.textContent = text;
  310 + return div.innerHTML;
  311 + }
  312 +
  313 + // 处理输入事件
  314 + const debouncedFetch = debounce(fetchSuggestions, 300);
  315 + function handleSearchInput(event) {
  316 + const query = event.target.value;
  317 + debouncedFetch(query);
  318 + }
  319 +
  320 + // 键盘导航
  321 + document.addEventListener('keydown', (e) => {
  322 + const list = document.getElementById('suggestionsList');
  323 + if (list.style.display === 'none' || currentSuggestions.length === 0) {
  324 + return;
  325 + }
  326 +
  327 + if (e.key === 'ArrowDown') {
  328 + e.preventDefault();
  329 + selectedIndex = (selectedIndex + 1) % currentSuggestions.length;
  330 + updateHighlight();
  331 + } else if (e.key === 'ArrowUp') {
  332 + e.preventDefault();
  333 + selectedIndex = selectedIndex <= 0 ? currentSuggestions.length - 1 : selectedIndex - 1;
  334 + updateHighlight();
  335 + } else if (e.key === 'Enter' && selectedIndex >= 0) {
  336 + e.preventDefault();
  337 + selectSuggestion(currentSuggestions[selectedIndex].canon);
  338 + } else if (e.key === 'Escape') {
  339 + hideSuggestions();
  340 + }
  341 + });
  342 +
  343 + // 点击外部关闭建议列表
  344 + document.addEventListener('click', (e) => {
  345 + const list = document.getElementById('suggestionsList');
  346 + const input = document.getElementById('searchInput');
  347 + if (!list.contains(e.target) && e.target !== input) {
  348 + hideSuggestions();
  349 + }
  350 + });
  351 + </script>
146 352 </body>
147 353 </html>
... ...