support export

This commit is contained in:
Julian Freeman
2026-04-10 21:53:55 -04:00
parent 5d872f7bf1
commit 856f4c01c9

View File

@@ -312,6 +312,96 @@ function toggleAddPanel() {
renderChatOrganizer(); renderChatOrganizer();
} }
function normalizeImportedCategories(rawCategories) {
if (!Array.isArray(rawCategories)) return [];
return rawCategories
.map(category => {
const name = String(category?.name || "").trim();
if (!name) return null;
const chatIds = Array.isArray(category?.chatIds)
? Array.from(new Set(category.chatIds.map(chatId => normalizeChatId(chatId)).filter(Boolean)))
: [];
return {
id: String(category?.id || createCategoryId()),
name,
chatIds
};
})
.filter(Boolean);
}
function normalizeImportedChatCache(rawChatCache) {
if (!rawChatCache || typeof rawChatCache !== "object") return {};
const nextCache = {};
Object.entries(rawChatCache).forEach(([rawChatId, meta]) => {
const chatId = normalizeChatId(rawChatId);
if (!chatId) return;
nextCache[chatId] = {
id: chatId,
name: String(meta?.name || chatId),
avatarUrl: String(meta?.avatarUrl || ""),
updatedAt: Number(meta?.updatedAt || Date.now())
};
});
return nextCache;
}
function exportChatCategories() {
const payload = {
version: 1,
exportedAt: new Date().toISOString(),
categories: chatOrganizerState.categories,
chatCache: chatOrganizerState.chatCache
};
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = `teams-chat-categories-${timestamp}.json`;
anchor.click();
URL.revokeObjectURL(url);
setOrganizerFeedback("分类已导出。");
}
async function importChatCategoriesFromFile(file) {
if (!file) return;
try {
const content = await file.text();
const parsed = JSON.parse(content);
const importedCategories = normalizeImportedCategories(parsed?.categories);
const importedChatCache = normalizeImportedChatCache(parsed?.chatCache);
if (!importedCategories.length) {
setOrganizerFeedback("导入失败:文件里没有有效分类。");
return;
}
chatOrganizerState.categories = importedCategories;
chatOrganizerState.chatCache = {
...chatOrganizerState.chatCache,
...importedChatCache
};
chatOrganizerState.expandedCategoryIds = {};
importedCategories.forEach(category => {
chatOrganizerState.expandedCategoryIds[category.id] = true;
});
chatOrganizerState.selectedCategoryId = importedCategories[0]?.id || "";
scheduleOrganizerStateSave();
setOrganizerFeedback(`已导入 ${importedCategories.length} 个分类。`);
} catch (error) {
console.error("Teams Alias: 导入分类失败", error);
setOrganizerFeedback("导入失败:文件格式不是有效的 JSON。");
}
}
function renderChatCard(chatId, categoryId) { function renderChatCard(chatId, categoryId) {
const meta = getChatMeta(chatId) || { id: chatId, name: chatId, avatarUrl: "" }; const meta = getChatMeta(chatId) || { id: chatId, name: chatId, avatarUrl: "" };
const avatar = meta.avatarUrl const avatar = meta.avatarUrl
@@ -502,6 +592,17 @@ function ensureChatOrganizerUI() {
font-weight: 600; font-weight: 600;
} }
.teams-alias-secondary-button {
border: 1px solid #cbd5e1;
border-radius: 10px;
padding: 9px 14px;
font-size: 13px;
cursor: pointer;
background: #fff;
color: #0f172a;
font-weight: 600;
}
.teams-alias-category-list, .teams-alias-category-list,
.teams-alias-visible-list, .teams-alias-visible-list,
.teams-alias-chat-list { .teams-alias-chat-list {
@@ -635,13 +736,6 @@ function ensureChatOrganizerUI() {
display: none; display: none;
} }
.teams-alias-main-title {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #334155;
}
@media (max-width: 900px) { @media (max-width: 900px) {
.teams-alias-modal { .teams-alias-modal {
left: 12px; left: 12px;
@@ -649,6 +743,11 @@ function ensureChatOrganizerUI() {
width: auto; width: auto;
top: 56px; top: 56px;
} }
.teams-alias-toolbar {
align-items: flex-start;
flex-direction: column;
}
} }
`; `;
document.head.appendChild(style); document.head.appendChild(style);
@@ -704,6 +803,16 @@ function ensureChatOrganizerUI() {
return; return;
} }
if (action === "export-categories") {
exportChatCategories();
return;
}
if (action === "trigger-import-categories") {
root.querySelector('[data-role="import-categories-input"]')?.click();
return;
}
if (action === "toggle-category") { if (action === "toggle-category") {
toggleCategoryExpanded(actionElement.getAttribute("data-category-id")); toggleCategoryExpanded(actionElement.getAttribute("data-category-id"));
return; return;
@@ -747,6 +856,12 @@ function ensureChatOrganizerUI() {
if (target.matches('[data-role="category-select"]')) { if (target.matches('[data-role="category-select"]')) {
chatOrganizerState.selectedCategoryId = target.value; chatOrganizerState.selectedCategoryId = target.value;
renderChatOrganizer(); renderChatOrganizer();
return;
}
if (target.matches('[data-role="import-categories-input"]')) {
const file = target.files?.[0];
importChatCategoriesFromFile(file);
target.value = "";
} }
}); });
@@ -813,11 +928,13 @@ function renderChatOrganizer() {
<button class="teams-alias-close" data-action="close-modal">×</button> <button class="teams-alias-close" data-action="close-modal">×</button>
</div> </div>
<div class="teams-alias-toolbar"> <div class="teams-alias-toolbar">
<p class="teams-alias-main-title">分类是主视图,添加和识别列表作为辅助面板使用。</p>
<div class="teams-alias-toolbar-actions"> <div class="teams-alias-toolbar-actions">
<button class="teams-alias-primary-button" data-action="toggle-create-panel">${chatOrganizerState.createPanelOpen ? "收起新建" : "新建分类"}</button> <button class="teams-alias-primary-button" data-action="toggle-create-panel">${chatOrganizerState.createPanelOpen ? "收起新建" : "新建分类"}</button>
<button class="teams-alias-primary-button" data-action="toggle-add-panel">${chatOrganizerState.addPanelOpen ? "收起添加" : "添加聊天"}</button> <button class="teams-alias-primary-button" data-action="toggle-add-panel">${chatOrganizerState.addPanelOpen ? "收起添加" : "添加聊天"}</button>
<button class="teams-alias-secondary-button" data-action="export-categories">导出分类</button>
<button class="teams-alias-secondary-button" data-action="trigger-import-categories">导入分类</button>
</div> </div>
<input type="file" accept="application/json,.json" data-role="import-categories-input" class="teams-alias-collapsed">
</div> </div>
<div class="teams-alias-modal-body"> <div class="teams-alias-modal-body">
<div class="teams-alias-panel ${chatOrganizerState.createPanelOpen ? "" : "teams-alias-collapsed"}"> <div class="teams-alias-panel ${chatOrganizerState.createPanelOpen ? "" : "teams-alias-collapsed"}">