diff --git a/.DS_Store b/.DS_Store index 70fdd9b..b3e0eb6 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/background.js b/background.js index 4cc32a3..bd5877c 100644 --- a/background.js +++ b/background.js @@ -1,10 +1,13 @@ -chrome.commands.onCommand.addListener(function (command) { - if (command === "open_plugin") { - chrome.windows.create({ - url: "popup.html", - type: "popup", - width: 640, - height: 800 - }); - } +// 仅在插件安装时设置默认 popup 页面,不再在 startup 中干扰当前 UI +chrome.runtime.onInstalled.addListener(() => { + chrome.storage.local.get(["token", "loginAt"], (res) => { + const maxAge = 24 * 60 * 60 * 1000; // 1天有效期 + const now = Date.now(); + + if (!res.token || !res.loginAt || (now - res.loginAt > maxAge)) { + chrome.action.setPopup({ popup: "login.html" }); + } else { + chrome.action.setPopup({ popup: "popup.html" }); + } + }); }); diff --git a/login.html b/login.html new file mode 100644 index 0000000..bdc66c4 --- /dev/null +++ b/login.html @@ -0,0 +1,59 @@ + + + + + 知识库系统登录 + + + +

知识库系统登录

+
+ + + +
+
+ + + diff --git a/login.js b/login.js new file mode 100644 index 0000000..b7ff31d --- /dev/null +++ b/login.js @@ -0,0 +1,42 @@ +const TOKEN_EXPIRE_MS = 24 * 60 * 60 * 1000; // 默认保留 1天 + +document.getElementById("loginBtn").addEventListener("click", async () => { + const username = document.getElementById("username").value.trim(); + const password = document.getElementById("password").value.trim(); + const errorBox = document.getElementById("errorBox"); + errorBox.textContent = ""; + + if (!username || !password) { + errorBox.textContent = "请输入用户名和密码"; + return; + } + + try { + const res = await fetch("http://app.wisdompulse.cn/api/v1/user/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username, password }) + }); + + const data = await res.json(); + if (res.ok && data.token) { + await chrome.storage.local.set({ + token: data.token, + username: data.username || username, + loginAt: Date.now() + }); + chrome.action.setPopup({ popup: "popup.html" }); + window.location.href = "popup.html"; + } else { + errorBox.textContent = data.message || "登录失败"; + } + } catch (err) { + errorBox.textContent = "网络错误,请稍后重试"; + } +}); + +document.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + document.getElementById("loginBtn").click(); + } +}); diff --git a/manifest.json b/manifest.json index 8e21a7e..53e49f6 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "渠道应用开发部知识库", - "version": "1.1", + "version": "1.2", "description": "通过关键词快速搜索内部文档", "permissions": ["storage"], "host_permissions": [ @@ -11,16 +11,8 @@ "background": { "service_worker": "background.js" }, - "commands": { - "open_plugin": { - "suggested_key": { - "default": "Alt+K" - }, - "description": "打开渠道应用开发部知识库插件" - } - }, "action": { - "default_popup": "popup.html", + "default_popup": "login.html", "default_icon": { "16": "icon.png", "48": "icon.png", diff --git a/popup.html b/popup.html index adbf4a5..1bf4206 100644 --- a/popup.html +++ b/popup.html @@ -8,9 +8,15 @@
-
-

渠道应用开发部知识库

- 跳转OA +

渠道应用开发部知识库

+ 跳转OA + +
+
👤
+
diff --git a/popup.js b/popup.js index bafb03d..5753a38 100644 --- a/popup.js +++ b/popup.js @@ -1,4 +1,4 @@ -document.addEventListener("DOMContentLoaded", () => { +document.addEventListener("DOMContentLoaded", async () => { const input = document.getElementById("searchInput"); const searchBtn = document.getElementById("searchBtn"); const clearHistoryBtn = document.getElementById("clearHistoryBtn"); @@ -6,17 +6,48 @@ document.addEventListener("DOMContentLoaded", () => { const historyBox = document.getElementById("historyTags"); const historyWrapper = document.getElementById("historyWrapper"); + const userDropdown = document.getElementById("userDropdown"); + const logoutBtn = document.getElementById("logoutBtn"); + const userNameLabel = document.getElementById("userName"); + const userAvatar = document.getElementById("userAvatar"); + + let token = null; let currentPage = 1; let totalPages = 1; let lastKeywords = []; + const TOKEN_EXPIRE_MS = 24 * 60 * 60 * 1000; + function log(...args) { - try { - const version = chrome.runtime?.getManifest?.().version || "dev"; - console.log(`[v${version}]`, ...args); - } catch (e) { - console.log("[v?]", ...args); - } + const version = chrome?.runtime?.getManifest?.().version || "dev"; + console.log(`[v${version}]`, ...args); + } + + async function loadToken() { + return new Promise((resolve) => { + chrome.storage.local.get(["token", "loginAt", "username"], (res) => { + log("加载本地存储 token:", res); + const now = Date.now(); + if (!res.token || !res.loginAt || (now - res.loginAt > TOKEN_EXPIRE_MS)) { + log("token 不存在或已过期"); + resolve(null); + } else { + log("token 加载成功"); + resolve({ + token: res.token, + username: res.username || "用户" + }); + } + }); + }); + } + + function handleLogout() { + log("执行退出登录逻辑"); + chrome.storage.local.remove(["token", "loginAt", "username"], () => { + chrome.action.setPopup({ popup: "login.html" }); + window.location.href = "login.html"; + }); } function showLoading() { @@ -50,7 +81,6 @@ document.addEventListener("DOMContentLoaded", () => { } function updateHistory(keyword) { - if (!chrome?.storage?.local) return; chrome.storage.local.get(["searchHistory"], (res) => { let history = res.searchHistory || []; history = [keyword, ...history.filter(item => item !== keyword)]; @@ -60,7 +90,6 @@ document.addEventListener("DOMContentLoaded", () => { } function renderHistoryTags() { - if (!chrome?.storage?.local) return; chrome.storage.local.get(["searchHistory"], (res) => { const history = res.searchHistory || []; historyBox.innerHTML = ""; @@ -85,6 +114,14 @@ document.addEventListener("DOMContentLoaded", () => { } async function fetchResults(keywords, page) { + log("执行搜索:", keywords, "page:", page); + + if (!token) { + log("token 未初始化,跳转 login"); + handleLogout(); + return; + } + currentPage = page; lastKeywords = keywords; @@ -95,18 +132,26 @@ document.addEventListener("DOMContentLoaded", () => { size: 10 }; - log("触发搜索:", requestBody); showLoading(); try { const response = await fetch("http://app.wisdompulse.cn/search", { method: "POST", headers: { + "Authorization": `${token}`, "Content-Type": "application/json" }, body: JSON.stringify(requestBody) }); + log("响应状态:", response.status); + + if (response.status === 401) { + log("后端返回 401,token 无效,登出"); + handleLogout(); + return; + } + const data = await response.json(); resultBox.innerHTML = ""; @@ -137,42 +182,55 @@ document.addEventListener("DOMContentLoaded", () => { renderPagination(); } catch (err) { - console.error("[ERROR] 搜索失败:", err); + console.error("搜索请求失败:", err); resultBox.innerHTML = "
  • 搜索失败,请检查网络或服务状态。
  • "; } } function triggerSearch() { - const inputValue = input.value.trim(); - if (!inputValue) { - clearResults(); + if (!token) { + log("triggerSearch 中 token 丢失,取消搜索"); return; } - const keywords = inputValue.split(/\s+/).filter(Boolean); - updateHistory(inputValue); + const val = input.value.trim(); + if (!val) return clearResults(); + const keywords = val.split(/\s+/); + updateHistory(val); fetchResults(keywords, 1); } - searchBtn.addEventListener("click", triggerSearch); - input.addEventListener("keydown", e => { - if (e.key === "Enter") triggerSearch(); - }); - - input.addEventListener("focus", renderHistoryTags); - input.addEventListener("input", () => { - if (!input.value.trim()) clearResults(); - }); - - input.addEventListener("blur", () => { - setTimeout(() => historyWrapper.classList.add("hidden"), 200); - }); - - clearHistoryBtn.addEventListener("click", () => { - chrome.storage.local.remove("searchHistory", () => { - log("历史记录已清空"); - renderHistoryTags(); + function bindEvents() { + log("绑定事件成功"); + searchBtn.addEventListener("click", triggerSearch); + input.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + log("用户按下回车 ⏎"); + triggerSearch(); + } }); - }); + input.addEventListener("focus", renderHistoryTags); + input.addEventListener("input", () => !input.value.trim() && clearResults()); + input.addEventListener("blur", () => setTimeout(() => historyWrapper.classList.add("hidden"), 200)); + clearHistoryBtn.addEventListener("click", () => { + chrome.storage.local.remove("searchHistory", renderHistoryTags); + }); + logoutBtn.addEventListener("click", handleLogout); + userAvatar.addEventListener("click", () => userDropdown.classList.toggle("hidden")); + window.addEventListener("click", (e) => { + if (!userDropdown.contains(e.target) && e.target !== userAvatar) { + userDropdown.classList.add("hidden"); + } + }); + } - log("插件初始化完成"); + const auth = await loadToken(); + if (!auth) { + log("未通过认证,执行登出"); + handleLogout(); + } else { + token = auth.token; + userNameLabel.textContent = auth.username; + log("用户认证通过,用户名:", auth.username); + bindEvents(); + } }); diff --git a/styles.css b/styles.css index 24a97fb..b0de8e9 100644 --- a/styles.css +++ b/styles.css @@ -207,3 +207,66 @@ h1 { background-color: #f0f0f0; border-radius: 3px; } + +.user-info { + font-size: 13px; + color: #333; + margin-left: 10px; + font-weight: normal; +} + +.logout-btn { + margin-left: 10px; + padding: 4px 10px; + font-size: 12px; + border: 1px solid #ccc; + background: #f9f9f9; + color: #333; + border-radius: 4px; + cursor: pointer; +} + +.logout-btn:hover { + background-color: #f0f0f0; + color: #d00; +} + +.user-menu { + position: absolute; + top: 16px; + right: 16px; +} + +.user-avatar { + cursor: pointer; + font-size: 18px; + padding: 4px; +} + +.user-dropdown { + position: absolute; + right: 0; + top: 30px; + background: white; + border: 1px solid #ccc; + box-shadow: 0 0 4px rgba(0,0,0,0.1); + padding: 6px 10px; + font-size: 13px; + width: 120px; + z-index: 99; +} + +.user-dropdown div { + padding: 6px 0; + cursor: pointer; +} + +.user-dropdown div:hover { + background-color: #f0f0f0; +} + +.hidden { + display: none; +} + +