feat: 新增免费演出雷达工具与定投评估功能,优化周报提醒逻辑

1. 新增免费演出雷达工具:含秀动/猫眼/大麦平台抓取、本地代理鉴权配置
2. 新增建行定投评估模块:四大指数PE-TTM估值与定投档位建议
3. 新增推送告警白名单过滤功能,限制推送指定指数告警
4. 重构周报首页提醒逻辑,移至服务端处理并减少前端请求
5. 新增多套调试探针脚本与单元测试用例
This commit is contained in:
yangxiangyuan
2026-08-04 17:33:14 +08:00
parent af5d0f43a5
commit 6a7a2b089a
25 changed files with 3499 additions and 121 deletions
+10 -112
View File
@@ -997,95 +997,6 @@ class ToolboxHome {
};
}
htmlToPlainText(html) {
const temp = document.createElement('div');
temp.innerHTML = String(html || '');
return String(temp.textContent || temp.innerText || '')
.replace(/\u00a0/g, ' ')
.replace(/\s+/g, ' ')
.trim();
}
getWeeklyTemplatePlainText() {
return [
'1、本周核心重点事项(置顶)',
'①置顶优先级,避免杂事分散精力',
'②本周 1-3 件最高优先级事(工作 / 家庭各列,如公司项目节点、孩子升学事宜)',
'③必做底线项(如固定会议、家庭日常、记账 / 复盘打卡)',
'2、上周复盘(工作)',
'-----',
'①公司工作完成项(标注是否达标、核心成果)',
'②未完成项 + 原因(客观 / 主观,如时间不足、需求变更)',
'③规则事务完成情况(重点记未完成的关键项)',
'3、上周复盘(个人 + 家庭)',
'-----',
'①个人 / 家庭事务完成情况(重点记未完成的关键项)',
'4、公司工作(计划)',
'-----',
'①核心任务(按重要 / 紧急排序,标注截止时间、协作人)',
'②常规工作(日常运维、对接、汇报等固定事项)',
'③待协调 / 需支持项(如需要同事配合、领导审批的内容)',
'5、个人和家庭 事务(计划,含理财)',
'-----',
'①自我提升(学习、运动、习惯打卡,如读 1 章书、运动 3 次)',
'②个人琐事(办事、缴费、社交等,标注时间节点)',
'③投资理财(盯盘节点、条件单调整、复盘上周收益 / 操作)',
'④家人相关(孩子学习 / 活动、老人照料、家庭聚餐等)',
'⑤家庭琐事(家务、采购、物业对接等,可标注分工)',
'⑥家庭规划(短期家庭事项,如周末出游、家电维修)',
'6、待解决问题 / 待协调项',
'-----',
'①工作中可能遇到的问题(如项目延期风险、资源不足)',
'②个人 / 家庭的待处理问题(如时间冲突、资金安排)',
'③初步应对思路(简单标注,避免临时慌乱)',
'7、本周小目标 / 打卡项(轻量化激励,易落地)',
'①工作小目标(如高效完成 1 个核心任务、减少无效沟通)',
'②个人 / 家庭小目标(如坚持记账、周末全家运动 1 次)'
].join(' ');
}
isWeeklyAutoTitle(title, weekId = '') {
const normalizedTitle = String(title || '').trim();
if (!normalizedTitle) return true;
if (normalizedTitle.includes('(自动新增)')) return true;
if (weekId && normalizedTitle === `${weekId} 周报`) return true;
return false;
}
isWeeklyMeaningfulReport(report, expectedWeekId = '') {
if (!report || typeof report !== 'object') return false;
const weekId = String(report.week_id || expectedWeekId || '').trim();
const title = String(report.title || '').trim();
const contentText = this.htmlToPlainText(report.content || '');
if (!title || !contentText) return false;
if (this.isWeeklyAutoTitle(title, weekId)) return false;
if (contentText === '这是自动新增的周报。你可以修改。') return false;
if (contentText === this.getWeeklyTemplatePlainText()) return false;
return true;
}
async getRecentWeeklyWindows(count = 3) {
const today = this.formatDate(new Date());
const currentYear = new Date().getFullYear();
const years = [currentYear - 1, currentYear];
const results = await Promise.all(
years.map(async (year) => {
const res = await fetch(`/api/weekly/weeks?year=${year}&t=${Date.now()}`, { cache: 'no-store' });
if (!res.ok) throw new Error(`weekly_weeks_${res.status}`);
const data = await res.json();
return Array.isArray(data && data.weeks) ? data.weeks : [];
})
);
const allWeeks = results
.flat()
.filter(item => item && item.week_id && item.start && item.end)
.sort((a, b) => String(a.start).localeCompare(String(b.start)));
if (allWeeks.length === 0) return [];
const currentIndex = allWeeks.findIndex(item => String(item.start) <= today && today <= String(item.end));
if (currentIndex < 0) return allWeeks.slice(-count);
return allWeeks.slice(Math.max(0, currentIndex - count + 1), currentIndex + 1);
}
renderFitnessDiaryReminder(summary) {
if (!this.fitnessDiaryReminder || !this.fitnessDiaryReminderText || !this.fitnessDiaryReminderDates) return;
const missingCount = Number(summary && summary.missingCount ? summary.missingCount : 0);
@@ -1148,30 +1059,17 @@ class ToolboxHome {
async loadWeeklyReportReminder() {
try {
const weeks = await this.getRecentWeeklyWindows(3);
if (!Array.isArray(weeks) || weeks.length === 0) return;
const reportResults = await Promise.all(
weeks.map(async (week) => {
const res = await fetch(`/api/weekly/report?week_id=${encodeURIComponent(week.week_id)}&t=${Date.now()}`, {
cache: 'no-store'
});
if (!res.ok) {
if (res.status === 401) return { unauthorized: true, week };
return { week, report: null };
}
const data = await res.json();
return { week, report: data && data.ok === true ? data.report : null };
})
);
if (reportResults.some(item => item && item.unauthorized)) return;
const missingWeeks = reportResults
.filter(item => !this.isWeeklyMeaningfulReport(item.report, item.week && item.week.week_id))
.map(item => item.week)
.filter(Boolean);
// 【首页提醒专用】直接调后端聚合接口 /api/weekly/reminder/status。
// 判定由服务端读库完成,不依赖周报 weekly_auth 登录态(仅需 weekly_gate 门禁);
// 相对路径同源调用:本机访问即 localhost:8976,外网访问自动走 8975 反代,接口本身未授权一律 401。
const res = await fetch(`/api/weekly/reminder/status?t=${Date.now()}`, { cache: 'no-store' });
if (!res.ok) return; // 无门禁 / 接口异常:静默隐藏提醒
const data = await res.json();
if (!data || data.ok !== true) return;
this.renderWeeklyReportReminder({
weeks: 3,
missingCount: missingWeeks.length,
missingWeeks
weeks: Number(data.weeks || 3),
missingCount: Number(data.missingCount || 0),
missingWeeks: Array.isArray(data.missingWeeks) ? data.missingWeeks : []
});
} catch (err) {
console.warn('加载周报提醒失败:', err);