添加登录逻辑
This commit is contained in:
parent
9c33a98984
commit
a12727cee4
@ -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
59
login.html
Normal 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
42
login.js
Normal 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();
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -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",
|
||||||
|
|||||||
@ -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
116
popup.js
@ -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("后端返回 401,token 无效,登出");
|
||||||
|
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();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
63
styles.css
63
styles.css
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user