This commit is contained in:
assast committed 2026-01-31 17:16:45 +08:00
1 parent de39d17c75
commit d61847b521
3 files changed
+78 -29

No files matched your search

+37 -2
View File
@@ -29,6 +29,7 @@
- 🔄 **OAuth2 助手** - 内置 OAuth2 授权流程,快速获取 Refresh Token
- 💾 **邮件缓存** - 智能缓存邮件列表,切换邮箱/文件夹即时展示,刷新页面清空缓存(⭐ 2026-01-29 新增)
- 🔓 **信任邮件模式** - 支持查看原始邮件内容,可选择绕过安全过滤(⭐ 2026-01-29 新增)
- 🏷️ **标签管理** - 支持给邮箱账号打标签,支持批量打标签/去标签,支持按标签筛选(⭐ 2026-01-31 新增)
#### Token 刷新管理
- 🔁 **全量刷新** - 一键刷新所有账号 Token,实时进度展示,防止 90 天过期(⭐ 2026-01-23 新增)
@@ -95,6 +96,9 @@ Web 应用采用四栏式布局设计:
### 登录速率限制保护
![登录失败强制间隔](img/登录失败强制间隔.png)
### 标签管理功能(⭐ 2026-01-31 新增)
![标签管理](img/标签管理.png)
### OAuth2 授权流程
#### 步骤 1:应用注册
@@ -438,7 +442,38 @@ user@outlook.com----password123----24d9a0ed-8787-4584-883c-2fd79308940a----0.AXE
- **删除分组** - 点击分组旁的「删除」按钮(默认分组不可删除)
- **导出分组** - 点击分组旁的「导出」按钮,导出该分组下的所有邮箱账号
### 6. 系统设置
### 6. 标签管理(⭐ 2026-01-31 新增)
标签功能允许您为邮箱账号添加自定义标签,方便分类和筛选。
#### 创建和管理标签
1. 点击邮箱面板标题栏的「🏷️」按钮,打开标签管理弹窗
2. 输入标签名称,选择标签颜色,点击「创建」
3. 已有标签会显示在列表中,可点击右侧的「删除」按钮移除
#### 给邮箱打标签
1. 在邮箱列表中,勾选一个或多个邮箱账号(每个邮箱左侧有复选框)
2. 底部会出现批量操作栏,显示「已选 N 项」
3. 点击「打标签」按钮,选择要添加的标签,确认即可
4. 标签会以彩色徽章形式显示在邮箱备注下方
#### 去除邮箱标签
1. 勾选已打标签的邮箱账号
2. 点击底部操作栏的「去标签」按钮
3. 选择要移除的标签,确认即可
#### 按标签筛选邮箱
1. 在排序按钮下方,有一行标签筛选区(仅在有标签时显示)
2. 勾选一个或多个标签复选框
3. 邮箱列表会自动过滤,只显示包含所选标签的账号
4. 取消所有勾选可恢复显示全部账号
#### 搜索标签
- 在全局搜索框中输入标签名称,可搜索到带有该标签的邮箱账号
![标签管理](img/标签管理.png)
### 7. 系统设置
点击导航栏的「⚙️ 设置」按钮,可以修改以下配置:
@@ -454,7 +489,7 @@ user@outlook.com----password123----24d9a0ed-8787-4584-883c-2fd79308940a----0.AXE
设置会保存在数据库中,重启应用后仍然有效。
### 7. Token 刷新管理(⭐ 2026-01-23 新增,2026-01-25 增强)
### 8. Token 刷新管理(⭐ 2026-01-23 新增,2026-01-25 增强)
为了防止 Refresh Token 因 90 天不使用而过期,系统提供了强大的 Token 刷新管理功能:
Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

+41 -27
View File
@@ -391,12 +391,11 @@
.account-remark {
font-size: 12px;
color: #666;
padding-left: 20px;
padding-left: 0;
margin-top: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 300px;
white-space: pre-wrap;
word-break: break-all;
line-height: 1.4;
}
.account-email {
@@ -1351,13 +1350,14 @@
style="padding: 4px 10px; font-size: 12px; background-color: #ffffff; border: 1px solid #e5e5e5; border-radius: 4px; color: #666; cursor: pointer; transition: all 0.15s;">
📧 邮箱名
</button>
<div style="width: 1px; height: 16px; background: #e5e5e5; margin: 0 4px;"></div>
<select id="tagFilter" onchange="filterAccountsByTag(this.value)"
style="padding: 4px; font-size: 12px; border: 1px solid #e5e5e5; border-radius: 4px; color: #666; outline: none; background: white;">
<option value="">全部标签</option>
</select>
<!-- Tag Filter moved to new row -->
</div>
</div>
<!-- Tag Filter Row -->
<div id="tagFilterContainer"
style="padding: 8px 16px; border-bottom: 1px solid #e5e5e5; display: flex; flex-wrap: wrap; gap: 8px; background-color: #fafafa; display: none;">
<!-- Tags checkboxes will be rendered here -->
</div>
<div class="account-list" id="accountList">
<div class="empty-state">
<div class="empty-state-icon">📁</div>
@@ -2686,12 +2686,16 @@ ${details}
let result = [...accounts];
// 1. Tag 筛选
const tagFilter = document.getElementById('tagFilter');
const selectedTagId = tagFilter ? tagFilter.value : '';
if (selectedTagId) {
// Get checked tags
const checkedBoxes = document.querySelectorAll('.tag-filter-checkbox:checked');
const selectedTagIds = Array.from(checkedBoxes).map(cb => parseInt(cb.value));
if (selectedTagIds.length > 0) {
result = result.filter(acc => {
if (!acc.tags) return false;
return acc.tags.some(t => t.id === parseInt(selectedTagId));
// Check if account has ANY of the selected tags (OR logic)
// If you want AND logic, use every() instead of some()
return acc.tags.some(t => selectedTagIds.includes(t.id));
});
}
@@ -2712,7 +2716,7 @@ ${details}
}
// Tag Filter Change Handler
function filterAccountsByTag(tagId) {
function handleTagFilterChange() {
if (accountsCache[currentGroupId]) {
const filteredAccounts = applyFiltersAndSort(accountsCache[currentGroupId]);
renderAccountList(filteredAccounts);
@@ -5067,20 +5071,30 @@ ${details}
// 更新标签筛选下拉框
function updateTagFilter() {
const filterEl = document.getElementById('tagFilter');
if (!filterEl) return;
const container = document.getElementById('tagFilterContainer');
if (!container) return;
const currentValue = filterEl.value;
let html = '<option value="">全部标签</option>';
allTags.forEach(tag => {
html += `<option value="${tag.id}">${escapeHtml(tag.name)}</option>`;
});
filterEl.innerHTML = html;
// 尝试保持选中
if (currentValue && allTags.find(t => t.id == currentValue)) {
filterEl.value = currentValue;
if (allTags.length === 0) {
container.style.display = 'none';
return;
}
container.style.display = 'flex';
let html = '';
allTags.forEach(tag => {
html += `
<label style="display: inline-flex; align-items: center; gap: 4px; font-size: 11px; cursor: pointer; padding: 2px 6px; border: 1px solid #e5e5e5; border-radius: 12px; background: white; user-select: none;">
<input type="checkbox" class="tag-filter-checkbox" value="${tag.id}" onchange="handleTagFilterChange()" style="margin: 0;">
<span style="width: 8px; height: 8px; border-radius: 50%; background-color: ${tag.color}; display: inline-block;"></span>
${escapeHtml(tag.name)}
</label>
`;
});
container.innerHTML = html;
/* Old dropdown code removed */
}
// 渲染标签列表