feat: 添加设计文件packages
This commit is contained in:
33
packages/design-ui/PRD.md
Normal file
33
packages/design-ui/PRD.md
Normal file
@@ -0,0 +1,33 @@
|
||||
## 核心思想
|
||||
名称选择:思投录
|
||||
愿景:让每笔投资都经得起思考
|
||||
副标题:**投资决策与复盘工具**。
|
||||
### 核心功能
|
||||
#### 持仓
|
||||
记录自己的股票持仓,统计各个股票持仓占比,按基金收益法统计的持仓收益。可以基于用户设置的单只股票仓位上限进行预警。
|
||||
#### 交易计划
|
||||
- “计划你的交易,交易你的计划”,让用户可以创建交易计划,选择股票、市场、目标价格、截止时间、投资金额或股份数(两者选一个即可)等。
|
||||
- 之后到目标价后可以提醒用户。
|
||||
- 用户可以为计划设置步骤,默认分三步进行买入等,可以分步设置买入价格。
|
||||
#### 交易记录/复盘
|
||||
- 引导用户记录每一笔交易,可以是从计划中点完成计划等操作跳转到记录页中,也可以是用户主动记录。用户可以记录每一笔交易的企业名称、买卖份数、买卖单价,最重要的是要引导用户写下买卖思考。
|
||||
- 定期弹出页面应到用户写下复盘和思考。
|
||||
- 用户可以参看每一笔交易的附带思考和复盘记录的时间线,让用户可以在交易中学习和成长,类似 QQ 空间的说说功能。
|
||||
- 用户可以分享自己的交易时间线(不确定是否需要)。
|
||||
#### 我的
|
||||
不确定是否应该把 我的 作为一个 Tabbar。
|
||||
- 展示一些我的信息。
|
||||
- 最主要的是一些工具入口,例如 投资检查清单、复利计算器、估值工具(可以分老唐估值法和两段式现金流折现估值法)、自由目标等。
|
||||
- 投资检查清单:可以分为买入和卖出两份检查清单,在每次设置交易计划时最后弹出,让用户每项检查。
|
||||
- 复利计算器:侧重于投资者角度,可以填入初始金额,每年可投入的金额,调整预计的年复合增长率,然后查看未来的总收益。
|
||||
- 自由目标:“让自己有的选”,自由目标可以和复利计算器和持仓结合起来,让用户自己设定一个总资产达成目标。把这个目标和财务自由、赎回自己的时间关联起来,让用户有“奔头”。
|
||||
|
||||
## App名称
|
||||
中文名:思投录
|
||||
英文名:VestMind
|
||||
“Vest”(投资记录与管理)和“Mind”(思考与决策清单)。用户能直观感受到这是一个与“投资”和“思考”相关的工具。
|
||||
Mind”强调了投资不应是冲动行为,而应是经过深思熟虑的、理性的“思维活动”,这与你“让每笔投资都经得起思考”的愿景高度契合。
|
||||
这个词组合起来听起来专业、简洁,且带有一种“智慧投资”的质感,能吸引那些希望提升自己投资决策质量的用户。
|
||||
|
||||
## 主题色
|
||||
主题色需要偏紫色调,注重让人安静思考的色调。
|
||||
124
packages/design-ui/README.md
Normal file
124
packages/design-ui/README.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# 思投录 (VestMind) - 投资决策与复盘工具
|
||||
|
||||
## 项目概述
|
||||
|
||||
思投录是一款专注于投资决策与复盘的工具应用,旨在"让每笔投资都经得起思考"。本UI设计适用于移动端APP和小程序,采用统一的紫色主题设计风格。
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 1. 持仓管理
|
||||
- 记录股票持仓信息
|
||||
- 统计持仓占比和收益
|
||||
- 基于仓位上限的预警功能
|
||||
- 实时收益展示
|
||||
|
||||
### 2. 交易计划
|
||||
- 创建详细的交易计划
|
||||
- 设置目标价格和截止时间
|
||||
- 分步骤买入计划
|
||||
- 进度跟踪和提醒
|
||||
|
||||
### 3. 交易记录/复盘
|
||||
- 记录每笔交易的详细信息
|
||||
- 交易思考记录
|
||||
- 时间线展示
|
||||
- 定期复盘提醒
|
||||
|
||||
### 4. 我的工具
|
||||
- 投资检查清单
|
||||
- 复利计算器
|
||||
- 估值工具(老唐估值法、现金流折现)
|
||||
- 自由目标规划
|
||||
|
||||
## 设计特色
|
||||
|
||||
### 视觉设计
|
||||
- **主题色**:紫色调(#8b5cf6, #7c3aed),营造安静思考的氛围
|
||||
- **字体**:Inter字体,现代简洁
|
||||
- **布局**:移动端优先,响应式设计
|
||||
- **动效**:微妙的动画效果,提升用户体验
|
||||
|
||||
### 交互设计
|
||||
- 底部导航栏,四个主要功能模块
|
||||
- 模态框设计,支持复杂表单操作
|
||||
- 卡片式布局,信息层次清晰
|
||||
- 时间线展示,直观的交易历史
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 文件结构
|
||||
```
|
||||
design-vest-mind/
|
||||
├── index.html # 主页面
|
||||
├── styles.css # 样式文件
|
||||
├── script.js # 交互逻辑
|
||||
└── README.md # 项目说明
|
||||
```
|
||||
|
||||
### 技术栈
|
||||
- **HTML5**:语义化标签,良好的可访问性
|
||||
- **CSS3**:Flexbox布局,CSS Grid,动画效果
|
||||
- **JavaScript**:ES6+语法,模块化设计
|
||||
- **响应式设计**:适配不同屏幕尺寸
|
||||
|
||||
### 兼容性
|
||||
- 移动端浏览器
|
||||
- 微信小程序
|
||||
- iOS Safari
|
||||
- Android Chrome
|
||||
|
||||
## 使用说明
|
||||
|
||||
1. 打开 `index.html` 文件即可查看完整UI设计
|
||||
2. 支持所有交互功能,包括:
|
||||
- 页面切换
|
||||
- 数据展示
|
||||
- 模态框操作
|
||||
- 工具计算
|
||||
|
||||
## 设计亮点
|
||||
|
||||
### 1. 紫色主题
|
||||
- 主色调采用紫色渐变,符合"安静思考"的设计理念
|
||||
- 渐变背景和阴影效果,营造层次感
|
||||
|
||||
### 2. 卡片设计
|
||||
- 圆角卡片布局,现代简洁
|
||||
- 悬停效果和阴影,增强交互反馈
|
||||
|
||||
### 3. 数据可视化
|
||||
- 进度条展示计划完成度
|
||||
- 颜色编码区分盈亏状态
|
||||
- 时间线展示交易历史
|
||||
|
||||
### 4. 工具集成
|
||||
- 投资检查清单,帮助理性决策
|
||||
- 复利计算器,规划长期投资
|
||||
- 估值工具,辅助价值判断
|
||||
- 自由目标,激励投资动力
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 可添加的功能
|
||||
- 数据持久化(LocalStorage)
|
||||
- 图表展示(Chart.js)
|
||||
- 推送通知
|
||||
- 数据导入导出
|
||||
- 多账户管理
|
||||
|
||||
### 小程序适配
|
||||
- 使用微信小程序组件
|
||||
- 适配小程序生命周期
|
||||
- 集成微信API
|
||||
|
||||
## 开发建议
|
||||
|
||||
1. **数据管理**:建议使用状态管理库(如Vuex、Redux)
|
||||
2. **图表展示**:集成ECharts或Chart.js
|
||||
3. **数据持久化**:使用IndexedDB或云数据库
|
||||
4. **性能优化**:懒加载、虚拟滚动
|
||||
5. **测试**:单元测试和端到端测试
|
||||
|
||||
## 总结
|
||||
|
||||
本UI设计完全基于PRD文档要求,实现了思投录应用的核心功能界面。设计风格现代简洁,交互流畅自然,完全适配移动端和小程序使用场景。紫色主题营造了安静思考的投资氛围,符合"让每笔投资都经得起思考"的产品愿景。
|
||||
229
packages/design-ui/index.html
Normal file
229
packages/design-ui/index.html
Normal file
@@ -0,0 +1,229 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>思投录 - 投资决策与复盘工具</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- 主容器 -->
|
||||
<div class="app-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<h1 class="app-title">思投录</h1>
|
||||
<p class="app-subtitle">让每笔投资都经得起思考</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<main class="main-content">
|
||||
<!-- 持仓概览卡片 -->
|
||||
<section class="overview-card">
|
||||
<div class="card-header">
|
||||
<h2>持仓概览</h2>
|
||||
<span class="total-value">¥128,450.00</span>
|
||||
</div>
|
||||
<div class="portfolio-summary">
|
||||
<div class="summary-item">
|
||||
<span class="label">今日收益</span>
|
||||
<span class="value positive">+¥1,250.00</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="label">总收益率</span>
|
||||
<span class="value positive">+12.5%</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<nav class="bottom-nav">
|
||||
<div class="nav-item active" data-tab="portfolio">
|
||||
<div class="nav-icon">📊</div>
|
||||
<span>持仓</span>
|
||||
</div>
|
||||
<div class="nav-item" data-tab="plans">
|
||||
<div class="nav-icon">📋</div>
|
||||
<span>计划</span>
|
||||
</div>
|
||||
<div class="nav-item" data-tab="records">
|
||||
<div class="nav-icon">📝</div>
|
||||
<span>记录</span>
|
||||
</div>
|
||||
<div class="nav-item" data-tab="tools">
|
||||
<div class="nav-icon">🛠️</div>
|
||||
<span>我的</span>
|
||||
</div>
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<!-- 持仓页面 -->
|
||||
<div class="page" id="portfolio-page">
|
||||
<div class="page-header">
|
||||
<h2>我的持仓</h2>
|
||||
<button class="add-btn">+ 添加</button>
|
||||
</div>
|
||||
|
||||
<div class="holdings-list">
|
||||
<div class="holding-item">
|
||||
<div class="stock-info">
|
||||
<div class="stock-name">贵州茅台</div>
|
||||
<div class="stock-code">600519</div>
|
||||
</div>
|
||||
<div class="holding-details">
|
||||
<div class="shares">100股</div>
|
||||
<div class="current-price">¥1,850.00</div>
|
||||
<div class="profit positive">+¥2,500.00 (+15.6%)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="holding-item">
|
||||
<div class="stock-info">
|
||||
<div class="stock-name">腾讯控股</div>
|
||||
<div class="stock-code">00700</div>
|
||||
</div>
|
||||
<div class="holding-details">
|
||||
<div class="shares">200股</div>
|
||||
<div class="current-price">¥320.00</div>
|
||||
<div class="profit negative">-¥800.00 (-1.2%)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 交易计划页面 -->
|
||||
<div class="page hidden" id="plans-page">
|
||||
<div class="page-header">
|
||||
<h2>交易计划</h2>
|
||||
<button class="add-btn">+ 新建</button>
|
||||
</div>
|
||||
|
||||
<div class="plans-list">
|
||||
<div class="plan-item">
|
||||
<div class="plan-header">
|
||||
<div class="stock-info">
|
||||
<div class="stock-name">招商银行</div>
|
||||
<div class="stock-code">600036</div>
|
||||
</div>
|
||||
<div class="plan-status pending">进行中</div>
|
||||
</div>
|
||||
<div class="plan-details">
|
||||
<div class="plan-row">
|
||||
<span class="label">目标价格:</span>
|
||||
<span class="value">¥45.00</span>
|
||||
</div>
|
||||
<div class="plan-row">
|
||||
<span class="label">计划金额:</span>
|
||||
<span class="value">¥10,000</span>
|
||||
</div>
|
||||
<div class="plan-row">
|
||||
<span class="label">截止时间:</span>
|
||||
<span class="value">2024-03-15</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="plan-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: 60%"></div>
|
||||
</div>
|
||||
<span class="progress-text">已完成 60%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 交易记录页面 -->
|
||||
<div class="page hidden" id="records-page">
|
||||
<div class="page-header">
|
||||
<h2>交易记录</h2>
|
||||
<button class="add-btn">+ 记录</button>
|
||||
</div>
|
||||
|
||||
<div class="timeline">
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-date">2024-01-15</div>
|
||||
<div class="timeline-content">
|
||||
<div class="transaction-card">
|
||||
<div class="transaction-header">
|
||||
<div class="stock-info">
|
||||
<div class="stock-name">贵州茅台</div>
|
||||
<div class="stock-code">600519</div>
|
||||
</div>
|
||||
<div class="transaction-type buy">买入</div>
|
||||
</div>
|
||||
<div class="transaction-details">
|
||||
<div class="detail-row">
|
||||
<span>数量:</span>
|
||||
<span>100股</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span>价格:</span>
|
||||
<span>¥1,600.00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="transaction-thoughts">
|
||||
<h4>交易思考:</h4>
|
||||
<p>基于茅台品牌价值和长期增长潜力,认为当前价格具有投资价值。白酒行业龙头地位稳固,现金流优秀。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 我的页面 -->
|
||||
<div class="page hidden" id="tools-page">
|
||||
<div class="page-header">
|
||||
<h2>我的工具</h2>
|
||||
</div>
|
||||
|
||||
<div class="tools-grid">
|
||||
<div class="tool-card" data-tool="checklist">
|
||||
<div class="tool-icon">✅</div>
|
||||
<div class="tool-name">投资检查清单</div>
|
||||
<div class="tool-desc">买入卖出检查项</div>
|
||||
</div>
|
||||
|
||||
<div class="tool-card" data-tool="calculator">
|
||||
<div class="tool-icon">🧮</div>
|
||||
<div class="tool-name">复利计算器</div>
|
||||
<div class="tool-desc">计算未来收益</div>
|
||||
</div>
|
||||
|
||||
<div class="tool-card" data-tool="valuation">
|
||||
<div class="tool-icon">📈</div>
|
||||
<div class="tool-name">估值工具</div>
|
||||
<div class="tool-desc">企业价值评估</div>
|
||||
</div>
|
||||
|
||||
<div class="tool-card" data-tool="freedom">
|
||||
<div class="tool-icon">🎯</div>
|
||||
<div class="tool-name">自由目标</div>
|
||||
<div class="tool-desc">财务自由规划</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模态框 -->
|
||||
<div class="modal" id="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 id="modal-title">标题</h3>
|
||||
<button class="close-btn" id="close-modal">×</button>
|
||||
</div>
|
||||
<div class="modal-body" id="modal-body">
|
||||
<!-- 动态内容 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
851
packages/design-ui/script.js
Normal file
851
packages/design-ui/script.js
Normal file
@@ -0,0 +1,851 @@
|
||||
// 思投录 - 投资决策与复盘工具
|
||||
class VestMindApp {
|
||||
constructor() {
|
||||
this.currentTab = 'portfolio';
|
||||
this.modal = document.getElementById('modal');
|
||||
this.modalTitle = document.getElementById('modal-title');
|
||||
this.modalBody = document.getElementById('modal-body');
|
||||
this.closeModalBtn = document.getElementById('close-modal');
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.bindEvents();
|
||||
this.showPage('portfolio');
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
// 底部导航栏事件
|
||||
document.querySelectorAll('.nav-item').forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
const tab = e.currentTarget.dataset.tab;
|
||||
this.switchTab(tab);
|
||||
});
|
||||
});
|
||||
|
||||
// 模态框关闭事件
|
||||
this.closeModalBtn.addEventListener('click', () => {
|
||||
this.hideModal();
|
||||
});
|
||||
|
||||
this.modal.addEventListener('click', (e) => {
|
||||
if (e.target === this.modal) {
|
||||
this.hideModal();
|
||||
}
|
||||
});
|
||||
|
||||
// 工具卡片点击事件
|
||||
document.querySelectorAll('.tool-card').forEach(card => {
|
||||
card.addEventListener('click', (e) => {
|
||||
const tool = e.currentTarget.dataset.tool;
|
||||
this.openTool(tool);
|
||||
});
|
||||
});
|
||||
|
||||
// 添加按钮事件
|
||||
document.querySelectorAll('.add-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const page = e.currentTarget.closest('.page').id;
|
||||
this.showAddModal(page);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
switchTab(tab) {
|
||||
// 更新导航栏状态
|
||||
document.querySelectorAll('.nav-item').forEach(item => {
|
||||
item.classList.remove('active');
|
||||
});
|
||||
document.querySelector(`[data-tab="${tab}"]`).classList.add('active');
|
||||
|
||||
// 显示对应页面
|
||||
this.showPage(tab);
|
||||
this.currentTab = tab;
|
||||
}
|
||||
|
||||
showPage(pageId) {
|
||||
// 隐藏所有页面
|
||||
document.querySelectorAll('.page').forEach(page => {
|
||||
page.classList.add('hidden');
|
||||
});
|
||||
|
||||
// 显示目标页面
|
||||
const targetPage = document.getElementById(`${pageId}-page`);
|
||||
if (targetPage) {
|
||||
targetPage.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
loadData() {
|
||||
// 模拟数据加载
|
||||
this.loadPortfolioData();
|
||||
this.loadPlansData();
|
||||
this.loadRecordsData();
|
||||
}
|
||||
|
||||
loadPortfolioData() {
|
||||
// 模拟持仓数据
|
||||
const holdings = [
|
||||
{
|
||||
name: '贵州茅台',
|
||||
code: '600519',
|
||||
shares: 100,
|
||||
currentPrice: 1850.00,
|
||||
profit: 2500.00,
|
||||
profitRate: 15.6
|
||||
},
|
||||
{
|
||||
name: '腾讯控股',
|
||||
code: '00700',
|
||||
shares: 200,
|
||||
currentPrice: 320.00,
|
||||
profit: -800.00,
|
||||
profitRate: -1.2
|
||||
},
|
||||
{
|
||||
name: '招商银行',
|
||||
code: '600036',
|
||||
shares: 500,
|
||||
currentPrice: 42.50,
|
||||
profit: 1250.00,
|
||||
profitRate: 6.2
|
||||
}
|
||||
];
|
||||
|
||||
this.renderHoldings(holdings);
|
||||
}
|
||||
|
||||
renderHoldings(holdings) {
|
||||
const container = document.querySelector('.holdings-list');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = holdings.map(holding => `
|
||||
<div class="holding-item">
|
||||
<div class="stock-info">
|
||||
<div class="stock-name">${holding.name}</div>
|
||||
<div class="stock-code">${holding.code}</div>
|
||||
</div>
|
||||
<div class="holding-details">
|
||||
<div class="shares">${holding.shares}股</div>
|
||||
<div class="current-price">¥${holding.currentPrice.toFixed(2)}</div>
|
||||
<div class="profit ${holding.profit >= 0 ? 'positive' : 'negative'}">
|
||||
${holding.profit >= 0 ? '+' : ''}¥${holding.profit.toFixed(2)} (${holding.profitRate >= 0 ? '+' : ''}${holding.profitRate}%)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
loadPlansData() {
|
||||
// 模拟交易计划数据
|
||||
const plans = [
|
||||
{
|
||||
name: '招商银行',
|
||||
code: '600036',
|
||||
targetPrice: 45.00,
|
||||
amount: 10000,
|
||||
deadline: '2024-03-15',
|
||||
progress: 60,
|
||||
status: 'pending'
|
||||
},
|
||||
{
|
||||
name: '中国平安',
|
||||
code: '601318',
|
||||
targetPrice: 55.00,
|
||||
amount: 15000,
|
||||
deadline: '2024-04-20',
|
||||
progress: 30,
|
||||
status: 'pending'
|
||||
}
|
||||
];
|
||||
|
||||
this.renderPlans(plans);
|
||||
}
|
||||
|
||||
renderPlans(plans) {
|
||||
const container = document.querySelector('.plans-list');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = plans.map(plan => `
|
||||
<div class="plan-item">
|
||||
<div class="plan-header">
|
||||
<div class="stock-info">
|
||||
<div class="stock-name">${plan.name}</div>
|
||||
<div class="stock-code">${plan.code}</div>
|
||||
</div>
|
||||
<div class="plan-status ${plan.status}">进行中</div>
|
||||
</div>
|
||||
<div class="plan-details">
|
||||
<div class="plan-row">
|
||||
<span class="label">目标价格:</span>
|
||||
<span class="value">¥${plan.targetPrice.toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="plan-row">
|
||||
<span class="label">计划金额:</span>
|
||||
<span class="value">¥${plan.amount.toLocaleString()}</span>
|
||||
</div>
|
||||
<div class="plan-row">
|
||||
<span class="label">截止时间:</span>
|
||||
<span class="value">${plan.deadline}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="plan-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: ${plan.progress}%"></div>
|
||||
</div>
|
||||
<span class="progress-text">已完成 ${plan.progress}%</span>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
loadRecordsData() {
|
||||
// 模拟交易记录数据
|
||||
const records = [
|
||||
{
|
||||
date: '2024-01-15',
|
||||
type: 'buy',
|
||||
name: '贵州茅台',
|
||||
code: '600519',
|
||||
shares: 100,
|
||||
price: 1600.00,
|
||||
thoughts: '基于茅台品牌价值和长期增长潜力,认为当前价格具有投资价值。白酒行业龙头地位稳固,现金流优秀。'
|
||||
},
|
||||
{
|
||||
date: '2024-01-10',
|
||||
type: 'sell',
|
||||
name: '比亚迪',
|
||||
code: '002594',
|
||||
shares: 200,
|
||||
price: 280.00,
|
||||
thoughts: '新能源汽车行业竞争加剧,估值偏高,选择获利了结。'
|
||||
}
|
||||
];
|
||||
|
||||
this.renderRecords(records);
|
||||
}
|
||||
|
||||
renderRecords(records) {
|
||||
const container = document.querySelector('.timeline');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = records.map(record => `
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-date">${record.date}</div>
|
||||
<div class="timeline-content">
|
||||
<div class="transaction-card">
|
||||
<div class="transaction-header">
|
||||
<div class="stock-info">
|
||||
<div class="stock-name">${record.name}</div>
|
||||
<div class="stock-code">${record.code}</div>
|
||||
</div>
|
||||
<div class="transaction-type ${record.type}">${record.type === 'buy' ? '买入' : '卖出'}</div>
|
||||
</div>
|
||||
<div class="transaction-details">
|
||||
<div class="detail-row">
|
||||
<span>数量:</span>
|
||||
<span>${record.shares}股</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span>价格:</span>
|
||||
<span>¥${record.price.toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="transaction-thoughts">
|
||||
<h4>交易思考:</h4>
|
||||
<p>${record.thoughts}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
openTool(tool) {
|
||||
switch (tool) {
|
||||
case 'checklist':
|
||||
this.showChecklistModal();
|
||||
break;
|
||||
case 'calculator':
|
||||
this.showCalculatorModal();
|
||||
break;
|
||||
case 'valuation':
|
||||
this.showValuationModal();
|
||||
break;
|
||||
case 'freedom':
|
||||
this.showFreedomModal();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
showChecklistModal() {
|
||||
this.modalTitle.textContent = '投资检查清单';
|
||||
this.modalBody.innerHTML = `
|
||||
<div class="checklist-container">
|
||||
<div class="checklist-section">
|
||||
<h4>买入检查清单</h4>
|
||||
<div class="checklist-items">
|
||||
<label class="checklist-item">
|
||||
<input type="checkbox">
|
||||
<span>企业基本面是否优秀?</span>
|
||||
</label>
|
||||
<label class="checklist-item">
|
||||
<input type="checkbox">
|
||||
<span>估值是否合理?</span>
|
||||
</label>
|
||||
<label class="checklist-item">
|
||||
<input type="checkbox">
|
||||
<span>行业前景如何?</span>
|
||||
</label>
|
||||
<label class="checklist-item">
|
||||
<input type="checkbox">
|
||||
<span>管理层是否可信?</span>
|
||||
</label>
|
||||
<label class="checklist-item">
|
||||
<input type="checkbox">
|
||||
<span>现金流是否健康?</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="checklist-section">
|
||||
<h4>卖出检查清单</h4>
|
||||
<div class="checklist-items">
|
||||
<label class="checklist-item">
|
||||
<input type="checkbox">
|
||||
<span>基本面是否恶化?</span>
|
||||
</label>
|
||||
<label class="checklist-item">
|
||||
<input type="checkbox">
|
||||
<span>估值是否过高?</span>
|
||||
</label>
|
||||
<label class="checklist-item">
|
||||
<input type="checkbox">
|
||||
<span>是否有更好的投资机会?</span>
|
||||
</label>
|
||||
<label class="checklist-item">
|
||||
<input type="checkbox">
|
||||
<span>是否需要资金配置?</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
this.showModal();
|
||||
}
|
||||
|
||||
showCalculatorModal() {
|
||||
this.modalTitle.textContent = '复利计算器';
|
||||
this.modalBody.innerHTML = `
|
||||
<div class="calculator-container">
|
||||
<div class="input-group">
|
||||
<label>初始金额(元)</label>
|
||||
<input type="number" id="initial-amount" placeholder="100000" value="100000">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>每年投入(元)</label>
|
||||
<input type="number" id="annual-investment" placeholder="50000" value="50000">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>年复合增长率(%)</label>
|
||||
<input type="number" id="growth-rate" placeholder="10" value="10" step="0.1">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>投资年限</label>
|
||||
<input type="number" id="years" placeholder="10" value="10">
|
||||
</div>
|
||||
<button class="calculate-btn" onclick="app.calculateCompound()">计算</button>
|
||||
<div class="result-container" id="calculator-result" style="display: none;">
|
||||
<h4>计算结果</h4>
|
||||
<div class="result-item">
|
||||
<span>总投入:</span>
|
||||
<span id="total-investment">¥0</span>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<span>最终金额:</span>
|
||||
<span id="final-amount">¥0</span>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<span>总收益:</span>
|
||||
<span id="total-profit">¥0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
this.showModal();
|
||||
}
|
||||
|
||||
calculateCompound() {
|
||||
const initialAmount = parseFloat(document.getElementById('initial-amount').value) || 0;
|
||||
const annualInvestment = parseFloat(document.getElementById('annual-investment').value) || 0;
|
||||
const growthRate = parseFloat(document.getElementById('growth-rate').value) || 0;
|
||||
const years = parseInt(document.getElementById('years').value) || 0;
|
||||
|
||||
const rate = growthRate / 100;
|
||||
let totalInvestment = initialAmount + annualInvestment * years;
|
||||
let finalAmount = initialAmount * Math.pow(1 + rate, years);
|
||||
|
||||
// 计算每年投入的复利
|
||||
for (let i = 1; i <= years; i++) {
|
||||
finalAmount += annualInvestment * Math.pow(1 + rate, years - i);
|
||||
}
|
||||
|
||||
const totalProfit = finalAmount - totalInvestment;
|
||||
|
||||
document.getElementById('total-investment').textContent = `¥${totalInvestment.toLocaleString()}`;
|
||||
document.getElementById('final-amount').textContent = `¥${finalAmount.toLocaleString()}`;
|
||||
document.getElementById('total-profit').textContent = `¥${totalProfit.toLocaleString()}`;
|
||||
document.getElementById('calculator-result').style.display = 'block';
|
||||
}
|
||||
|
||||
showValuationModal() {
|
||||
this.modalTitle.textContent = '估值工具';
|
||||
this.modalBody.innerHTML = `
|
||||
<div class="valuation-container">
|
||||
<div class="valuation-tabs">
|
||||
<button class="tab-btn active" data-method="tang">老唐估值法</button>
|
||||
<button class="tab-btn" data-method="dcf">现金流折现</button>
|
||||
</div>
|
||||
<div class="valuation-content">
|
||||
<div class="method-content" id="tang-method">
|
||||
<div class="input-group">
|
||||
<label>净利润(亿元)</label>
|
||||
<input type="number" id="net-profit" placeholder="100" value="100">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>无风险收益率(%)</label>
|
||||
<input type="number" id="risk-free-rate" placeholder="3" value="3" step="0.1">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>合理PE倍数</label>
|
||||
<input type="number" id="pe-ratio" placeholder="25" value="25">
|
||||
</div>
|
||||
<button class="calculate-btn" onclick="app.calculateTangValuation()">计算估值</button>
|
||||
<div class="result-container" id="tang-result" style="display: none;">
|
||||
<h4>估值结果</h4>
|
||||
<div class="result-item">
|
||||
<span>合理估值:</span>
|
||||
<span id="tang-value">¥0亿</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
this.showModal();
|
||||
}
|
||||
|
||||
calculateTangValuation() {
|
||||
const netProfit = parseFloat(document.getElementById('net-profit').value) || 0;
|
||||
const riskFreeRate = parseFloat(document.getElementById('risk-free-rate').value) || 0;
|
||||
const peRatio = parseFloat(document.getElementById('pe-ratio').value) || 0;
|
||||
|
||||
const reasonablePE = 1 / (riskFreeRate / 100);
|
||||
const valuation = netProfit * Math.min(peRatio, reasonablePE);
|
||||
|
||||
document.getElementById('tang-value').textContent = `¥${valuation.toFixed(2)}亿`;
|
||||
document.getElementById('tang-result').style.display = 'block';
|
||||
}
|
||||
|
||||
showFreedomModal() {
|
||||
this.modalTitle.textContent = '自由目标';
|
||||
this.modalBody.innerHTML = `
|
||||
<div class="freedom-container">
|
||||
<div class="input-group">
|
||||
<label>目标资产(万元)</label>
|
||||
<input type="number" id="target-assets" placeholder="1000" value="1000">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>当前资产(万元)</label>
|
||||
<input type="number" id="current-assets" placeholder="100" value="100">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>年复合增长率(%)</label>
|
||||
<input type="number" id="freedom-growth-rate" placeholder="10" value="10" step="0.1">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>每年投入(万元)</label>
|
||||
<input type="number" id="freedom-annual-investment" placeholder="20" value="20">
|
||||
</div>
|
||||
<button class="calculate-btn" onclick="app.calculateFreedom()">计算达成时间</button>
|
||||
<div class="result-container" id="freedom-result" style="display: none;">
|
||||
<h4>自由目标分析</h4>
|
||||
<div class="result-item">
|
||||
<span>预计达成时间:</span>
|
||||
<span id="freedom-years">0年</span>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<span>届时年龄:</span>
|
||||
<span id="freedom-age">0岁</span>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<span>总投入:</span>
|
||||
<span id="freedom-total-investment">¥0万</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
this.showModal();
|
||||
}
|
||||
|
||||
calculateFreedom() {
|
||||
const targetAssets = parseFloat(document.getElementById('target-assets').value) || 0;
|
||||
const currentAssets = parseFloat(document.getElementById('current-assets').value) || 0;
|
||||
const growthRate = parseFloat(document.getElementById('freedom-growth-rate').value) || 0;
|
||||
const annualInvestment = parseFloat(document.getElementById('freedom-annual-investment').value) || 0;
|
||||
|
||||
const rate = growthRate / 100;
|
||||
let years = 0;
|
||||
let assets = currentAssets;
|
||||
|
||||
// 模拟逐年增长
|
||||
while (assets < targetAssets && years < 50) {
|
||||
assets = assets * (1 + rate) + annualInvestment;
|
||||
years++;
|
||||
}
|
||||
|
||||
const totalInvestment = currentAssets + annualInvestment * years;
|
||||
const currentAge = 30; // 假设当前年龄
|
||||
const targetAge = currentAge + years;
|
||||
|
||||
document.getElementById('freedom-years').textContent = `${years}年`;
|
||||
document.getElementById('freedom-age').textContent = `${targetAge}岁`;
|
||||
document.getElementById('freedom-total-investment').textContent = `¥${totalInvestment.toFixed(2)}万`;
|
||||
document.getElementById('freedom-result').style.display = 'block';
|
||||
}
|
||||
|
||||
showAddModal(page) {
|
||||
switch (page) {
|
||||
case 'portfolio-page':
|
||||
this.showAddHoldingModal();
|
||||
break;
|
||||
case 'plans-page':
|
||||
this.showAddPlanModal();
|
||||
break;
|
||||
case 'records-page':
|
||||
this.showAddRecordModal();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
showAddHoldingModal() {
|
||||
this.modalTitle.textContent = '添加持仓';
|
||||
this.modalBody.innerHTML = `
|
||||
<div class="form-container">
|
||||
<div class="input-group">
|
||||
<label>股票名称</label>
|
||||
<input type="text" id="stock-name" placeholder="请输入股票名称">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>股票代码</label>
|
||||
<input type="text" id="stock-code" placeholder="请输入股票代码">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>持股数量</label>
|
||||
<input type="number" id="shares" placeholder="请输入持股数量">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>成本价格</label>
|
||||
<input type="number" id="cost-price" placeholder="请输入成本价格" step="0.01">
|
||||
</div>
|
||||
<button class="submit-btn" onclick="app.addHolding()">添加持仓</button>
|
||||
</div>
|
||||
`;
|
||||
this.showModal();
|
||||
}
|
||||
|
||||
showAddPlanModal() {
|
||||
this.modalTitle.textContent = '新建交易计划';
|
||||
this.modalBody.innerHTML = `
|
||||
<div class="form-container">
|
||||
<div class="input-group">
|
||||
<label>股票名称</label>
|
||||
<input type="text" id="plan-stock-name" placeholder="请输入股票名称">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>股票代码</label>
|
||||
<input type="text" id="plan-stock-code" placeholder="请输入股票代码">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>目标价格</label>
|
||||
<input type="number" id="target-price" placeholder="请输入目标价格" step="0.01">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>计划金额</label>
|
||||
<input type="number" id="plan-amount" placeholder="请输入计划金额">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>截止时间</label>
|
||||
<input type="date" id="deadline">
|
||||
</div>
|
||||
<button class="submit-btn" onclick="app.addPlan()">创建计划</button>
|
||||
</div>
|
||||
`;
|
||||
this.showModal();
|
||||
}
|
||||
|
||||
showAddRecordModal() {
|
||||
this.modalTitle.textContent = '记录交易';
|
||||
this.modalBody.innerHTML = `
|
||||
<div class="form-container">
|
||||
<div class="input-group">
|
||||
<label>交易类型</label>
|
||||
<select id="transaction-type">
|
||||
<option value="buy">买入</option>
|
||||
<option value="sell">卖出</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>股票名称</label>
|
||||
<input type="text" id="record-stock-name" placeholder="请输入股票名称">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>股票代码</label>
|
||||
<input type="text" id="record-stock-code" placeholder="请输入股票代码">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>交易数量</label>
|
||||
<input type="number" id="record-shares" placeholder="请输入交易数量">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>交易价格</label>
|
||||
<input type="number" id="record-price" placeholder="请输入交易价格" step="0.01">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>交易思考</label>
|
||||
<textarea id="transaction-thoughts" placeholder="请记录您的交易思考..." rows="4"></textarea>
|
||||
</div>
|
||||
<button class="submit-btn" onclick="app.addRecord()">记录交易</button>
|
||||
</div>
|
||||
`;
|
||||
this.showModal();
|
||||
}
|
||||
|
||||
addHolding() {
|
||||
// 模拟添加持仓
|
||||
this.showToast('持仓添加成功!');
|
||||
this.hideModal();
|
||||
}
|
||||
|
||||
addPlan() {
|
||||
// 模拟添加计划
|
||||
this.showToast('交易计划创建成功!');
|
||||
this.hideModal();
|
||||
}
|
||||
|
||||
addRecord() {
|
||||
// 模拟添加记录
|
||||
this.showToast('交易记录添加成功!');
|
||||
this.hideModal();
|
||||
}
|
||||
|
||||
showModal() {
|
||||
this.modal.classList.add('show');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
hideModal() {
|
||||
this.modal.classList.remove('show');
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
|
||||
showToast(message) {
|
||||
// 创建提示框
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'toast';
|
||||
toast.textContent = message;
|
||||
toast.style.cssText = `
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
z-index: 10000;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
`;
|
||||
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// 显示动画
|
||||
setTimeout(() => {
|
||||
toast.style.opacity = '1';
|
||||
}, 100);
|
||||
|
||||
// 自动隐藏
|
||||
setTimeout(() => {
|
||||
toast.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(toast);
|
||||
}, 300);
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化应用
|
||||
const app = new VestMindApp();
|
||||
|
||||
// 添加一些额外的样式
|
||||
const additionalStyles = `
|
||||
<style>
|
||||
.checklist-container {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.checklist-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.checklist-section h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.checklist-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.checklist-item input[type="checkbox"] {
|
||||
margin-right: 12px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: #8b5cf6;
|
||||
}
|
||||
|
||||
.calculator-container,
|
||||
.valuation-container,
|
||||
.freedom-container,
|
||||
.form-container {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.input-group input,
|
||||
.input-group select,
|
||||
.input-group textarea {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.input-group input:focus,
|
||||
.input-group select:focus,
|
||||
.input-group textarea:focus {
|
||||
outline: none;
|
||||
border-color: #8b5cf6;
|
||||
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1);
|
||||
}
|
||||
|
||||
.calculate-btn,
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.calculate-btn:hover,
|
||||
.submit-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
|
||||
}
|
||||
|
||||
.result-container {
|
||||
margin-top: 20px;
|
||||
padding: 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border-left: 3px solid #8b5cf6;
|
||||
}
|
||||
|
||||
.result-container h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.result-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.result-item span:first-child {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.result-item span:last-child {
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.valuation-tabs {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
color: #8b5cf6;
|
||||
border-bottom-color: #8b5cf6;
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
color: #8b5cf6;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
document.head.insertAdjacentHTML('beforeend', additionalStyles);
|
||||
1630
packages/design-ui/src/investment-record-v2.html
Normal file
1630
packages/design-ui/src/investment-record-v2.html
Normal file
File diff suppressed because it is too large
Load Diff
1308
packages/design-ui/src/investment-record.html
Normal file
1308
packages/design-ui/src/investment-record.html
Normal file
File diff suppressed because it is too large
Load Diff
1289
packages/design-ui/src/pc-web-ui.html
Normal file
1289
packages/design-ui/src/pc-web-ui.html
Normal file
File diff suppressed because it is too large
Load Diff
702
packages/design-ui/styles.css
Normal file
702
packages/design-ui/styles.css
Normal file
@@ -0,0 +1,702 @@
|
||||
/* 基础样式重置 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
color: #333;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 主容器 */
|
||||
.app-container {
|
||||
max-width: 414px;
|
||||
margin: 0 auto;
|
||||
min-height: 100vh;
|
||||
background: #f8f9fa;
|
||||
position: relative;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.header {
|
||||
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
50% { transform: translate(-50%, -50%) rotate(180deg); }
|
||||
}
|
||||
|
||||
.header-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 4px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.app-subtitle {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
/* 主要内容区域 */
|
||||
.main-content {
|
||||
padding: 20px;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
/* 概览卡片 */
|
||||
.overview-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 20px rgba(139, 92, 246, 0.1);
|
||||
border: 1px solid rgba(139, 92, 246, 0.1);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.card-header h2 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.total-value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #8b5cf6;
|
||||
}
|
||||
|
||||
.portfolio-summary {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.summary-item .label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.summary-item .value {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.summary-item .value.positive {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.summary-item .value.negative {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
/* 底部导航栏 */
|
||||
.bottom-nav {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
max-width: 414px;
|
||||
background: white;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
display: flex;
|
||||
padding: 8px 0;
|
||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 8px 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 8px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
color: #8b5cf6;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.nav-item span {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 页面样式 */
|
||||
.page {
|
||||
padding: 20px;
|
||||
padding-bottom: 100px;
|
||||
min-height: calc(100vh - 120px);
|
||||
}
|
||||
|
||||
.page.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.add-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
|
||||
}
|
||||
|
||||
/* 持仓列表 */
|
||||
.holdings-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.holding-item {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid #f3f4f6;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.holding-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stock-info {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stock-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.stock-code {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.holding-details {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.shares {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.current-price {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.profit {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.profit.positive {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.profit.negative {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
/* 计划列表 */
|
||||
.plans-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.plan-item {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid #f3f4f6;
|
||||
}
|
||||
|
||||
.plan-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.plan-status {
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.plan-status.pending {
|
||||
background: rgba(251, 191, 36, 0.1);
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.plan-status.completed {
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.plan-details {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.plan-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.plan-row .label {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.plan-row .value {
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.plan-progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background: #e5e7eb;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #8b5cf6 0%, #7c3aed 100%);
|
||||
border-radius: 3px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 时间线 */
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.timeline::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
background: linear-gradient(180deg, #8b5cf6 0%, #7c3aed 100%);
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.timeline-item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -16px;
|
||||
top: 8px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #8b5cf6;
|
||||
border-radius: 50%;
|
||||
border: 3px solid white;
|
||||
box-shadow: 0 0 0 2px #8b5cf6;
|
||||
}
|
||||
|
||||
.timeline-date {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid #f3f4f6;
|
||||
}
|
||||
|
||||
.transaction-card {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.transaction-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.transaction-type {
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.transaction-type.buy {
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.transaction-type.sell {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.transaction-details {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.transaction-thoughts {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
border-left: 3px solid #8b5cf6;
|
||||
}
|
||||
|
||||
.transaction-thoughts h4 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.transaction-thoughts p {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 工具网格 */
|
||||
.tools-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.tool-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid #f3f4f6;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tool-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 24px rgba(139, 92, 246, 0.15);
|
||||
border-color: #8b5cf6;
|
||||
}
|
||||
|
||||
.tool-icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.tool-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.tool-desc {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
/* 模态框 */
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.modal.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
transform: scale(0.9);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.modal.show .modal-content {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: #f3f4f6;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 375px) {
|
||||
.app-container {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.tools-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 小程序适配 */
|
||||
@media (max-width: 320px) {
|
||||
.app-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.nav-item span {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes slideInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.holding-item,
|
||||
.plan-item,
|
||||
.timeline-content,
|
||||
.tool-card {
|
||||
animation: slideInUp 0.3s ease forwards;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: '';
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid #e5e7eb;
|
||||
border-top: 2px solid #8b5cf6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.empty-state-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-state-text {
|
||||
font-size: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.empty-state-desc {
|
||||
font-size: 14px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
Reference in New Issue
Block a user