// 联系人自动补全模块 const _acCache = {}; async function acFetchNames(field) { if (_acCache[field] && Date.now() - _acCache[field].ts < 60000) return _acCache[field].data; try { const res = await fetch(API + '/api/contacts?field=' + field, { headers: headers() }); if (!res.ok) return []; const data = await res.json(); _acCache[field] = { data, ts: Date.now() }; return data; } catch { return []; } } function acInvalidateCache(field) { if (field) delete _acCache[field]; else Object.keys(_acCache).forEach(k => delete _acCache[k]); } function acSetup(inputId, field) { const input = document.getElementById(inputId); if (!input || input._acBound) return; input._acBound = true; input.setAttribute('autocomplete', 'off'); const wrap = document.createElement('div'); wrap.className = 'ac-wrap'; input.parentNode.insertBefore(wrap, input); wrap.appendChild(input); const list = document.createElement('div'); list.className = 'ac-list'; wrap.appendChild(list); let names = []; let activeIdx = -1; async function loadNames() { names = await acFetchNames(field); } function render(query) { const q = query.trim().toLowerCase(); const filtered = q ? names.filter(n => n.toLowerCase().includes(q)) : names; if (filtered.length === 0) { list.classList.remove('show'); return; } activeIdx = -1; list.innerHTML = filtered.slice(0, 10).map((n, i) => { let display = n; if (q) { const idx = n.toLowerCase().indexOf(q); if (idx >= 0) { display = n.slice(0, idx) + '' + n.slice(idx, idx + q.length) + '' + n.slice(idx + q.length); } } return `