This commit is contained in:
2026-09-15 00:31:34 +08:00
parent c4b08be4eb
commit 9fa3c63d7d
5 changed files with 231 additions and 12 deletions
+16
View File
@@ -271,6 +271,18 @@
"message": "Import",
"description": "Import button"
},
"searchBtnTitle": {
"message": "Search",
"description": "Search button title"
},
"searchPlaceholder": {
"message": "Search domain / username / alias",
"description": "Search box placeholder"
},
"noSearchResult": {
"message": "No matching results",
"description": "No search result hint"
},
"fillCurrentBtn": {
"message": "Fill Current Page",
"description": "Fill current page button"
@@ -351,6 +363,10 @@
"message": "OK",
"description": "Confirm button"
},
"okBtn": {
"message": "OK",
"description": "OK button (submit in dialog)"
},
"envWarnEmpty": {
"message": "Environment name cannot be empty",
"description": "Env name empty warning"
+16
View File
@@ -271,6 +271,18 @@
"message": "导入",
"description": "导入按钮"
},
"searchBtnTitle": {
"message": "搜索",
"description": "搜索按钮 title"
},
"searchPlaceholder": {
"message": "搜索域名 / 用户名 / 别名",
"description": "搜索框 placeholder"
},
"noSearchResult": {
"message": "无匹配结果",
"description": "搜索无结果提示"
},
"fillCurrentBtn": {
"message": "在当前页面填充",
"description": "在当前页面填充按钮"
@@ -351,6 +363,10 @@
"message": "确认",
"description": "确认按钮"
},
"okBtn": {
"message": "确定",
"description": "确定按钮(弹窗内提交)"
},
"envWarnEmpty": {
"message": "环境名称不能为空",
"description": "环境名称为空警告"
+78 -1
View File
@@ -18,10 +18,18 @@ body {
padding: 16px;
display: flex;
flex-direction: column;
min-height: 100vh;
height: 100vh;
overflow-y: auto;
box-sizing: border-box;
}
header,
.add-section,
.current-section,
.footer {
flex-shrink: 0;
}
header h1 {
font-size: 18px;
text-align: center;
@@ -166,6 +174,20 @@ h2 {
margin-bottom: 0;
}
/* 添加配置表单收起态:有配置时仅显示入口按钮 */
#addEntryBtn {
display: none;
}
.add-section.collapsed #addEntryBtn {
display: block;
}
.add-section.collapsed .add-section-header,
.add-section.collapsed > .form-group,
.add-section.collapsed > .advanced-options,
.add-section.collapsed > #addBtn {
display: none;
}
#cancelEditBtn {
padding: 4px 12px;
}
@@ -176,12 +198,67 @@ h2 {
display: flex;
flex-direction: column;
min-height: 0;
position: relative;
}
.list-header {
flex-shrink: 0;
}
/* 图标按钮 */
.btn-icon {
padding: 5px;
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 0;
}
/* 悬浮搜索框 */
.search-box {
position: absolute;
top: 38px;
right: 0;
z-index: 50;
align-items: center;
gap: 4px;
background: #16213e;
border: 1px solid #0f3460;
border-radius: 6px;
padding: 6px 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
width: 220px;
box-sizing: border-box;
}
.search-box:not([hidden]) {
display: flex;
}
.search-box input {
flex: 1;
min-width: 0;
background: transparent;
border: none;
color: #e0e0e0;
font-size: 13px;
outline: none;
}
.search-clear {
background: none;
border: none;
color: #888;
cursor: pointer;
font-size: 16px;
padding: 0 4px;
line-height: 1;
}
.search-clear:hover {
color: #e94560;
}
.form-group {
margin-bottom: 12px;
}
+18 -3
View File
@@ -43,6 +43,7 @@
</header>
<section class="add-section" id="addSection">
<button type="button" id="addEntryBtn" class="btn-primary" data-i18n="addConfigTitle">添加域名配置</button>
<div class="add-section-header">
<h2 id="addConfigTitle" data-i18n="addConfigTitle">添加域名配置</h2>
<button type="button" id="cancelEditBtn" class="btn-edit" data-i18n="cancelBtn" hidden>取消</button>
@@ -125,11 +126,22 @@
<div class="list-header">
<h2 data-i18n="configuredDomainsTitle">已配置域名</h2>
<div class="list-actions">
<button type="button" id="exportBtn" class="btn-io" data-i18n="exportBtn">导出</button>
<button type="button" id="importBtn" class="btn-io" data-i18n="importBtn">导入</button>
<button type="button" id="searchBtn" class="btn-io btn-icon" data-i18n-title="searchBtnTitle" title="搜索">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
</button>
<button type="button" id="exportBtn" class="btn-io btn-icon" data-i18n-title="exportBtn" title="导出">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"/><path d="M7 8l5-5 5 5"/><path d="M5 17v2a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-2"/></svg>
</button>
<button type="button" id="importBtn" class="btn-io btn-icon" data-i18n-title="importBtn" title="导入">
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 21V9"/><path d="M7 16l5 5 5-5"/><path d="M5 7V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v2"/></svg>
</button>
<input type="file" id="importFile" accept="application/json,.json" hidden>
</div>
</div>
<div id="searchBox" class="search-box" hidden>
<input type="text" id="searchInput" data-i18n-placeholder="searchPlaceholder" placeholder="搜索域名 / 用户名 / 别名">
<button type="button" id="searchClear" class="search-clear">×</button>
</div>
<div id="configList" class="config-list">
</div>
</section>
@@ -226,10 +238,13 @@
<!-- 编辑域名配置对话框 -->
<div id="editConfigModal" class="modal">
<div class="modal-content edit-config-content">
<h3 data-i18n="editConfigTitle">编辑域名配置</h3>
<h3 id="editConfigModalTitle" data-i18n="editConfigTitle">编辑域名配置</h3>
<div class="form-group">
<label for="editDomain" data-i18n="domainLabel">域名 / URL 匹配规则</label>
<div class="domain-wrapper">
<input type="text" id="editDomain" data-i18n-placeholder="domainPlaceholder" placeholder="example.com 或 *.example.com 或 site.com/admin/*">
<button type="button" id="editFillDomain" class="btn-fill-domain" data-i18n="fillDomainBtn" data-i18n-title="fillDomainBtnTitle" title="填入当前标签页域名">填入当前域名</button>
</div>
<small data-i18n="domainHelp">支持通配符 *。带 / 时按 hostname+path 匹配,如 oa.com/login/*;不带 / 时只匹配域名</small>
</div>
<div class="form-group">
+99 -4
View File
@@ -68,6 +68,7 @@ document.addEventListener("DOMContentLoaded", () => {
// 编辑配置弹窗
const editConfigModal = $("editConfigModal");
const editConfigModalTitle = $("editConfigModalTitle");
const editDomainInput = $("editDomain");
const editUsernameInput = $("editUsername");
const editPasswordInput = $("editPassword");
@@ -80,7 +81,14 @@ document.addEventListener("DOMContentLoaded", () => {
const editSubmitSelectorInput = $("editSubmitSelector");
const editConfigCancel = $("editConfigCancel");
const editConfigSave = $("editConfigSave");
const editFillDomainBtn = $("editFillDomain");
let editModalEditingId = null;
const addEntryBtn = $("addEntryBtn");
const searchBtn = $("searchBtn");
const searchBox = $("searchBox");
const searchInput = $("searchInput");
const searchClear = $("searchClear");
let searchKeyword = "";
// 锁定界面
const lockScreen = $("lockScreen");
@@ -646,11 +654,12 @@ document.addEventListener("DOMContentLoaded", () => {
async function renderConfigList() {
const configs = await loadConfigs();
const allConfigs = await loadConfigs();
configList.innerHTML = "";
// 无配置时禁用导出,此时表单在上、空状态的列表在下;有配置时反过来
const isEmpty = configs.length === 0;
const isEmpty = allConfigs.length === 0;
addSection.classList.toggle("collapsed", !isEmpty);
exportBtn.disabled = isEmpty;
exportBtn.classList.toggle("btn-disabled", isEmpty);
const parent = addSection.parentElement;
@@ -678,6 +687,24 @@ document.addEventListener("DOMContentLoaded", () => {
return;
}
// 搜索过滤:支持域名、用户名、别名模糊检索
const kw = searchKeyword.trim().toLowerCase();
const configs = kw
? allConfigs.filter((c) =>
(c.domain || "").toLowerCase().includes(kw) ||
(c.username || "").toLowerCase().includes(kw) ||
(c.alias || "").toLowerCase().includes(kw))
: allConfigs;
if (configs.length === 0) {
configList.innerHTML = `
<div class="empty-state">
<div class="empty-text">${t("noSearchResult")}</div>
</div>
`;
return;
}
configs.forEach((cfg) => {
const item = document.createElement("div");
item.className = "config-item" + (cfg.enabled === false ? " config-disabled" : "");
@@ -724,6 +751,8 @@ document.addEventListener("DOMContentLoaded", () => {
editBtn.textContent = t("editBtn");
editBtn.addEventListener("click", () => {
editModalEditingId = cfg.id;
editConfigModalTitle.textContent = t("editConfigTitle");
editConfigSave.textContent = t("updateConfigBtn");
editDomainInput.value = cfg.domain;
editAliasInput.value = cfg.alias || "";
editUsernameInput.value = cfg.username;
@@ -842,7 +871,6 @@ document.addEventListener("DOMContentLoaded", () => {
}
const config = {
id: editModalEditingId,
domain,
alias: editAliasInput.value.trim() || null,
username,
@@ -856,17 +884,65 @@ document.addEventListener("DOMContentLoaded", () => {
};
const configs = await loadConfigs();
if (editModalEditingId) {
const idx = configs.findIndex((c) => c.id === editModalEditingId);
if (idx !== -1) {
config.id = editModalEditingId;
configs[idx] = config;
}
showToast(t("toastUpdated"));
} else {
config.id = Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
configs.push(config);
showToast(t("toastAdded"));
}
await saveConfigs(configs);
editConfigModal.classList.remove("show");
editModalEditingId = null;
renderConfigList();
renderEnvSelect();
if (envManageModal.classList.contains("show")) renderEnvManageList();
showToast(t("toastUpdated"));
});
// 添加配置入口按钮:打开新增弹窗
addEntryBtn.addEventListener("click", () => {
editModalEditingId = null;
editConfigModalTitle.textContent = t("addConfigTitle");
editConfigSave.textContent = t("okBtn");
editDomainInput.value = "";
editUsernameInput.value = "";
editPasswordInput.value = "";
editAliasInput.value = "";
editAutoSubmitSelect.value = "true";
editEnabledCheckbox.checked = true;
editDelayMsInput.value = "";
editUsernameSelectorInput.value = "";
editPasswordSelectorInput.value = "";
editSubmitSelectorInput.value = "";
editConfigModal.classList.add("show");
editDomainInput.focus();
});
// 搜索按钮:切换悬浮搜索框
searchBtn.addEventListener("click", () => {
searchBox.hidden = !searchBox.hidden;
if (!searchBox.hidden) searchInput.focus();
});
// 搜索输入:实时过滤
searchInput.addEventListener("input", () => {
searchKeyword = searchInput.value;
renderConfigList();
});
// 清除搜索
searchClear.addEventListener("click", () => {
searchInput.value = "";
searchKeyword = "";
renderConfigList();
searchInput.focus();
});
// 编辑配置弹窗:取消
@@ -958,6 +1034,25 @@ document.addEventListener("DOMContentLoaded", () => {
}
});
editFillDomainBtn.addEventListener("click", async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab || !tab.url) {
showToast(t("toastNoTab"));
return;
}
try {
const hostname = new URL(tab.url).hostname;
if (!hostname) {
showToast(t("toastNoDomain"));
return;
}
editDomainInput.value = hostname;
showToast(t("toastDomainFilled", hostname));
} catch {
showToast(t("toastUrlParseFail"));
}
});
// 导出整个 environments 结构(含环境名)
exportBtn.addEventListener("click", async () => {
const { environments } = await loadEnvironments();