feat: 添加设计文件packages

This commit is contained in:
R524809
2026-02-11 16:01:42 +08:00
parent 161781cbbd
commit 571465cfbb
26 changed files with 15808 additions and 5 deletions

33
packages/design-ui/PRD.md Normal file
View File

@@ -0,0 +1,33 @@
## 核心思想
名称选择:思投录
愿景:让每笔投资都经得起思考
副标题:**投资决策与复盘工具**。
### 核心功能
#### 持仓
记录自己的股票持仓,统计各个股票持仓占比,按基金收益法统计的持仓收益。可以基于用户设置的单只股票仓位上限进行预警。
#### 交易计划
- “计划你的交易,交易你的计划”,让用户可以创建交易计划,选择股票、市场、目标价格、截止时间、投资金额或股份数(两者选一个即可)等。
- 之后到目标价后可以提醒用户。
- 用户可以为计划设置步骤,默认分三步进行买入等,可以分步设置买入价格。
#### 交易记录/复盘
- 引导用户记录每一笔交易,可以是从计划中点完成计划等操作跳转到记录页中,也可以是用户主动记录。用户可以记录每一笔交易的企业名称、买卖份数、买卖单价,最重要的是要引导用户写下买卖思考。
- 定期弹出页面应到用户写下复盘和思考。
- 用户可以参看每一笔交易的附带思考和复盘记录的时间线,让用户可以在交易中学习和成长,类似 QQ 空间的说说功能。
- 用户可以分享自己的交易时间线(不确定是否需要)。
#### 我的
不确定是否应该把 我的 作为一个 Tabbar。
- 展示一些我的信息。
- 最主要的是一些工具入口,例如 投资检查清单、复利计算器、估值工具(可以分老唐估值法和两段式现金流折现估值法)、自由目标等。
- 投资检查清单:可以分为买入和卖出两份检查清单,在每次设置交易计划时最后弹出,让用户每项检查。
- 复利计算器:侧重于投资者角度,可以填入初始金额,每年可投入的金额,调整预计的年复合增长率,然后查看未来的总收益。
- 自由目标:“让自己有的选”,自由目标可以和复利计算器和持仓结合起来,让用户自己设定一个总资产达成目标。把这个目标和财务自由、赎回自己的时间关联起来,让用户有“奔头”。
## App名称
中文名:思投录
英文名VestMind
“Vest”投资记录与管理和“Mind”思考与决策清单。用户能直观感受到这是一个与“投资”和“思考”相关的工具。
Mind”强调了投资不应是冲动行为而应是经过深思熟虑的、理性的“思维活动”这与你“让每笔投资都经得起思考”的愿景高度契合。
这个词组合起来听起来专业、简洁,且带有一种“智慧投资”的质感,能吸引那些希望提升自己投资决策质量的用户。
## 主题色
主题色需要偏紫色调,注重让人安静思考的色调。

View 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文档要求实现了思投录应用的核心功能界面。设计风格现代简洁交互流畅自然完全适配移动端和小程序使用场景。紫色主题营造了安静思考的投资氛围符合"让每笔投资都经得起思考"的产品愿景。

View 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">&times;</button>
</div>
<div class="modal-body" id="modal-body">
<!-- 动态内容 -->
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>

View 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);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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;
}