/**
* 主应用逻辑
*/
class App {
constructor() {
this.data = null;
this.treeView = null;
this.currentFeature = null;
this.init();
}
/**
* 初始化应用
*/
async init() {
try {
await this.loadData();
this.initComponents();
this.bindEvents();
this.updateStats();
} catch (error) {
console.error('初始化失败:', error);
this.showError('加载失败,请刷新页面重试');
}
}
/**
* 加载数据
*/
async loadData() {
try {
const response = await fetch('data/data.json');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
this.data = await response.json();
} catch (error) {
console.error('加载数据失败:', error);
throw error;
}
}
/**
* 初始化组件
*/
initComponents() {
// 初始化树形菜单
const treeContainer = document.getElementById('tree-container');
this.treeView = new TreeView(treeContainer, this.data);
// 设置节点点击回调
this.treeView.onNodeClick = (node) => {
this.handleNodeClick(node);
};
// 默认展开第一层
this.treeView.expandFirstLevel();
}
/**
* 绑定事件
*/
bindEvents() {
// 搜索
const searchInput = document.getElementById('search-input');
const searchBtn = document.getElementById('search-btn');
let searchTimer = null;
searchInput?.addEventListener('input', (e) => {
clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
this.treeView.search(e.target.value);
}, 300);
});
searchBtn?.addEventListener('click', () => {
this.treeView.search(searchInput.value);
});
// 全部折叠
const collapseBtn = document.getElementById('collapse-all');
collapseBtn?.addEventListener('click', () => {
this.treeView.collapseAll();
});
// 状态筛选
const filterStatus = document.getElementById('filter-status');
filterStatus?.addEventListener('change', () => {
this.filterByStatus(filterStatus.value);
});
// 排序
const sortBy = document.getElementById('sort-by');
sortBy?.addEventListener('change', () => {
this.sortData(sortBy.value);
});
// 主题切换
const themeToggle = document.getElementById('theme-toggle');
themeToggle?.addEventListener('click', () => {
this.toggleTheme();
});
}
/**
* 处理节点点击
* @param {Object} node - 节点数据
*/
handleNodeClick(node) {
const panelTitle = document.getElementById('panel-title');
const panelInfo = document.getElementById('panel-info');
const cardsContainer = document.getElementById('cards-container');
if (node.type === 'feature' && node.data) {
// 点击特征节点,显示搜索结果
this.currentFeature = node.data;
// 更新标题
panelTitle.textContent = `"${node.label}" 的搜索结果`;
// 构建详细的搜索信息
const metadata = node.data.search_metadata;
if (metadata && metadata.status === 'success') {
// 有搜索结果,显示完整参数
const params = metadata.search_params || {};
panelInfo.innerHTML = `
搜索词: ${params.keyword || '未知'}
内容类型: ${params.content_type || '未知'}
排序方式: ${params.sort_type || '未知'}
结果: ${metadata.note_count || 0} 条帖子
`;
} else if (metadata && metadata.status === 'failed') {
// 搜索失败
panelInfo.innerHTML = `
搜索失败
`;
} else if (node.searchWord === null) {
// search_word 为 null(重复被过滤)
panelInfo.innerHTML = `
该特征的搜索词与其他特征重复,已被过滤
`;
} else {
// 待搜索
panelInfo.innerHTML = `
待搜索
${node.searchWord ? ` - 搜索词: ${node.searchWord}` : ''}
`;
}
// 渲染帖子卡片
if (node.data.search_result) {
const notes = node.data.search_result.data?.data || [];
// 存储到全局变量供 CardRenderer 使用
window.__currentNotes = notes;
CardRenderer.renderNotes(notes, cardsContainer);
} else {
cardsContainer.innerHTML = `
`;
}
} else if (node.type === 'association') {
// 点击关联分类,显示特征列表
const features = node.data.特征列表 || [];
const jaccardScore = (node.data['Jaccard相似度'] * 100).toFixed(1);
panelTitle.textContent = node.label;
panelInfo.innerHTML = `
共 ${features.length} 个特征
Jaccard相似度: ${jaccardScore}%
`;
this.renderFeatureList(features, cardsContainer);
} else if (node.type === 'result') {
// 点击结果项,显示概览
panelTitle.textContent = node.label;
panelInfo.textContent = `共 ${node.children?.length || 0} 个关联分类`;
this.renderResultOverview(node.data, cardsContainer);
} else if (node.type === 'note') {
// 点击单个帖子,显示该帖子详情
window.__currentNotes = [node.data];
CardRenderer.renderNotes([node.data], cardsContainer);
}
}
/**
* 渲染特征列表
* @param {Array} features - 特征数组
* @param {HTMLElement} container - 容器元素
*/
renderFeatureList(features, container) {
const html = `
| 特征名称 |
搜索词 |
帖子数 |
状态 |
${features.map(feature => {
const noteCount = feature.search_metadata?.note_count || 0;
const status = feature.search_metadata?.status || 'pending';
const statusText = {
success: '成功',
failed: '失败',
pending: '待搜索'
}[status] || '未知';
const statusColor = {
success: 'var(--secondary-color)',
failed: 'var(--danger-color)',
pending: 'var(--warning-color)'
}[status] || 'var(--text-secondary)';
return `
| ${feature.特征名称} |
${feature.search_word || '-'}
|
${noteCount}
|
${statusText}
|
`;
}).join('')}
`;
container.innerHTML = html;
}
/**
* 渲染结果概览
* @param {Object} result - 结果数据
* @param {HTMLElement} container - 容器元素
*/
renderResultOverview(result, container) {
const html = `
基本信息
人设特征名称
${result.最高匹配信息?.人设特征名称 || '-'}
关联数量
${result.找到的关联?.length || 0} 个
关联分类
${(result.找到的关联 || []).map(assoc => `
${assoc.目标分类路径}
${assoc.特征列表?.length || 0} 个特征
Jaccard: ${(assoc['Jaccard相似度'] * 100).toFixed(1)}%
`).join('')}
`;
container.innerHTML = html;
}
/**
* 按状态筛选
* @param {string} status - 状态值
*/
filterByStatus(status) {
// TODO: 实现筛选逻辑
console.log('筛选状态:', status);
}
/**
* 排序数据
* @param {string} sortBy - 排序方式
*/
sortData(sortBy) {
// TODO: 实现排序逻辑
console.log('排序方式:', sortBy);
}
/**
* 切换主题
*/
toggleTheme() {
const body = document.body;
const themeToggle = document.getElementById('theme-toggle');
if (body.classList.contains('dark-theme')) {
body.classList.remove('dark-theme');
themeToggle.textContent = '🌙';
localStorage.setItem('theme', 'light');
} else {
body.classList.add('dark-theme');
themeToggle.textContent = '☀️';
localStorage.setItem('theme', 'dark');
}
}
/**
* 更新统计数据
*/
updateStats() {
const statResults = document.getElementById('stat-results');
const statFeatures = document.getElementById('stat-features');
const statNotes = document.getElementById('stat-notes');
// 统计数据
let totalFeatures = 0;
let totalNotes = 0;
this.data.forEach(result => {
(result.找到的关联 || []).forEach(assoc => {
(assoc.特征列表 || []).forEach(feature => {
totalFeatures++;
if (feature.search_metadata?.note_count) {
totalNotes += feature.search_metadata.note_count;
}
});
});
});
statResults.textContent = this.data.length;
statFeatures.textContent = totalFeatures;
statNotes.textContent = totalNotes;
}
/**
* 显示错误
* @param {string} message - 错误消息
*/
showError(message) {
const treeContainer = document.getElementById('tree-container');
const cardsContainer = document.getElementById('cards-container');
treeContainer.innerHTML = ``;
cardsContainer.innerHTML = ``;
}
}
// 初始化应用
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
new App();
});
} else {
new App();
}
// 恢复主题设置
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
document.body.classList.add('dark-theme');
const themeToggle = document.getElementById('theme-toggle');
if (themeToggle) themeToggle.textContent = '☀️';
}