+
+
我的
+
+
+ {(user?.name || '?').slice(0, 1).toUpperCase()}
+
{user?.name || '用户'}
{user?.email}
-
-
- 用户 ID
- #{user?.id}
-
-
- 注册时间
- {user?.createdAt ? new Date(user.createdAt).toLocaleDateString() : '-'}
-
-
- 退出登录
- ›
-
-
+
+ 用户 ID
+
+ 注册时间
+
+
+ 退出登录
+
+
)
-}
\ No newline at end of file
+}
diff --git a/src/pages/MonitorPage.jsx b/src/pages/MonitorPage.jsx
index 46294ee..4b46a28 100644
--- a/src/pages/MonitorPage.jsx
+++ b/src/pages/MonitorPage.jsx
@@ -1,10 +1,16 @@
+import { ErrorBlock, NavBar } from 'antd-mobile'
+
// 监控: 暂留占位
+// 使用 antd-mobile ErrorBlock 仿原生空状态页
export default function MonitorPage() {
return (
-
-
◉
-
监控
-
后续接入: 指标阈值告警 / 时序趋势
+
+ 监控
+
)
-}
\ No newline at end of file
+}
diff --git a/src/pages/macro/ArticleDetail.jsx b/src/pages/macro/ArticleDetail.jsx
index 16ca7ad..7935c5f 100644
--- a/src/pages/macro/ArticleDetail.jsx
+++ b/src/pages/macro/ArticleDetail.jsx
@@ -1,8 +1,10 @@
import { useEffect, useState } from 'react'
-import { useParams, useNavigate, Link } from 'react-router-dom'
+import { useParams, useNavigate } from 'react-router-dom'
+import { NavBar } from 'antd-mobile'
import { api } from '../../api/client.js'
// 文章详情: /macro/articles/:slug
+// 使用 antd-mobile NavBar 提供原生风格的返回按钮
export default function ArticleDetail() {
const { slug } = useParams()
const navigate = useNavigate()
@@ -20,16 +22,19 @@ export default function ArticleDetail() {
return () => { cancelled = true }
}, [slug])
- if (loading) return
加载中...
- if (err) return
{err}
- if (!article) return
文章不存在
-
return (
-
-
navigate(-1)} role="button">‹ 返回
-
{article.title}
-
{article.date}
-
+
+
navigate(-1)}>文章详情
+ {loading &&
加载中...
}
+ {err &&
{err}
}
+ {!loading && !err && !article &&
文章不存在
}
+ {!loading && !err && article && (
+
+
{article.title}
+
{article.date}
+
+
+ )}
)
-}
\ No newline at end of file
+}
diff --git a/src/pages/macro/ArticlesView.jsx b/src/pages/macro/ArticlesView.jsx
index e3accd3..4c33cde 100644
--- a/src/pages/macro/ArticlesView.jsx
+++ b/src/pages/macro/ArticlesView.jsx
@@ -1,12 +1,15 @@
import { useEffect, useState } from 'react'
-import { Link } from 'react-router-dom'
+import { useNavigate } from 'react-router-dom'
+import { List } from 'antd-mobile'
import { api } from '../../api/client.js'
// 经济文章: 列表, 点进 /macro/articles/:slug
+// 使用 antd-mobile List 仿原生设置/通讯录样式
export default function ArticlesView() {
const [articles, setArticles] = useState([])
const [err, setErr] = useState('')
const [loading, setLoading] = useState(true)
+ const navigate = useNavigate()
useEffect(() => {
let cancelled = false
@@ -25,15 +28,23 @@ export default function ArticlesView() {
return (
经济文章
-
+
{articles.map((a) => (
-
- {a.title}
- {a.date}
- {a.excerpt}
-
+
+ {a.date}
+ {a.excerpt}
+ >
+ }
+ arrowIcon
+ clickable
+ onClick={() => navigate('/macro/articles/' + encodeURIComponent(a.slug))}
+ />
))}
-
+
)
-}
\ No newline at end of file
+}
diff --git a/src/pages/macro/DataView.jsx b/src/pages/macro/DataView.jsx
index bc51791..7ce52d5 100644
--- a/src/pages/macro/DataView.jsx
+++ b/src/pages/macro/DataView.jsx
@@ -1,8 +1,11 @@
import { useEffect, useState } from 'react'
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'
+import { CapsuleTabs, Card } from 'antd-mobile'
import { api } from '../../api/client.js'
-// 经济数据: 从 /api/indicators 拉所有指标, 顶上一个 chip 行切换, 下面渲染图表+表格
+// 经济数据: 顶部 CapsuleTabs 选指标, 下面渲染图表 + 表格
+// 使用 antd-mobile: CapsuleTabs / Card
+// 多 groupName 的指标: 在 chip 文本中携带分组前缀以便区分
export default function DataView() {
const [indicators, setIndicators] = useState([])
const [selected, setSelected] = useState(null)
@@ -27,37 +30,32 @@ export default function DataView() {
if (err) return
{err}
if (!indicators.length) return
暂无指标数据, 请先执行 npm run db:seed
- // 按 groupName 分组, chip 行按组排列
- const groups = groupBy(indicators, 'groupName')
+ // 为 chip label 计算: 多个 group 时显示 "分组 / 标题", 单个 group 只显示标题
+ const groupSet = new Set(indicators.map((i) => i.groupName || ''))
+ const showGroupPrefix = groupSet.size > 1
+ const labelOf = (i) => {
+ const t = (i.data && i.data.title) || i.name
+ if (showGroupPrefix && i.groupName) return i.groupName + ' · ' + t
+ return t
+ }
+
const cur = indicators.find((i) => i.name === selected)
const chartData = cur ? buildMonthlySeries(cur.data) : []
return (
经济数据
- {Object.entries(groups).map(([groupName, list]) => (
-
-
{groupName}
-
- {list.map((i) => (
-
- ))}
-
-
- ))}
+
+ {indicators.map((i) => (
+
+ ))}
+
{cur && (
-
-
{cur.data.title || cur.name}
+
- 单位 {cur.data.unit || '-'} · 来源 {cur.data.source || 'seed'}
+ {labelOf(cur)} · 单位 {cur.data.unit || '-'} · 来源 {cur.data.source || 'seed'}
@@ -67,7 +65,7 @@ export default function DataView() {
-
+
@@ -83,27 +81,14 @@ export default function DataView() {
-
+
)}
)
}
-function groupBy(arr, key) {
- return arr.reduce((acc, x) => {
- const k = x[key] || '其它'
- ;(acc[k] = acc[k] || []).push(x)
- return acc
- }, {})
-}
-
-function shortTitle(i) {
- if (i.data && i.data.title) return i.data.title
- return i.name
-}
-
// 把 data.rows: [{month, value}, ...] 直接返回, 服务端已经按月排好
function buildMonthlySeries(data) {
return (data && data.rows) || []
-}
\ No newline at end of file
+}
diff --git a/src/pages/macro/StrategyView.jsx b/src/pages/macro/StrategyView.jsx
index d4a6eb4..5f2fd8e 100644
--- a/src/pages/macro/StrategyView.jsx
+++ b/src/pages/macro/StrategyView.jsx
@@ -1,13 +1,15 @@
import { useEffect, useState } from 'react'
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'
+import { CapsuleTabs, Card } from 'antd-mobile'
import { api } from '../../api/client.js'
-// 策略研究: 顶部 chip 行选策略, 下面显示 4 张图 (debt / gdp / debtToGdp / interestToGdp) + 表格
+// 策略研究: 顶部 CapsuleTabs 选策略, 下面 4 张图 + 表格
+// 使用 antd-mobile: CapsuleTabs / Card
const SERIES = [
- { key: 'debt', label: '政府债务 (万亿美元)', color: '#8884d8' },
- { key: 'gdp', label: 'GDP (万亿美元)', color: '#3cb44b' },
- { key: 'debtToGdp', label: '债务/GDP (%)', color: '#4363d8' },
- { key: 'interestToGdp', label: '债务利息/GDP (%)', color: '#f58231' },
+ { key: 'debt', label: '政府债务 (万亿美元)', color: '#8884d8' },
+ { key: 'gdp', label: 'GDP (万亿美元)', color: '#3cb44b' },
+ { key: 'debtToGdp', label: '债务/GDP (%)', color: '#4363d8' },
+ { key: 'interestToGdp', label: '债务利息/GDP (%)', color: '#f58231' },
]
export default function StrategyView() {
@@ -40,22 +42,14 @@ export default function StrategyView() {
return (
策略研究
-
+
{strategies.map((s) => (
-
+
))}
-
-
+
{cur && (
-
-
{cur.description || cur.name}
+
数据 {rows.length} 条 · 来源 {cur.data.source || 'seed'}
@@ -100,9 +94,9 @@ export default function StrategyView() {
-
+
)}
)
-}
\ No newline at end of file
+}
diff --git a/src/styles/globals.css b/src/styles/globals.css
index 37f382b..17ec4e5 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -1,513 +1,20 @@
-* {
- box-sizing: border-box;
- margin: 0;
- padding: 0;
-}
-
-body {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
- sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- background-color: #f5f5f5;
- color: #333;
-}
-
-.container {
- min-height: 100vh;
- display: flex;
- flex-direction: column;
-}
-
-/* Navbar */
-.navbar {
- background-color: #333;
- color: white;
- padding: 1rem;
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.logo {
- font-size: 1.5rem;
- font-weight: bold;
-}
-
-.logo a {
- color: white;
- text-decoration: none;
-}
-
-.nav-links {
- display: flex;
- gap: 1rem;
-}
-
-.nav-links a {
- color: white;
- text-decoration: none;
- padding: 0.5rem;
- border-radius: 4px;
-}
-
-.nav-links a:hover {
- background-color: #555;
-}
-
-/* Global link styles */
-a {
- text-decoration: none;
- cursor: pointer;
-}
-
-/* Main content */
-main {
- flex: 1;
- padding: 2rem;
- max-width: 1200px;
- margin: 0 auto;
- width: 100%;
-}
-
-.title {
- font-size: 2.5rem;
- margin-bottom: 1rem;
- color: #333;
-}
-
-.description {
- font-size: 1.2rem;
- margin-bottom: 2rem;
- color: #666;
-}
-
-.grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
- gap: 1.5rem;
-}
-
-.card {
- background-color: white;
- padding: 1.5rem;
- border-radius: 8px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
- transition: transform 0.2s ease;
-}
-
-.card:hover {
- transform: translateY(-5px);
-}
-
-.card h2 {
- margin-bottom: 0.5rem;
- color: #333;
-}
-
-.card p {
- color: #666;
-}
-
-/* Footer */
-.footer {
- background-color: #333;
- color: white;
- padding: 1rem;
- text-align: center;
- margin-top: auto;
-}
-
-.footer-content {
- max-width: 1200px;
- margin: 0 auto;
-}
-
-/* Data page layout */
-.data-layout {
- display: flex;
- gap: 2rem;
- margin-top: 2rem;
-}
-
-.data-sidebar {
- width: 250px;
- flex-shrink: 0;
-}
-
-.data-content {
- flex: 1;
- min-width: 0;
-}
-
-.indicator-group {
- margin-bottom: 1.5rem;
-}
-
-.group-title {
- font-size: 1.1rem;
- font-weight: 600;
- color: #333;
- margin-bottom: 0.75rem;
- padding-bottom: 0.5rem;
- border-bottom: 2px solid #ddd;
-}
-
-.indicator-list {
- list-style: none;
- padding: 0;
- margin: 0;
-}
-
-.indicator-item {
- padding: 0.75rem 1rem;
- margin-bottom: 0.5rem;
- background-color: white;
- border-radius: 6px;
- cursor: pointer;
- transition: all 0.2s ease;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
-}
-
-.indicator-item:hover {
- background-color: #f0f0f0;
- transform: translateX(4px);
-}
-
-.indicator-item.active {
- background-color: #333;
- color: white;
-}
-
-.indicator-detail {
- background-color: white;
- padding: 2rem;
- border-radius: 8px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-}
-
-.indicator-title {
- font-size: 1.8rem;
- color: #333;
- margin-bottom: 0.5rem;
-}
-
-.indicator-description {
- color: #666;
- margin-bottom: 2rem;
-}
-
-.chart-container {
- margin-bottom: 3rem;
- padding: 1rem;
- background-color: #fafafa;
- border-radius: 8px;
-}
-
-.data-table-container {
- margin-top: 2rem;
-}
-
-.table-title {
- font-size: 1.3rem;
- color: #333;
- margin-bottom: 1rem;
-}
-
-.data-table {
- width: 100%;
- border-collapse: collapse;
- font-size: 0.95rem;
-}
-
-.data-table th,
-.data-table td {
- padding: 0.75rem 1rem;
- text-align: left;
- border-bottom: 1px solid #eee;
-}
-
-.data-table th {
- background-color: #f5f5f5;
- font-weight: 600;
- color: #333;
-}
-
-.data-table tbody tr:hover {
- background-color: #f9f9f9;
-}
-
-/* Articles section */
-.articles-section {
- margin-top: 4rem;
- margin-bottom: 2rem;
-}
-
-.section-title {
- font-size: 2rem;
- color: #333;
- margin-bottom: 1.5rem;
- padding-bottom: 0.5rem;
- border-bottom: 2px solid #ddd;
-}
-
-.articles-grid {
- display: flex;
- flex-direction: column;
- gap: 1rem;
- margin-bottom: 2rem;
-}
-
-.article-card {
- background-color: white;
- padding: 1.5rem;
- border-radius: 8px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
- transition: all 0.2s ease;
- cursor: pointer;
-}
-
-.article-card:hover {
- background-color: #f9f9f9;
- transform: translateX(5px);
-}
-
-.article-date {
- font-size: 0.9rem;
- color: #666;
- margin-bottom: 0.75rem;
-}
-
-.article-title {
- font-size: 1.2rem;
- font-weight: 600;
- color: #333;
- margin-bottom: 0.75rem;
- line-height: 1.4;
-}
-
-.article-excerpt {
- color: #666;
- line-height: 1.5;
- font-size: 0.95rem;
-}
-
-/* Pagination */
-.pagination {
- display: flex;
- justify-content: flex-end;
- gap: 0.5rem;
- margin-top: 2rem;
-}
-
-.page-link {
- padding: 0.5rem 1rem;
- background-color: white;
- border: 1px solid #ddd;
- border-radius: 4px;
- color: #333;
- text-decoration: none;
- transition: all 0.2s ease;
-}
-
-.page-link:hover {
- background-color: #f0f0f0;
-}
-
-.page-link.active {
- background-color: #333;
- color: white;
- border-color: #333;
-}
-
-/* Article detail page */
-.article-detail {
- max-width: 800px;
- margin: 0 auto;
- background-color: white;
- padding: 2rem;
- border-radius: 8px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-}
-
-.article-detail-title {
- font-size: 2.5rem;
- color: #333;
- margin-bottom: 1rem;
- line-height: 1.2;
-}
-
-.article-detail-date {
- font-size: 1rem;
- color: #666;
- margin-bottom: 2rem;
- padding-bottom: 1rem;
- border-bottom: 1px solid #eee;
-}
-
-.article-detail-content {
- color: #333;
- line-height: 1.6;
-}
-
-.article-detail-content h1,
-.article-detail-content h2,
-.article-detail-content h3,
-.article-detail-content h4,
-.article-detail-content h5,
-.article-detail-content h6 {
- margin-top: 2rem;
- margin-bottom: 1rem;
- color: #333;
-}
-
-.article-detail-content p {
- margin-bottom: 1.5rem;
-}
-
-.article-detail-content ul,
-.article-detail-content ol {
- margin-bottom: 1.5rem;
- padding-left: 2rem;
-}
-
-.article-detail-content li {
- margin-bottom: 0.5rem;
-}
-
-.article-detail-content img {
- max-width: 100%;
- height: auto;
- margin: 2rem 0;
- border-radius: 8px;
-}
-
-@media (max-width: 900px) {
- .data-layout {
- flex-direction: column;
- }
-
- .data-sidebar {
- width: 100%;
- }
-
- .pagination {
- justify-content: center;
- }
-
- .article-detail {
- padding: 1.5rem;
- }
-
- .article-detail-title {
- font-size: 2rem;
- }
-
- .strategy-layout {
- flex-direction: column;
- }
-
- .strategy-sidebar {
- width: 100%;
- }
-}
-
-/* Strategy page layout */
-.strategy-layout {
- display: flex;
- gap: 2rem;
- margin-top: 2rem;
-}
-
-.strategy-sidebar {
- width: 250px;
- flex-shrink: 0;
-}
-
-.strategy-content {
- flex: 1;
- min-width: 0;
-}
-
-.sidebar-title {
- font-size: 1.1rem;
- font-weight: 600;
- color: #333;
- margin-bottom: 0.75rem;
- padding-bottom: 0.5rem;
- border-bottom: 2px solid #ddd;
-}
-
-.strategy-list {
- list-style: none;
- padding: 0;
- margin: 0;
-}
-
-.strategy-item {
- padding: 0.75rem 1rem;
- margin-bottom: 0.5rem;
- background-color: white;
- border-radius: 6px;
- cursor: pointer;
- transition: all 0.2s ease;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
-}
-
-.strategy-item:hover {
- background-color: #f0f0f0;
- transform: translateX(4px);
-}
-
-.strategy-item.active {
- background-color: #333;
- color: white;
-}
-
-.strategy-detail {
- background-color: white;
- padding: 2rem;
- border-radius: 8px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-}
-
-.strategy-title {
- font-size: 1.8rem;
- color: #333;
- margin-bottom: 0.5rem;
-}
-
-.strategy-description {
- color: #666;
- margin-bottom: 2rem;
-}
-
-.chart-section {
- margin-bottom: 3rem;
-}
-
-.chart-title {
- font-size: 1.3rem;
- color: #333;
- margin-bottom: 1rem;
-}
-
-
-/* ================== 移动端优先布局 (mobile-first) ================== */
+/* ================== 移动端优先布局 (mobile-first, antd-mobile) ================== */
+/* 主题变量: 与 antd-mobile 的 --adm-* 保持一致, 便于组件统一 */
:root {
--color-bg: #f5f5f5;
--color-card: #ffffff;
--color-text: #333333;
--color-muted: #666666;
- --color-border: #e5e7eb;
- --color-primary: #1f2937;
- --color-accent: #2563eb;
- --color-danger: #dc2626;
- --bottom-nav-h: 56px;
- --top-tabs-h: 44px;
+ --color-border: #eeeeee;
+ --color-primary: #1677ff;
+ --color-accent: #1677ff;
+ --color-danger: #ff3141;
+ --safe-top: env(safe-area-inset-top, 0px);
--safe-bottom: env(safe-area-inset-bottom, 0px);
+ --safe-left: env(safe-area-inset-left, 0px);
+ --safe-right: env(safe-area-inset-right, 0px);
+ --tabbar-h: 50px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
@@ -528,178 +35,86 @@ a { color: inherit; text-decoration: none; cursor: pointer; }
button { font: inherit; cursor: pointer; border: none; background: none; color: inherit; }
input, textarea, select { font: inherit; }
-/* 应用主框架 */
+/* 应用主框架: 全屏 flex 列布局, 不再 max-width 居中 (解决"左右不贴边") */
.app-root {
display: flex;
flex-direction: column;
- min-height: 100vh;
- min-height: 100dvh;
- max-width: 768px;
- margin: 0 auto;
+ height: 100vh;
+ height: 100dvh;
+ width: 100%;
+ max-width: 100%;
background: var(--color-bg);
position: relative;
+ padding-left: var(--safe-left);
+ padding-right: var(--safe-right);
}
.app-main {
flex: 1 1 auto;
- padding-bottom: calc(var(--bottom-nav-h) + var(--safe-bottom) + 8px);
+ min-height: 0;
overflow-y: auto;
+ overflow-x: hidden;
+ padding-bottom: calc(var(--tabbar-h) + var(--safe-bottom));
+ -webkit-overflow-scrolling: touch;
}
-.app-main.has-top-tabs {
- padding-top: var(--top-tabs-h);
-}
+/* 带有 antd-mobile NavBar 的页面 (Home/Monitor/Me/Article) */
+.page-with-navbar { display: flex; flex-direction: column; min-height: 100%; }
+.page-with-navbar .adm-error-block { flex: 1; }
-/* 顶栏 tabs (宏观页用) */
-.top-tabs {
- position: sticky;
- top: 0;
- z-index: 10;
- height: var(--top-tabs-h);
- display: flex;
- background: #fff;
- border-bottom: 1px solid var(--color-border);
- align-items: stretch;
-}
-
-.top-tabs-item {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 14px;
- color: var(--color-muted);
- position: relative;
- user-select: none;
-}
-
-.top-tabs-item.active {
- color: var(--color-primary);
- font-weight: 600;
-}
-
-.top-tabs-item.active::after {
- content: '';
- position: absolute;
- left: 30%;
- right: 30%;
- bottom: 6px;
- height: 2px;
- background: var(--color-primary);
- border-radius: 2px;
-}
-
-/* 滑屏容器 */
-.swipe-container {
- position: relative;
- overflow: hidden;
- touch-action: pan-y;
-}
-
-.swipe-track {
- display: flex;
- width: 300%;
- transform: translateX(var(--swipe-offset, 0%));
- transition: transform 0.25s ease;
- will-change: transform;
-}
-
-.swipe-track.no-anim { transition: none; }
-
-.swipe-pane {
- width: 33.3333%;
- flex-shrink: 0;
- overflow-y: auto;
-}
-
-/* 底部 tab */
-.bottom-nav {
+/* 底部 antd-mobile TabBar 定位 */
+.app-tabbar.adm-tab-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
- height: calc(var(--bottom-nav-h) + var(--safe-bottom));
- padding-bottom: var(--safe-bottom);
- display: flex;
background: #fff;
border-top: 1px solid var(--color-border);
- max-width: 768px;
- margin: 0 auto;
}
+.tab-icon { display: inline-flex; }
-.bottom-nav-item {
- flex: 1;
+/* 宏观页 SwipeTabs */
+.swipe-tabs-wrap {
display: flex;
flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 2px;
- font-size: 11px;
- color: var(--color-muted);
- user-select: none;
+ height: 100%;
+ min-height: 0;
+}
+.swipe-container {
+ position: relative;
+ flex: 1 1 auto;
+ min-height: 0;
+ overflow: hidden;
+ touch-action: pan-y;
+}
+.swipe-track {
+ display: flex;
+ width: 300%;
+ height: 100%;
+ transform: translateX(var(--swipe-offset, 0%));
+ transition: transform 0.25s ease;
+ will-change: transform;
+}
+.swipe-track.no-anim { transition: none; }
+.swipe-pane {
+ width: 33.3333%;
+ flex-shrink: 0;
+ height: 100%;
+ overflow-y: auto;
+ overflow-x: hidden;
+ -webkit-overflow-scrolling: touch;
}
-.bottom-nav-item .icon { font-size: 20px; line-height: 1; }
-.bottom-nav-item.active { color: var(--color-primary); font-weight: 600; }
+/* ================== 通用组件辅助类 ================== */
-/* 通用卡片 */
-.card { background: #fff; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 2px rgba(0,0,0,.04); }
-.card + .card { margin-top: 10px; }
-
-.page-title { font-size: 20px; font-weight: 700; padding: 14px 16px 4px; }
-.page-desc { font-size: 13px; color: var(--color-muted); padding: 0 16px 12px; }
-.section-title { font-size: 16px; font-weight: 600; padding: 14px 16px 8px; }
-
-/* 列表 */
-.list { padding: 0 12px; }
-.list-item { background: #fff; padding: 14px; border-radius: 10px; margin-bottom: 10px; box-shadow: 0 1px 2px rgba(0,0,0,.04); display: block; }
-.list-item-title { font-size: 15px; font-weight: 600; margin-bottom: 4px; line-height: 1.4; }
-.list-item-meta { font-size: 12px; color: var(--color-muted); margin-bottom: 6px; }
-.list-item-excerpt { font-size: 13px; color: var(--color-muted); line-height: 1.5; }
-
-/* 图表 */
-.chart-wrap { background: #fafafa; border-radius: 8px; padding: 8px; margin: 8px 0 16px; }
-
-/* 表格 */
-.data-table { width: 100%; border-collapse: collapse; font-size: 13px; }
-.data-table th, .data-table td { padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--color-border); }
-.data-table th { background: #f8f8f8; font-weight: 600; }
-
-/* 登录 */
-.login-page { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 24px; background: linear-gradient(180deg, #1f2937 0%, #1f2937 35%, #f5f5f5 35%); }
-.login-card { background: #fff; width: 100%; max-width: 380px; border-radius: 14px; padding: 24px; box-shadow: 0 4px 24px rgba(0,0,0,.1); }
-.login-title { font-size: 20px; font-weight: 700; text-align: center; margin-bottom: 6px; }
-.login-subtitle { text-align: center; color: var(--color-muted); margin-bottom: 20px; font-size: 13px; }
-.login-field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
-.login-field label { font-size: 12px; color: var(--color-muted); }
-.login-field input { padding: 12px; border: 1px solid var(--color-border); border-radius: 8px; outline: none; }
-.login-field input:focus { border-color: var(--color-primary); }
-.login-options { display: flex; align-items: center; gap: 6px; margin-bottom: 18px; font-size: 13px; color: var(--color-muted); }
-.login-btn { width: 100%; padding: 13px; background: var(--color-primary); color: #fff; border-radius: 8px; font-weight: 600; }
-.login-btn:disabled { opacity: 0.6; }
-.login-hint { text-align: center; font-size: 12px; color: var(--color-muted); margin-top: 14px; line-height: 1.6; }
-.login-error { background: #fef2f2; color: var(--color-danger); padding: 10px; border-radius: 6px; font-size: 13px; margin-bottom: 12px; }
-
-/* 我 */
-.me-header { background: var(--color-primary); color: #fff; padding: 30px 16px 24px; }
-.me-name { font-size: 18px; font-weight: 600; }
-.me-email { font-size: 13px; opacity: .8; margin-top: 2px; }
-.me-list { padding: 12px; }
-.me-list .list-item { display: flex; align-items: center; justify-content: space-between; }
-.me-list .list-item .label { font-size: 14px; }
-.me-list .list-item .value { font-size: 13px; color: var(--color-muted); }
-.me-list .danger { color: var(--color-danger); }
-
-.loading, .page-loading { padding: 40px; text-align: center; color: var(--color-muted); }
-.empty { padding: 40px; text-align: center; color: var(--color-muted); font-size: 13px; }
-.error-banner { background: #fef2f2; color: var(--color-danger); padding: 10px 14px; font-size: 13px; border-radius: 6px; margin: 12px; }
-
-/* 占位页 */
-.placeholder-page { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 60vh; padding: 24px; text-align: center; color: var(--color-muted); gap: 12px; }
-.placeholder-page .icon { font-size: 48px; opacity: .6; }
+/* 图表卡片: 用于 DataView/StrategyView */
+.chart-card.adm-card { padding: 12px; }
+.chart-card.adm-card .adm-card-body { padding: 0; }
/* 文章详情 */
+.article-page { display: flex; flex-direction: column; min-height: 100%; }
+.article-page .adm-nav-bar { background: #fff; border-bottom: 1px solid var(--color-border); }
.article-detail { background: #fff; padding: 18px 16px 24px; }
.article-detail h1 { font-size: 20px; line-height: 1.35; margin-bottom: 8px; }
.article-detail .meta { font-size: 12px; color: var(--color-muted); margin-bottom: 16px; padding-bottom: 10px; border-bottom: 1px solid var(--color-border); }
@@ -708,10 +123,48 @@ input, textarea, select { font: inherit; }
.article-content p { margin-bottom: 0.9em; }
.article-content ul, .article-content ol { margin: 0 0 1em 1.4em; }
.article-content img { max-width: 100%; border-radius: 6px; margin: 0.6em 0; }
-.article-back { display: inline-flex; align-items: center; gap: 4px; padding: 8px 0; color: var(--color-accent); font-size: 13px; }
-/* 侧边栏式选择器 (移动端改为顶部 chip 行) */
-.chip-row { display: flex; gap: 8px; overflow-x: auto; padding: 8px 12px; }
-.chip-row::-webkit-scrollbar { display: none; }
-.chip { padding: 7px 12px; background: #fff; border: 1px solid var(--color-border); border-radius: 999px; font-size: 13px; white-space: nowrap; }
-.chip.active { background: var(--color-primary); color: #fff; border-color: var(--color-primary); }
+/* 文章列表项 (用于 ArticlesView) */
+.articles-list .adm-list-item-description { color: var(--color-muted); }
+.list-item-meta { font-size: 12px; color: var(--color-muted); margin-bottom: 4px; }
+.list-item-excerpt { font-size: 13px; color: var(--color-muted); line-height: 1.5; }
+
+/* 图表 / 表格 */
+.chart-wrap { background: #fafafa; border-radius: 8px; padding: 8px; margin: 8px 0 16px; }
+.data-table { width: 100%; border-collapse: collapse; font-size: 13px; }
+.data-table th, .data-table td { padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--color-border); }
+.data-table th { background: #f8f8f8; font-weight: 600; }
+
+/* 页面标题 */
+.page-title { font-size: 20px; font-weight: 700; padding: 14px 16px 4px; }
+
+/* 通用状态 */
+.loading, .page-loading { padding: 40px; text-align: center; color: var(--color-muted); }
+.empty { padding: 40px; text-align: center; color: var(--color-muted); font-size: 13px; }
+.error-banner { background: #fef2f2; color: var(--color-danger); padding: 10px 14px; font-size: 13px; border-radius: 6px; margin: 12px; }
+
+/* ================== 登录页 (独立全屏, 未走 MobileShell) ================== */
+.login-page { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 24px; background: linear-gradient(180deg, #1f2937 0%, #1f2937 38%, #f5f5f5 38%); }
+.login-form { width: 100%; max-width: 380px; }
+.login-brand { color: #fff; font-size: 28px; font-weight: 700; text-align: center; margin-bottom: 4px; }
+.login-subtitle { color: rgba(255,255,255,.7); text-align: center; margin-bottom: 16px; font-size: 13px; }
+.login-card.adm-card { border-radius: 14px; }
+.login-card.adm-card .adm-card-body { padding: 22px 18px; }
+.login-field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
+.login-field label { font-size: 12px; color: var(--color-muted); }
+.login-field .adm-input { padding: 8px 10px; border: 1px solid var(--color-border); border-radius: 8px; }
+.login-field .adm-input:focus-within { border-color: var(--color-primary); }
+.login-options { margin-bottom: 18px; }
+.login-hint { text-align: center; font-size: 12px; color: var(--color-muted); margin-top: 14px; line-height: 1.6; }
+.login-error { background: #fef2f2; color: var(--color-danger); padding: 10px; border-radius: 6px; font-size: 13px; margin-bottom: 12px; }
+
+/* ================== 我的页 ================== */
+.me-page { display: flex; flex-direction: column; min-height: 100%; background: var(--color-bg); }
+.me-navbar.adm-nav-bar { background: #fff; border-bottom: 1px solid var(--color-border); }
+.me-profile { background: linear-gradient(135deg, #1677ff, #4096ff); color: #fff; padding: 24px 16px 28px; display: flex; flex-direction: column; align-items: center; gap: 6px; }
+.me-avatar { background: rgba(255,255,255,.2); color: #fff; font-size: 28px; }
+.me-name { font-size: 18px; font-weight: 600; }
+.me-email { font-size: 13px; opacity: .85; }
+.me-list { margin-top: 12px; }
+.me-list .adm-list-item { padding-left: 16px; padding-right: 16px; }
+.me-logout { color: var(--color-danger); }
\ No newline at end of file