- 升级 ECharts 到 5.4.2,重构 lib/ 目录结构 - 新增 DataTables 2.x 和 FixedColumns 插件 - 新增 quant/ 宏观研究报告模块 - 重构图表页面:stock_trend、hkholdbycode 等采用 JS fetch API.doorcome.cn 模式 - 新增 CLAUDE.md 补充页面文档和新数据流说明 - 更新 inc/config.php TuShare API 配置 - 更新新闻联播分析模块 news/ 和相关研究报告 research/ - 新增 api_document/ 参考文档 - 清理 .gitignore,排除 uploads/xls/.mcp.json/ source maps 等非代码文件 - 所有 PHP 文件通过 php -l 语法检查 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
139 lines
6.5 KiB
PHP
139 lines
6.5 KiB
PHP
<?php
|
|
include_once __DIR__ . "/../inc/config.php";
|
|
|
|
$id = intval($_GET['id'] ?? 0);
|
|
if ($id <= 0) {
|
|
header("Location: index.php");
|
|
exit();
|
|
}
|
|
|
|
$mysqli = get_mysqli_connection();
|
|
$stmt = $mysqli->prepare("SELECT * FROM mac_report WHERE id = ? AND is_active = 1");
|
|
$stmt->bind_param('i', $id);
|
|
$stmt->execute();
|
|
$result = $stmt->get_result();
|
|
$report = $result->fetch_assoc();
|
|
$stmt->close();
|
|
$mysqli->close();
|
|
|
|
if (!$report) {
|
|
header("Location: index.php");
|
|
exit();
|
|
}
|
|
|
|
$date = $report['report_date'];
|
|
$title = $report['title'];
|
|
$content = $report['content'];
|
|
$subject_type = $report['subject_type'];
|
|
$subject_code = $report['subject_code'];
|
|
$created_at = $report['created_at'] ? date('Y-m-d H:i', strtotime($report['created_at'])) : '';
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title><?= htmlspecialchars($title) ?> - 宏观研究</title>
|
|
<script src="/lib/js/tailwindcss-3.4.17.js"></script>
|
|
<script src="/lib/js/marked-15.0.12.min.js"></script>
|
|
<style type="text/tailwindcss">
|
|
@layer utilities {
|
|
.prose h1 { @apply text-2xl font-bold text-gray-900 mt-8 mb-4 pb-2 border-b border-gray-200; }
|
|
.prose h2 { @apply text-xl font-semibold text-gray-900 mt-6 mb-3; }
|
|
.prose h3 { @apply text-lg font-semibold text-gray-800 mt-5 mb-2; }
|
|
.prose h4 { @apply text-base font-semibold text-gray-800 mt-4 mb-2; }
|
|
.prose p { @apply text-gray-700 leading-relaxed my-3; }
|
|
.prose strong { @apply font-semibold text-gray-900; }
|
|
.prose a { @apply text-indigo-600 hover:text-indigo-800 underline decoration-indigo-300 hover:decoration-indigo-600 transition-colors; }
|
|
.prose blockquote { @apply border-l-4 border-indigo-300 bg-indigo-50 pl-4 pr-2 py-2 my-4 rounded-r text-gray-700 italic; }
|
|
.prose ul { @apply list-disc pl-6 my-3 space-y-1; }
|
|
.prose ol { @apply list-decimal pl-6 my-3 space-y-1; }
|
|
.prose li { @apply text-gray-700 leading-relaxed; }
|
|
.prose code { @apply bg-gray-100 text-pink-600 px-1.5 py-0.5 rounded text-sm font-mono; }
|
|
.prose pre { @apply bg-gray-900 text-gray-100 rounded-lg p-4 my-4 overflow-x-auto; }
|
|
.prose pre code { @apply bg-transparent text-gray-100 px-0 py-0; }
|
|
.prose table { @apply w-full border-collapse my-4 text-sm; }
|
|
.prose thead { @apply bg-gray-50; }
|
|
.prose th { @apply border border-gray-300 px-3 py-2 text-left font-semibold text-gray-700; }
|
|
.prose td { @apply border border-gray-300 px-3 py-2 text-gray-600; }
|
|
.prose tr:hover td { @apply bg-gray-50; }
|
|
.prose hr { @apply border-0 border-t border-gray-200 my-8; }
|
|
.prose img { @apply max-w-full rounded-lg my-4; }
|
|
.prose em { @apply italic; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-gray-50 min-h-screen font-sans text-gray-900 antialiased">
|
|
<header class="bg-white shadow-sm sticky top-0 z-50 border-b border-gray-200">
|
|
<div class="max-w-3xl mx-auto px-4 py-4 flex justify-between items-center">
|
|
<div class="flex items-center space-x-2 min-w-0">
|
|
<a href="index.php" class="flex items-center space-x-2 hover:opacity-80 transition-opacity">
|
|
<div class="w-8 h-8 bg-indigo-600 rounded-lg flex items-center justify-center flex-shrink-0">
|
|
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
|
|
</svg>
|
|
</div>
|
|
<span class="text-lg font-bold hidden sm:inline">宏观研究</span>
|
|
</a>
|
|
</div>
|
|
<div class="flex items-center space-x-4">
|
|
<a href="/index-2.php" class="text-sm text-gray-400 hover:text-indigo-600 transition-colors">首页</a>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="max-w-3xl mx-auto px-4 py-8">
|
|
<article>
|
|
<!-- Report header -->
|
|
<div class="mb-8">
|
|
<div class="flex items-center space-x-3 mb-3">
|
|
<?php if ($subject_type): ?>
|
|
<span class="px-2 py-0.5 rounded text-xs font-medium bg-indigo-100 text-indigo-700 uppercase tracking-wide"><?= htmlspecialchars($subject_type) ?></span>
|
|
<?php endif; ?>
|
|
<time class="text-sm text-gray-400">报告日期:<?= $date ?></time>
|
|
<?php if ($created_at): ?>
|
|
<span class="text-sm text-gray-400">生成时间:<?= $created_at ?></span>
|
|
<?php endif; ?>
|
|
</div>
|
|
<h1 class="text-2xl md:text-3xl font-bold text-gray-900 leading-tight"><?= htmlspecialchars($title) ?></h1>
|
|
<?php if ($subject_code): ?>
|
|
<p class="text-sm text-gray-400 mt-2"><?= htmlspecialchars($subject_code) ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<!-- Divider -->
|
|
<div class="border-t border-gray-200 my-6"></div>
|
|
|
|
<!-- Markdown content rendered by marked.js -->
|
|
<div id="content" class="prose max-w-none"></div>
|
|
</article>
|
|
|
|
<div class="border-t border-gray-200 mt-10 pt-6 text-center">
|
|
<a href="index.php" class="inline-flex items-center space-x-1 text-sm text-indigo-600 hover:text-indigo-800 transition-colors">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
|
|
</svg>
|
|
<span>返回列表</span>
|
|
</a>
|
|
</div>
|
|
</main>
|
|
|
|
<footer class="border-t border-gray-200 py-8 text-center text-sm text-gray-400">
|
|
<p>宏观研究 © <?= date('Y') ?></p>
|
|
</footer>
|
|
|
|
<script>
|
|
var rawContent = <?= json_encode($content) ?>;
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Configure marked
|
|
marked.setOptions({
|
|
breaks: true,
|
|
gfm: true,
|
|
});
|
|
var html = marked.parse(rawContent);
|
|
document.getElementById('content').innerHTML = html;
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|