添加登录逻辑

This commit is contained in:
luke 2025-05-25 14:21:06 +08:00
parent 9c33a98984
commit a12727cee4
8 changed files with 281 additions and 58 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -1,10 +1,13 @@
chrome.commands.onCommand.addListener(function (command) { // 仅在插件安装时设置默认 popup 页面,不再在 startup 中干扰当前 UI
if (command === "open_plugin") { chrome.runtime.onInstalled.addListener(() => {
chrome.windows.create({ chrome.storage.local.get(["token", "loginAt"], (res) => {
url: "popup.html", const maxAge = 24 * 60 * 60 * 1000; // 1天有效期
type: "popup", const now = Date.now();
width: 640,
height: 800 if (!res.token || !res.loginAt || (now - res.loginAt > maxAge)) {
}); chrome.action.setPopup({ popup: "login.html" });
} else {
chrome.action.setPopup({ popup: "popup.html" });
} }
});
}); });

59
login.html Normal file
View File

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>知识库系统登录</title>
<style>
body {
font-family: sans-serif;
padding: 20px;
width: 300px;
box-sizing: border-box;
}
h2 {
margin-top: 0;
}
.form-group {
display: flex;
flex-direction: column;
gap: 12px;
}
input, button {
width: 100%;
padding: 10px;
box-sizing: border-box;
font-size: 14px;
}
button {
background: #007bff;
color: white;
border: none;
cursor: pointer;
}
button:hover {
background: #0056b3;
}
.error {
color: red;
font-size: 12px;
margin-top: 8px;
}
</style>
</head>
<body>
<h2>知识库系统登录</h2>
<div class="form-group">
<input id="username" placeholder="用户名" />
<input id="password" type="password" placeholder="密码" />
<button id="loginBtn">登录</button>
</div>
<div class="error" id="errorBox"></div>
<script src="login.js"></script>
</body>
</html>

42
login.js Normal file
View File

@ -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();
}
});

View File

