Merge remote-tracking branch 'origin/qyqy_develop' into RM2_develop

This commit is contained in:
zhangshy
2026-09-13 23:52:17 +08:00
14 changed files with 480 additions and 24 deletions
+1
View File
@@ -4,6 +4,7 @@ const ENDPOINTS = Object.freeze({
A034: { method: 'POST', path: '/api/v1/auth/tokens', auth: false },
V001: { method: 'POST', path: '/api/v1/visitor-tokens', auth: false, raw: true },
P001: { method: 'GET', path: '/api/v1/products' },
P002: { method: 'GET', path: '/api/v1/products/{productCode}/nav-history' },
C001: { method: 'POST', path: '/api/v1/conversations', idempotent: true },
C002: { method: 'GET', path: '/api/v1/conversations/{sessionId}' },
C003: { method: 'GET', path: '/api/v1/conversations/{sessionId}/messages' },
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>基金详情 · 南方财富</title>
<link rel="stylesheet" href="/static/portal/common/base.css">
<link rel="stylesheet" href="/static/portal/guest/product-detail/product-detail.css?v=20260913-6">
<link rel="stylesheet" href="/static/portal/guest/product-detail/product-detail.css?v=20260913-7">
</head>
<body>
<main id="main-content" class="page-shell" data-detail-root>
@@ -13,11 +13,11 @@
<div class="source-notice" role="note" data-source-notice></div>
<div class="product-note" role="note" data-product-note hidden></div>
<section class="detail-grid">
<article class="panel detail-chart"><div class="panel__header"><h2 class="panel__title">历史净值</h2><span class="section-heading__meta">近十二期</span></div><div class="panel__body"><div class="detail-chart__quote"><strong data-current-nav>--</strong><span data-change>--</span></div><svg class="detail-chart__svg" viewBox="0 0 760 280" role="img" aria-label="基金历史净值走势" data-chart></svg><div class="detail-chart__axis"><span data-start-date></span><span data-end-date></span></div></div></article>
<article class="panel detail-chart"><div class="panel__header"><h2 class="panel__title">历史净值</h2><span class="section-heading__meta" data-chart-range>近 120 个交易日</span></div><div class="panel__body"><div class="detail-chart__quote"><strong data-current-nav>--</strong><span data-change>--</span></div><svg class="detail-chart__svg" viewBox="0 0 760 280" role="img" aria-label="基金历史净值走势" data-chart></svg><div class="detail-chart__axis"><span data-start-date></span><span data-end-date></span></div></div></article>
<aside class="panel"><div class="panel__header"><h2 class="panel__title">产品要素</h2></div><div class="panel__body detail-facts" data-product-facts></div></aside>
</section>
<section class="panel risk-disclosure"><div class="panel__header"><h2 class="panel__title">风险与交易说明</h2></div><div class="panel__body"><div class="risk-disclosure__grid"><div><strong>风险等级</strong><span data-risk-level></span></div><div><strong>最小金额</strong><span data-min-amount></span></div><div><strong>交易方式</strong><span>场内市价模拟成交</span></div></div><p>基金净值会随市场变化。历史数据不代表未来表现,交易前应结合自身风险承受能力判断。</p></div></section>
</main>
<script type="module" src="/static/portal/guest/product-detail/product-detail.js?v=20260913-6"></script>
<script type="module" src="/static/portal/guest/product-detail/product-detail.js?v=20260913-7"></script>
</body>
</html>
@@ -8,6 +8,8 @@
.detail-chart__grid { fill: none; stroke: var(--line); stroke-width: 1; }
.detail-chart__line { fill: none; stroke: var(--brand); stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.detail-chart__axis { display: flex; justify-content: space-between; color: var(--muted); font-size: var(--fs-small); }
/* 无历史净值时的占位:撑到与图表相近的高度,避免整块塌陷成一行小字。 */
.detail-chart__empty { display: flex; align-items: center; justify-content: center; min-height: 240px; margin: var(--space-4) 0 0; padding: var(--space-4); color: var(--muted); background: var(--surface); border: 1px dashed var(--line); border-radius: var(--radius-sm); font-size: var(--fs-small); text-align: center; }
.detail-facts { display: grid; padding-top: var(--space-2); padding-bottom: var(--space-2); }
.detail-facts__row { min-height: var(--control-height); display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); border-bottom: 1px solid var(--line); }
.detail-facts__row span { color: var(--muted); }
@@ -16,7 +16,7 @@ function orDash(value, render) {
return value === null || value === undefined || value === '' ? '--' : render(value);
}
function render(product) {
function render(product, points) {
document.title = `${product.product_name} · 南方财富`;
document.querySelector('[data-product-name]').textContent = product.product_name;
document.querySelector('[data-product-meta]').textContent =
@@ -65,11 +65,45 @@ function render(product) {
].map(([label, value]) =>
`<div class="detail-facts__row"><span>${escapeHtml(label)}</span><strong>${escapeHtml(value)}</strong></div>`).join('');
// 没有真实历史净值时不绘制虚构曲线,避免把 mock 数据误认为行情。
document.querySelector('[data-chart]').outerHTML =
'<p class="detail-chart__empty">历史净值数据尚未接入,暂不展示走势图。</p>';
document.querySelector('[data-start-date]').textContent = '';
document.querySelector('[data-end-date]').textContent = product.latest_trade_date || '';
renderChart(points);
}
/**
* 画历史净值走势。数据来自 `GET /api/v1/products/{code}/nav-history`(P002),
* 即 `fin_nav_history` 表 —— 由 `tools/sync_nav_history.py` 从东财净值接口同步。
*
* ⚠️ 取不到数据时**不画任何曲线**:此前那条线是演示数据里编的 12 个点位,
* 而走势图最容易被当成真实业绩,宁可不画,也要把"尚未接入"说清楚。
*/
function renderChart(points) {
const chart = document.querySelector('[data-chart]');
const startNode = document.querySelector('[data-start-date]');
const endNode = document.querySelector('[data-end-date]');
const rangeNode = document.querySelector('[data-chart-range]');
if (!points.length) {
chart.outerHTML =
'<p class="detail-chart__empty">历史净值数据尚未接入,暂不展示走势图。</p>';
startNode.textContent = '';
endNode.textContent = '';
if (rangeNode) rangeNode.textContent = '';
return;
}
const values = points.map((item) => Number(item.nav));
const min = Math.min(...values);
const max = Math.max(...values);
const range = max - min || 1;
const step = values.length > 1 ? 740 / (values.length - 1) : 0;
const coords = values
.map((value, index) => {
const x = index * step + 10;
const y = 250 - ((value - min) / range) * 210;
return `${x.toFixed(1)},${y.toFixed(1)}`;
})
.join(' ');
chart.innerHTML = `<path class="detail-chart__grid" d="M10 40H750M10 110H750M10 180H750M10 250H750"/><polyline class="detail-chart__line" points="${coords}"/>`;
startNode.textContent = points[0].nav_date;
endNode.textContent = points[points.length - 1].nav_date;
if (rangeNode) rangeNode.textContent = `近 ${points.length} 个交易日`;
}
async function load() {
@@ -78,7 +112,23 @@ async function load() {
const { data } = await apiClient.get('P001', { headers });
const products = data?.products || [];
if (!products.length) throw new Error('产品库暂时没有可展示的产品');
render(products.find((item) => item.product_code === requestedCode) || products[0]);
const product = products.find((item) => item.product_code === requestedCode) || products[0];
// 历史净值单独取,且**失败不影响产品信息展示**(走势图会显示"尚未接入"):
// 产品要素是主内容,不该因为一张图取不到就整页报错。
let points = [];
try {
const history = await apiClient.get('P002', {
headers,
pathParams: { productCode: product.product_code },
query: { days: 120 },
});
points = history.data?.points || [];
} catch {
points = [];
}
render(product, points);
} catch (error) {
renderError(root, error, load);
}