0
This commit is contained in:
@@ -271,6 +271,18 @@
|
|||||||
"message": "Import",
|
"message": "Import",
|
||||||
"description": "Import button"
|
"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": {
|
"fillCurrentBtn": {
|
||||||
"message": "Fill Current Page",
|
"message": "Fill Current Page",
|
||||||
"description": "Fill current page button"
|
"description": "Fill current page button"
|
||||||
@@ -351,6 +363,10 @@
|
|||||||
"message": "OK",
|
"message": "OK",
|
||||||
"description": "Confirm button"
|
"description": "Confirm button"
|
||||||
},
|
},
|
||||||
|
"okBtn": {
|
||||||
|
"message": "OK",
|
||||||
|
"description": "OK button (submit in dialog)"
|
||||||
|
},
|
||||||
"envWarnEmpty": {
|
"envWarnEmpty": {
|
||||||
"message": "Environment name cannot be empty",
|
"message": "Environment name cannot be empty",
|
||||||
"description": "Env name empty warning"
|
"description": "Env name empty warning"
|
||||||
|
|||||||
@@ -271,6 +271,18 @@
|
|||||||
"message": "导入",
|
"message": "导入",
|
||||||
"description": "导入按钮"
|
"description": "导入按钮"
|
||||||
},
|
},
|
||||||
|
"searchBtnTitle": {
|
||||||
|
"message": "搜索",
|
||||||
|
"description": "搜索按钮 title"
|
||||||
|
},
|
||||||
|
"searchPlaceholder": {
|
||||||
|
"message": "搜索域名 / 用户名 / 别名",
|
||||||
|
"description": "搜索框 placeholder"
|
||||||
|
},
|
||||||
|
"noSearchResult": {
|
||||||
|
"message": "无匹配结果",
|
||||||
|
"description": "搜索无结果提示"
|
||||||
|
},
|
||||||
"fillCurrentBtn": {
|
"fillCurrentBtn": {
|
||||||
"message": "在当前页面填充",
|
"message": "在当前页面填充",
|
||||||
"description": "在当前页面填充按钮"
|
"description": "在当前页面填充按钮"
|
||||||
@@ -351,6 +363,10 @@
|
|||||||
"message": "确认",
|
"message": "确认",
|
||||||
"description": "确认按钮"
|
"description": "确认按钮"
|
||||||
},
|
},
|
||||||
|
"okBtn": {
|
||||||
|
"message": "确定",
|
||||||
|
"description": "确定按钮(弹窗内提交)"
|
||||||
|
},
|
||||||
"envWarnEmpty": {
|
"envWarnEmpty": {
|
||||||
"message": "环境名称不能为空",
|
"message": "环境名称不能为空",
|
||||||
"description": "环境名称为空警告"
|
"description": "环境名称为空警告"
|
||||||
|
|||||||
@@ -18,10 +18,18 @@ body {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 100vh;
|
height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header,
|
||||||
|
.add-section,
|
||||||
|
.current-section,
|
||||||
|
.footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
header h1 {
|
header h1 {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -166,6 +174,20 @@ h2 {
|
|||||||
margin-bottom: 0;
|
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 {
|
#cancelEditBtn {
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
}
|
}
|
||||||
@@ -176,12 +198,67 @@ h2 {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-header {
|
.list-header {
|
||||||
flex-shrink: 0;
|
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 {
|
.form-group {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-4
@@ -43,6 +43,7 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="add-section" id="addSection">
|
<section class="add-section" id="addSection">
|
||||||
|
<button type="button" id="addEntryBtn" class="btn-primary" data-i18n="addConfigTitle">添加域名配置</button>
|
||||||
<div class="add-section-header">
|
<div class="add-section-header">
|
||||||
<h2 id="addConfigTitle" data-i18n="addConfigTitle">添加域名配置</h2>
|
<h2 id="addConfigTitle" data-i18n="addConfigTitle">添加域名配置</h2>
|
||||||
<button type="button" id="cancelEditBtn" class="btn-edit" data-i18n="cancelBtn" hidden>取消</button>
|
<button type="button" id="cancelEditBtn" class="btn-edit" data-i18n="cancelBtn" hidden>取消</button>
|
||||||
@@ -125,11 +126,22 @@
|
|||||||
<div class="list-header">
|
<div class="list-header">
|
||||||
<h2 data-i18n="configuredDomainsTitle">已配置域名</h2>
|
<h2 data-i18n="configuredDomainsTitle">已配置域名</h2>
|
||||||
<div class="list-actions">
|
<div class="list-actions">
|
||||||
<button type="button" id="exportBtn" class="btn-io" data-i18n="exportBtn">导出</button>
|
<button type="button" id="searchBtn" class="btn-io btn-icon" data-i18n-title="searchBtnTitle" title="搜索">
|
||||||
<button type="button" id="importBtn" class="btn-io" data-i18n="importBtn">导入</button>
|
<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>
|
<input type="file" id="importFile" accept="application/json,.json" hidden>
|
||||||
</div>
|
</div>
|
||||||
</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 id="configList" class="config-list">
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -226,10 +238,13 @@
|
|||||||
<!-- 编辑域名配置对话框 -->
|
<!-- 编辑域名配置对话框 -->
|
||||||
<div id="editConfigModal" class="modal">
|
<div id="editConfigModal" class="modal">
|
||||||
<div class="modal-content edit-config-content">
|
<div class="modal-content edit-config-content">
|
||||||
<h3 data-i18n="editConfigTitle">编辑域名配置</h3>
|
<h3 id="editConfigModalTitle" data-i18n="editConfigTitle">编辑域名配置</h3>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="editDomain" data-i18n="domainLabel">域名 / URL 匹配规则</label>
|
<label for="editDomain" data-i18n="domainLabel">域名 / URL 匹配规则</label>
|
||||||
<input type="text" id="editDomain" data-i18n-placeholder="domainPlaceholder" placeholder="example.com 或 *.example.com 或 site.com/admin/*">
|
<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>
|
<small data-i18n="domainHelp">支持通配符 *。带 / 时按 hostname+path 匹配,如 oa.com/login/*;不带 / 时只匹配域名</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
// 编辑配置弹窗
|
// 编辑配置弹窗
|
||||||
const editConfigModal = $("editConfigModal");
|
const editConfigModal = $("editConfigModal");
|
||||||
|
const editConfigModalTitle = $("editConfigModalTitle");
|
||||||
const editDomainInput = $("editDomain");
|
const editDomainInput = $("editDomain");
|
||||||
const editUsernameInput = $("editUsername");
|
const editUsernameInput = $("editUsername");
|
||||||
const editPasswordInput = $("editPassword");
|
const editPasswordInput = $("editPassword");
|
||||||
@@ -80,7 +81,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const editSubmitSelectorInput = $("editSubmitSelector");
|
const editSubmitSelectorInput = $("editSubmitSelector");
|
||||||
const editConfigCancel = $("editConfigCancel");
|
const editConfigCancel = $("editConfigCancel");
|
||||||
const editConfigSave = $("editConfigSave");
|
const editConfigSave = $("editConfigSave");
|
||||||
|
const editFillDomainBtn = $("editFillDomain");
|
||||||
let editModalEditingId = null;
|
let editModalEditingId = null;
|
||||||
|
const addEntryBtn = $("addEntryBtn");
|
||||||
|
const searchBtn = $("searchBtn");
|
||||||
|
const searchBox = $("searchBox");
|
||||||
|
const searchInput = $("searchInput");
|
||||||
|
const searchClear = $("searchClear");
|
||||||
|
let searchKeyword = "";
|
||||||
|
|
||||||
// 锁定界面
|
// 锁定界面
|
||||||
const lockScreen = $("lockScreen");
|
const lockScreen = $("lockScreen");
|
||||||
@@ -646,11 +654,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
|
|
||||||
async function renderConfigList() {
|
async function renderConfigList() {
|
||||||
const configs = await loadConfigs();
|
const allConfigs = await loadConfigs();
|
||||||
configList.innerHTML = "";
|
configList.innerHTML = "";
|
||||||
|
|
||||||
// 无配置时禁用导出,此时表单在上、空状态的列表在下;有配置时反过来
|
// 无配置时禁用导出,此时表单在上、空状态的列表在下;有配置时反过来
|
||||||
const isEmpty = configs.length === 0;
|
const isEmpty = allConfigs.length === 0;
|
||||||
|
addSection.classList.toggle("collapsed", !isEmpty);
|
||||||
exportBtn.disabled = isEmpty;
|
exportBtn.disabled = isEmpty;
|
||||||
exportBtn.classList.toggle("btn-disabled", isEmpty);
|
exportBtn.classList.toggle("btn-disabled", isEmpty);
|
||||||
const parent = addSection.parentElement;
|
const parent = addSection.parentElement;
|
||||||
@@ -678,6 +687,24 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
return;
|
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) => {
|
configs.forEach((cfg) => {
|
||||||
const item = document.createElement("div");
|
const item = document.createElement("div");
|
||||||
item.className = "config-item" + (cfg.enabled === false ? " config-disabled" : "");
|
item.className = "config-item" + (cfg.enabled === false ? " config-disabled" : "");
|
||||||
@@ -724,6 +751,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
editBtn.textContent = t("editBtn");
|
editBtn.textContent = t("editBtn");
|
||||||
editBtn.addEventListener("click", () => {
|
editBtn.addEventListener("click", () => {
|
||||||
editModalEditingId = cfg.id;
|
editModalEditingId = cfg.id;
|
||||||
|
editConfigModalTitle.textContent = t("editConfigTitle");
|
||||||
|
editConfigSave.textContent = t("updateConfigBtn");
|
||||||
editDomainInput.value = cfg.domain;
|
editDomainInput.value = cfg.domain;
|
||||||
editAliasInput.value = cfg.alias || "";
|
editAliasInput.value = cfg.alias || "";
|
||||||
editUsernameInput.value = cfg.username;
|
editUsernameInput.value = cfg.username;
|
||||||
@@ -842,7 +871,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
id: editModalEditingId,
|
|
||||||
domain,
|
domain,
|
||||||
alias: editAliasInput.value.trim() || null,
|
alias: editAliasInput.value.trim() || null,
|
||||||
username,
|
username,
|
||||||
@@ -856,17 +884,65 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const configs = await loadConfigs();
|
const configs = await loadConfigs();
|
||||||
const idx = configs.findIndex((c) => c.id === editModalEditingId);
|
|
||||||
if (idx !== -1) {
|
if (editModalEditingId) {
|
||||||
configs[idx] = config;
|
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);
|
await saveConfigs(configs);
|
||||||
editConfigModal.classList.remove("show");
|
editConfigModal.classList.remove("show");
|
||||||
editModalEditingId = null;
|
editModalEditingId = null;
|
||||||
renderConfigList();
|
renderConfigList();
|
||||||
renderEnvSelect();
|
renderEnvSelect();
|
||||||
if (envManageModal.classList.contains("show")) renderEnvManageList();
|
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 结构(含环境名)
|
// 导出整个 environments 结构(含环境名)
|
||||||
exportBtn.addEventListener("click", async () => {
|
exportBtn.addEventListener("click", async () => {
|
||||||
const { environments } = await loadEnvironments();
|
const { environments } = await loadEnvironments();
|
||||||
|
|||||||
Reference in New Issue
Block a user