Compare commits

..
4 Commits
Author SHA1 Message Date
simon 10569bb828 docs: 更新 README/CLAUDE/AGENTS/DB_REFERENCE
- README: 新增投资资讯日报功能、日报 API 数据源、三种数据流模式、sync-echart 部署与 gitea 仓库说明
- CLAUDE.md: 页面目录表补 charts/news_reports.php,模式B 代表页面补充
- AGENTS.md: 模式B 页面清单与 API 端点补充
- DB_REFERENCE.md: 补 news_report/news_event 两张日报表(共 18 张)
2026-08-06 09:33:53 +08:00
simon 11f31762ad feat: 页首开始/结束日期输入框写入默认值(近30天) 2026-08-06 09:29:26 +08:00
simon 7785de7d1f fix: newsReports.js 加版本号参数, 规避浏览器缓存导致的重要事件Tab无反应 2026-08-06 09:28:14 +08:00
simon 4da1fc9301 feat: 重要事件聚合视图 + stats 英文键格式加固
- normalizeKV 显式识别 intl 英文键格式(importance/count、event_type/count、source/count),不再依赖键顺序
- 新增"重要事件"视图 Tab:跨日报聚合(/api/news/events/),支持 天数/重要度/类型 筛选,复用事件行渲染
- 兼容文档 v1.1:intl sentiment 对象格式、finance news.event_types 对象格式
2026-08-06 09:25:16 +08:00
6 changed files with 145 additions and 16 deletions
+1 -1
View File
@@ -16,7 +16,7 @@
### 三种数据传递模式(实测现状)
1. **模式 Bfetch 外部 API**:页面 JS 用 `fetch()``https://api.doorcome.cn/api/*`(如 `stockparam``indexDatas``stockinfo``getdiv``stockep``stockmargin``stockbasic`)。代表页面:`stock_trend.php``stockdiv.php``stockep.php``stockmargin.php``hkholdbycode.php``index_trend.php``index_mv_all.php``index_margin.php`
1. **模式 Bfetch 外部 API**:页面 JS 用 `fetch()``https://api.doorcome.cn/api/*`(如 `stockparam``indexDatas``stockinfo``getdiv``stockep``stockmargin``stockbasic``news/reports``news/events`)。代表页面:`stock_trend.php``stockdiv.php``stockep.php``stockmargin.php``hkholdbycode.php``index_trend.php``index_mv_all.php``index_margin.php``news_reports.php`(日报 + 重要事件聚合)
2. **模式 CAJAX → `inc/ajax.inc.php`**:页面 JS 用 `$.ajax``../inc/ajax.inc.php`,通过 `$_REQUEST['t']` 路由(if 链,不是 switch/case)。`t` 取值:`stockList``financeData``esfTBD``esfListDaily``newTBD``moneyflowData``estateData`。代表页面:`moneyflow.php``realestate.php``estateTradeDaily.php``estateListDaily.php``estateNewTradeDaily.php``stockkeydata.php``stockTradeRecord.php`
3. **模式 A(服务端注入)**PHP 查询后 `json_encode()` 注入 `<script>` 标签中的 JS 变量。`window.chartConfig` 全局变量**仅剩 `deprecated/` 在用**;活跃页面 `charts/chartStDetail.php` 是变体(注入命名变量 `data1``data5``legend` 等)。
+2 -1
View File
@@ -51,7 +51,7 @@
**模式 B(较新——JS fetch 调用外部 API):**
1. 页面直接加载 JS,不注入数据
2. JS 在 `$(document).ready` 中通过 `fetch()` 调用 `https://api.doorcome.cn/api/*`(如 `stockparam``indexDatas``stockinfo``getdiv``stockep``stockmargin``stockbasic`)获取数据
3. 代表页面:`stock_trend.php``stockdiv.php``stockep.php``stockmargin.php``hkholdbycode.php``index_trend.php``index_mv_all.php``index_margin.php`
3. 代表页面:`stock_trend.php``stockdiv.php``stockep.php``stockmargin.php``hkholdbycode.php``index_trend.php``index_mv_all.php``index_margin.php``news_reports.php`(含重要事件聚合,调 `/api/news/events/`
**模式 CAJAX → `inc/ajax.inc.php`):**
1. 页面 JS 用 `$.ajax``../inc/ajax.inc.php`,通过 `$_REQUEST['t']` 路由(if 链):`stockList``financeData``esfTBD``esfListDaily``newTBD``moneyflowData``estateData`
@@ -82,6 +82,7 @@
| `charts/stockTradeRecord.php` | 个人股票交易记录 |
| `charts/myexcel.php` | 交易记录 Excel/CSV 导入处理(配合上传流程) |
| `charts/chartStDetail.php` | 个股详细图表(服务端注入命名变量 data1…data5 |
| `charts/news_reports.php` | 投资资讯日报导航页(国内/国际日报列表+详情+重要事件聚合,fetch api.doorcome.cn |
> 注:`stock_ih.php`、`index_ih.php`、`chartSix.php`、`chartThree.php` 及旧版 `stock_trend.php`/`index_trend.php`/`index_mv_all.php` 已移入 `deprecated/`,勿引用。
+8 -1
View File
@@ -2,7 +2,7 @@
数据库:`myquant`(配置在 `inc/config.php`
共 16 张表,其中 10 张在使用,3 张未使用,3 张已由 API 替代。
共 18 张表,其中 12 张在使用,3 张未使用,3 张已由 API 替代。
## 全表清单
@@ -52,6 +52,13 @@
| `pv_log` | ✅ 在用 | 页面访问日志 | `inc/functions.inc.php` |
| `pv_counter` | ✅ 在用 | 页面访问计数器 | `inc/functions.inc.php` |
### 日报 API2 张,djapi 后端管理)
| 表名 | 状态 | 用途 | 引用文件 |
|------|------|------|---------|
| `news_report` | ✅ 在用 | 投资资讯日报主表(国内 finance / 国际 intl,每天每类型一份) | djapi `api/report/` 写入;前端 `charts/news_reports.php``api.doorcome.cn/api/news/reports/` 只读消费 |
| `news_event` | ✅ 在用 | 日报事件明细(xwlb/news/cninfo/intl 板块,含重要度/情感/来源/链接) | 同上,`/api/news/events/` 跨日报聚合 |
## 说明
- 所有 SQL 查询均通过 `db_query()` 参数化查询(prepare/execute),`excelOperate.inc.php` 中 insert 操作用字符串拼接
+19 -9
View File
@@ -36,24 +36,32 @@ A 股基本面分析和宁波房地产数据可视化平台,基于 PHP + EChar
| 二手房每日成交量 | 宁波各区县二手房成交面积和套数(日/月维度) |
| 二手房每日挂牌量 | 宁波各区县二手房每日新增挂牌套数和均价 |
### 投资资讯日报
| 页面 | 功能 |
|------|------|
| 日报查询导航页(`charts/news_reports.php`) | 国内 / 国际 AI 深度研究日报,按日期分模块查看(AI 摘要、新闻联播要闻、财经新闻、公告调研、数据总览) |
| 重要事件聚合 | 跨日报检索重要事件(近 N 天 / 重要度 / 类型筛选),数据来自 `api.doorcome.cn/api/news/events/` |
### 其他模块
- **新闻联播分析**`news/`):CCTV 新闻联播内容抓取与关键词分析
- **研究报告**`research/`):AI 产业链、锂矿、新能源等行业深度研究报告(含 PDF 和 HTML 版本)
- **宏观研究**`quant/`):宏观量化分析报告,数据存储在 `mac_report`
## 技术栈
- **后端**PHP 8.4,无框架
- **前端**ECharts 5.4、jQuery 3.6、Tailwind CSS 3.4、DataTables 1.13、Font Awesome 6.4
- **数据库**MySQL,通过 `mysqli` 连接
- **外部 API**TuShare`api.tushare.pro` / `api.waditu.com`
- **外部 API**TuShare`api.tushare.pro` / `api.waditu.com`、日报接口(`api.doorcome.cn/api/news/reports/``/api/news/events/`,由 djapi 后端提供,表 `news_report`/`news_event`
- **数据来源**:宁波市房产交易服务信息网(`cnnbfdc.com`
## 项目结构
```
├── index.php / index-2.php # 入口导航页
├── charts/ # 数据可视化页面(21个
├── charts/ # 数据可视化页面(含 charts/news_reports.php 日报导航页
├── inc/ # PHP 核心代码
│ ├── config.php # DB连接、db_query()、jsonResponse()
│ ├── getData.inc.php # 数据查询(指数、持仓、资金流向)
@@ -70,11 +78,12 @@ A 股基本面分析和宁波房地产数据可视化平台,基于 PHP + EChar
│ ├── js/ # JS 库(jQuery、ECharts、DataTables 等)
│ ├── css/ # CSS 库(Font Awesome、DataTables
│ └── webfonts/ # 字体文件
├── js/ # 页面专属图表 JS
├── js/ # 页面专属图表/渲染 JSnewsReports.js 为日报页逻辑)
├── css/ # 自定义样式
├── html/ # 公共 HTML 头部/底部
├── news/ # 新闻联播分析模块
├── research/ # 研究报告模块
├── podcast-docs/ # 播客相关文档
└── deprecated/ # 已废弃的旧页面
```
@@ -82,15 +91,16 @@ A 股基本面分析和宁波房地产数据可视化平台,基于 PHP + EChar
```
用户请求 → .php 页面(charts/
→ inc/*.php 查询 MySQL 或调用 TuShare API
json_encode() 注入 <script> 标签
js/*.js 读取 window.chartConfig 或 fetch API
→ ECharts 渲染交互式图表
模式A: inc/*.php 查询 MySQL → json_encode() 注入 <script> 变量 → js/*.js 渲染
模式B: js 直接 fetch api.doorcome.cn/api/* (日报、指数、个股行情等新页面)
模式C: js 通过 $.ajax 调 inc/ajax.inc.php 的 t= 路由(房地产、资金流向等)
→ ECharts / HTML 渲染交互式图表
```
## 开发环境
- **本地 PHP**`d:\software\php8.4\`
- **部署**`sync-echart`ssh + tar 同步到 `www.doorcome.cn:/var/www/html/echart/`
- **本地 PHP**`d:\software\php8.4\`(本机 Mac 无 PHP`php -l` 语法检查走服务器)
- **部署**`~/bin/sync-echart`rsync 单向推送本地 → `simon@www.doorcome.cn:/var/www/html/echart/``-n` 预览;排除本地环境文件与数据目录 uploads/xls/files/research
- **代码仓库**gitea `ssh://git@gitea/simon/echart.git``~/.ssh/config` 别名 gitea,端口 2222
- **调试**XDebug 端口 9000VS Code 配置 `.vscode/launch.json`
- **代码指引**:见 `CLAUDE.md``AGENTS.md`
+35 -2
View File
@@ -35,7 +35,7 @@
.badge-neg { @apply bg-red-100 text-red-700; }
.badge-neu { @apply bg-gray-200 text-gray-600; }
.type-tab {
.type-tab, .view-tab {
@apply px-4 py-2 rounded-lg text-sm font-medium text-gray-500 hover:text-primary hover:bg-primary/5 transition-colors cursor-pointer;
}
.tab-active { @apply bg-primary text-white hover:text-white hover:bg-primary shadow-sm; }
@@ -148,6 +148,10 @@
<!-- 筛选栏 -->
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-4 mb-6">
<div class="flex flex-wrap items-center gap-3">
<div class="flex bg-gray-100 rounded-lg p-1">
<span class="view-tab tab-active" data-view="list">日报列表</span>
<span class="view-tab" data-view="events">重要事件</span>
</div>
<div class="flex bg-gray-100 rounded-lg p-1">
<span class="type-tab tab-active" data-type="">全部</span>
<span class="type-tab" data-type="finance">国内</span>
@@ -169,6 +173,35 @@
<div id="reportList" class="space-y-4"></div>
</div>
<!-- 重要事件聚合视图 -->
<div id="eventsView" class="hidden">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-4 mb-4">
<div class="flex flex-wrap items-center gap-3">
<span class="text-sm text-gray-500"></span>
<select id="evDays" class="filter-input">
<option value="7" selected>7</option>
<option value="14">14</option>
<option value="30">30</option>
</select>
<span class="text-sm text-gray-500"> · 重要度 </span>
<select id="evImportance" class="filter-input">
<option value="4" selected>4</option>
<option value="5">5</option>
<option value="3">3</option>
</select>
<span class="text-sm text-gray-500">类型</span>
<select id="evType" class="filter-input">
<option value="">全部</option>
<option value="finance">国内</option>
<option value="intl">国际</option>
</select>
<button id="btnEvQuery" class="btn-primary"><i class="fa-solid fa-magnifying-glass mr-1"></i>查询</button>
</div>
<div class="mt-3 text-sm text-gray-400" id="eventsInfo"></div>
</div>
<div id="eventsList"></div>
</div>
<!-- 详情视图 -->
<div id="detailView" class="hidden">
<div class="flex items-center justify-between mb-4 flex-wrap gap-2">
@@ -193,6 +226,6 @@
数据来源: api.doorcome.cn 日报接口 · 仅供个人研究参考
</footer>
<script src="/js/newsReports.js"></script>
<script src="/js/newsReports.js?v=20260806"></script>
</body>
</html>
+80 -2
View File
@@ -14,7 +14,7 @@ var NewsReports = {
SECTION_ICONS: { xwlb: 'fa-tower-broadcast', news: 'fa-fire', cninfo: 'fa-clipboard-list', intl: 'fa-earth-asia' },
SENTIMENT: { positive: { icon: '🟢', cls: 'badge-pos', txt: '利好' }, negative: { icon: '🔴', cls: 'badge-neg', txt: '利空' }, neutral: { icon: '⚪', cls: 'badge-neu', txt: '中性' } },
state: { reportType: '', startDate: '', endDate: '', currentId: null },
state: { reportType: '', startDate: '', endDate: '', currentId: null, eventsLoaded: false },
init: function () {
var now = new Date();
@@ -22,6 +22,10 @@ var NewsReports = {
this.state.startDate = this.fmtDate(start);
this.state.endDate = this.fmtDate(now);
// 默认值写入日期输入框 (开始=30天前, 结束=今天)
$('#startDate').val(this.state.startDate);
$('#endDate').val(this.state.endDate);
this.bindEvents();
this.loadReports();
},
@@ -39,6 +43,12 @@ var NewsReports = {
bindEvents: function () {
var self = this;
// 视图切换 (日报列表 / 重要事件)
$('.view-tab').on('click', function () {
$('.view-tab').removeClass('tab-active');
$(this).addClass('tab-active');
self.switchView($(this).data('view'));
});
// 类型 Tabs
$('.type-tab').on('click', function () {
$('.type-tab').removeClass('tab-active');
@@ -52,12 +62,73 @@ var NewsReports = {
self.state.endDate = $('#endDate').val() || self.state.endDate;
self.loadReports();
});
// 重要事件查询
$('#btnEvQuery').on('click', function () {
self.loadEvents();
});
// 返回列表
$('#btnBack').on('click', function () {
self.showList();
});
},
// ==================== 重要事件聚合 ====================
switchView: function (view) {
if (view === 'events') {
$('#listView').addClass('hidden');
$('#detailView').addClass('hidden');
$('#eventsView').removeClass('hidden');
if (!this.state.eventsLoaded) this.loadEvents();
} else {
$('#eventsView').addClass('hidden');
$('#listView').removeClass('hidden');
}
},
loadEvents: function () {
var self = this;
this.showLoading('正在加载重要事件...');
var days = $('#evDays').val() || 7;
var imp = $('#evImportance').val() || 4;
var type = $('#evType').val() || '';
var url = this.API_EVENTS + '?days=' + days + '&importance=' + imp + '&limit=300';
if (type) url += '&report_type=' + type;
$.getJSON(url)
.done(function (data) {
self.renderEvents(data);
self.state.eventsLoaded = true;
self.hideLoading();
})
.fail(function (xhr) {
self.renderEventsError(xhr);
self.hideLoading();
});
},
renderEvents: function (list) {
var self = this;
if (!Array.isArray(list) || list.length === 0) {
$('#eventsList').html('<div class="empty-box"><i class="fa-solid fa-inbox"></i><p>该条件下暂无重要事件</p></div>');
$('#eventsInfo').text('');
return;
}
$('#eventsInfo').text('共 ' + list.length + ' 条重要事件(按重要度、日期降序)');
var rows = list.map(function (e, i) {
var typeName = self.TYPE_NAMES[e.report_type] || e.report_type;
var secName = self.SECTION_NAMES[e.section] || e.section;
var extra = ['<span class="meta-item"><i class="fa-regular fa-calendar mr-1"></i>' + selfEscape(e.report_date) + ' · ' + selfEscape(typeName) + ' · ' + selfEscape(secName) + '</span>'];
return self.renderEventRow(e, i + 1, extra);
}).join('');
$('#eventsList').html('<div class="module-card"><div class="module-title"><i class="fa-solid fa-fire text-indigo-600"></i>重要事件聚合<span class="module-sub">跨日报检索 · 重要度 ≥ ' + $('#evImportance').val() + '</span></div><div class="divide-y divide-gray-100">' + rows + '</div></div>');
},
renderEventsError: function (xhr) {
var detail = '';
if (xhr && xhr.responseJSON && xhr.responseJSON.error) detail = xhr.responseJSON.error;
$('#eventsList').html('<div class="empty-box"><i class="fa-solid fa-triangle-exclamation text-red-500"></i><p>重要事件加载失败</p>' + (detail ? '<p class="text-sm text-gray-400">' + selfEscape(detail) + '</p>' : '') + '</div>');
$('#eventsInfo').text('');
},
// ==================== 列表 ====================
loadReports: function () {
var self = this;
@@ -230,13 +301,14 @@ var NewsReports = {
'</div>';
},
renderEventRow: function (e, idx) {
renderEventRow: function (e, idx, extraMeta) {
var sen = this.SENTIMENT[e.sentiment] || this.SENTIMENT.neutral;
var impCls = e.importance >= 5 ? 'imp-5' : (e.importance >= 4 ? 'imp-4' : '');
var title = selfEscape(e.title || '');
if (e.url) title = '<a href="' + selfEscape(e.url) + '" target="_blank" rel="noopener" class="event-link">' + title + '<i class="fa-solid fa-up-right-from-square ml-1 text-xs"></i></a>';
var summary = e.summary ? '<p class="event-summary">' + selfEscape(e.summary) + '</p>' : '';
var meta = [];
if (extraMeta) meta = meta.concat(extraMeta);
if (e.event_type) meta.push('<span class="meta-item"><i class="fa-regular fa-tag mr-1"></i>' + selfEscape(e.event_type) + '</span>');
if (e.source) meta.push('<span class="meta-item"><i class="fa-regular fa-newspaper mr-1"></i>' + selfEscape(e.source) + '</span>');
return '' +
@@ -291,6 +363,12 @@ var NewsReports = {
}
return;
}
if ('count' in item) {
// intl 英文键格式: {"importance":2,"count":2} / {"event_type":"宏观","count":15} / {"source":"X","count":30}
var lk2 = Object.keys(item).filter(function (k) { return k !== 'count'; })[0];
out.push({ label: lk2 ? item[lk2] : '—', value: item['count'] });
return;
}
var keys = Object.keys(item);
if (keys.length >= 2) out.push({ label: item[keys[0]], value: item[keys[1]] });
else if (keys.length === 1) out.push({ label: keys[0], value: item[keys[0]] });