@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "渠道应用开发部知识库", "name": "渠道应用开发部知识库",
"version": "1.1", "version": "1.2",
"description": "通过关键词快速搜索内部文档", "description": "通过关键词快速搜索内部文档",
"permissions": ["storage"], "permissions": ["storage"],
"host_permissions": [ "host_permissions": [
@ -11,16 +11,8 @@
"background": { "background": {
"service_worker": "background.js" "service_worker": "background.js"
}, },
"commands": {
"open_plugin": {
"suggested_key": {
"default": "Alt+K"
},
"description": "打开渠道应用开发部知识库插件"
}
},
"action": { "action": {
"default_popup": "popup.html", "default_popup": "login.html",
"default_icon": { "default_icon": {
"16": "icon.png", "16": "icon.png",
"48": "icon.png", "48": "icon.png",

View File

@ -8,9 +8,15 @@
<body> <body>
<div class="header"> <div class="header">
<img src="icon.png" alt="logo" class="logo" /> <img src="icon.png" alt="logo" class="logo" />
<div class="title-box">
<h1>渠道应用开发部知识库</h1> <h1>渠道应用开发部知识库</h1>
<a href="https://oa.ahrcu.com/sys/portal/page.jsp" target="_blank" class="oa-link">跳转OA</a> <a href="https://oa.ahrcu.com/sys/portal/page.jsp" target="_blank" class="oa-link">跳转OA</a>
<div class="user-menu">
<div class="user-avatar" id="userAvatar">👤</div>
<div class="user-dropdown hidden" id="userDropdown">
<div id="userName">未登录</div>
<div id="logoutBtn" class="logout-option">退出登录</div>
</div>
</div> </div>
</div> </div>

116
popup.js
View File

@ -1,4 +1,4 @@
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", async () => {
const input = document.getElementById("searchInput"); const input = document.getElementById("searchInput");
const searchBtn = document.getElementById("searchBtn"); const searchBtn = document.getElementById("searchBtn");
const clearHistoryBtn = document.getElementById("clearHistoryBtn"); const clearHistoryBtn = document.getElementById("clearHistoryBtn");
@ -6,17 +6,48 @@ document.addEventListener("DOMContentLoaded", () => {
const historyBox = document.getElementById("historyTags"); const historyBox = document.getElementById("historyTags");
const historyWrapper = document.getElementById("historyWrapper"); 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 currentPage = 1;
let totalPages = 1; let totalPages = 1;
let lastKeywords = []; let lastKeywords = [];
const TOKEN_EXPIRE_MS = 24 * 60 * 60 * 1000;
function log(...args) { function log(...args) {
try { const version = chrome?.runtime?.getManifest?.().version || "dev";
const version = chrome.runtime?.getManifest?.().version || "dev";
console.log(`[v${version}]`, ...args); console.log(`[v${version}]`, ...args);
} catch (e) {
console.log("[v?]", ...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() { function showLoading() {
@ -50,7 +81,6 @@ document.addEventListener("DOMContentLoaded", () => {
} }
function updateHistory(keyword) { function updateHistory(keyword) {
if (!chrome?.storage?.local) return;
chrome.storage.local.get(["searchHistory"], (res) => { chrome.storage.local.get(["searchHistory"], (res) => {
let history = res.searchHistory || []; let history = res.searchHistory || [];
history = [keyword, ...history.filter(item => item !== keyword)]; history = [keyword, ...history.filter(item => item !== keyword)];
@ -60,7 +90,6 @@ document.addEventListener("DOMContentLoaded", () => {
} }
function renderHistoryTags() { function renderHistoryTags() {
if (!chrome?.storage?.local) return;
chrome.storage.local.get(["searchHistory"], (res) => { chrome.storage.local.get(["searchHistory"], (res) => {
const history = res.searchHistory || []; const history = res.searchHistory || [];
historyBox.innerHTML = ""; historyBox.innerHTML = "";
@ -85,6 +114,14 @@ document.addEventListener("DOMContentLoaded", () => {
} }
async function fetchResults(keywords, page) { async function fetchResults(keywords, page) {
log("执行搜索:", keywords, "page:", page);
if (!token) {
log("token 未初始化,跳转 login");
handleLogout();
return;
}
currentPage = page; currentPage = page;
lastKeywords = keywords; lastKeywords = keywords;
@ -95,18 +132,26 @@ document.addEventListener("DOMContentLoaded", () => {
size: 10 size: 10
}; };
log("触发搜索:", requestBody);
showLoading(); showLoading();
try { try {
const response = await fetch("http://app.wisdompulse.cn/search", { const response = await fetch("http://app.wisdompulse.cn/search", {
method: "POST", method: "POST",
headers: { headers: {
"Authorization": `${token}`,
"Content-Type": "application/json" "Content-Type": "application/json"
}, },
body: JSON.stringify(requestBody) body: JSON.stringify(requestBody)
}); });
log("响应状态:", response.status);
if (response.status === 401) {
log("后端返回 401token 无效,登出");
handleLogout();
return;
}
const data = await response.json(); const data = await response.json();
resultBox.innerHTML = ""; resultBox.innerHTML = "";
@ -137,42 +182,55 @@ document.addEventListener("DOMContentLoaded", () => {
renderPagination(); renderPagination();
} catch (err) { } catch (err) {
console.error("[ERROR] 搜索失败:", err); console.error("搜索请求失败:", err);
resultBox.innerHTML = "<li>搜索失败,请检查网络或服务状态。</li>"; resultBox.innerHTML = "<li>搜索失败,请检查网络或服务状态。</li>";
} }
} }
function triggerSearch() { function triggerSearch() {
const inputValue = input.value.trim(); if (!token) {
if (!inputValue) { log("triggerSearch 中 token 丢失,取消搜索");
clearResults();
return; return;
} }
const keywords = inputValue.split(/\s+/).filter(Boolean); const val = input.value.trim();
updateHistory(inputValue); if (!val) return clearResults();
const keywords = val.split(/\s+/);
updateHistory(val);
fetchResults(keywords, 1); fetchResults(keywords, 1);
} }
function bindEvents() {
log("绑定事件成功");
searchBtn.addEventListener("click", triggerSearch); searchBtn.addEventListener("click", triggerSearch);
input.addEventListener("keydown", e => { input.addEventListener("keydown", (e) => {
if (e.key === "Enter") triggerSearch(); if (e.key === "Enter") {
log("用户按下回车 ⏎");
triggerSearch();
}
}); });
input.addEventListener("focus", renderHistoryTags); input.addEventListener("focus", renderHistoryTags);
input.addEventListener("input", () => { input.addEventListener("input", () => !input.value.trim() && clearResults());
if (!input.value.trim()) clearResults(); input.addEventListener("blur", () => setTimeout(() => historyWrapper.classList.add("hidden"), 200));
});
input.addEventListener("blur", () => {
setTimeout(() => historyWrapper.classList.add("hidden"), 200);
});
clearHistoryBtn.addEventListener("click", () => { clearHistoryBtn.addEventListener("click", () => {
chrome.storage.local.remove("searchHistory", () => { chrome.storage.local.remove("searchHistory", renderHistoryTags);
log("历史记录已清空");
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();
}
}); });

View File

@ -207,3 +207,66 @@ h1 {
background-color: #f0f0f0; background-color: #f0f0f0;
border-radius: 3px; 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;
}