Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions apps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,25 @@ A product promotional video creation tool that combines the power of Remotion (p

---

### 6. StockVisualization — For Stock Traders

> **Tagline**: *Visualize stock market trends*

An AI-powered stock visualization tool built on RDS Supabase, featuring interactive charts and a paper trading simulator to track real-time portfolio performance.

**Key Features**:
- Data-Driven Stock Visualization
- Interactive Portfolio Simulator
- Real-time Portfolio Performance
- Supabase-Powered Data Backend

**Use Cases**:
- Visualize stock market trends
- Track portfolio performance in real-time
- Check stock analysis reports

**Tech Stack**: Next.js, RDS Supabase, Build-in Authentication

## Getting Started

### Prerequisites
Expand Down Expand Up @@ -207,6 +226,7 @@ We welcome contributions! Each app has its own development guidelines in its res
- [ ] **CoWork** v1.0 - Email assistant
- [ ] **NoteGenius** v1.0 - Blog writing mode
- [ ] **PromoStudio** v1.0 - Basic video generation
- [ ] **StockVisualization** v1.0 - Visualize stock market trends

## License

Expand Down
3 changes: 3 additions & 0 deletions apps/qwen-stock-visualization/.env.local.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Supabase Environment Variables
NEXT_PUBLIC_SUPABASE_URL=""
NEXT_PUBLIC_SUPABASE_ANON_KEY=""
3 changes: 3 additions & 0 deletions apps/qwen-stock-visualization/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extends: ['next/core-web-vitals'],
}
3 changes: 3 additions & 0 deletions apps/qwen-stock-visualization/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
123 changes: 123 additions & 0 deletions apps/qwen-stock-visualization/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# 股票数据实时可视化系统

这是一个直观、交互性强且个性化的股票数据可视化与模拟投资平台。

## 功能特性

- 用户身份认证(注册、登录)
- 个性化股票仪表盘
- 实时股票价格可视化
- 自选股管理
- 模拟交易功能
- 投资组合管理
- 收益追踪与分析

## 技术栈

- Next.js 14+
- React
- Supabase (PostgreSQL数据库、身份验证)
- Tailwind CSS
- Recharts (数据可视化)

## 快速开始

### 1. 环境配置

首先,您需要在 Supabase 上创建一个项目并获取以下凭据:

- `NEXT_PUBLIC_SUPABASE_URL` - 你的 Supabase 项目 URL
- `NEXT_PUBLIC_SUPABASE_ANON_KEY` - 你的 Supabase 匿名密钥

### 2. 数据库设置

**重要提示:** 你需要手动创建数据库表结构。本项目提供了数据库迁移文件,位于 `supabase/migrations/000001_init_simple_schema.sql`。

你可以通过以下方式之一来创建数据库表:

**选项 A:使用 Supabase CLI**
```bash
# 安装 Supabase CLI 后执行
supabase db push
```

**选项 B:手动执行 SQL**
1. 登录到你的 Supabase 项目控制台
2. 转到 SQL 编辑器
3. 复制并执行 `supabase/migrations/000001_init_simple_schema.sql` 文件中的所有 SQL 语句

### 3. 安装依赖

```bash
npm install
```

### 3. 环境变量配置

复制 `.env.local.example` 文件并填入您的 Supabase 凭据:

```bash
cp .env.local.example .env.local
# 编辑 .env.local 文件并添加您的 Supabase 凭据
```

### 4. 运行开发服务器

```bash
npm run dev
```

应用将在 http://localhost:3000 上启动。

### 5. 填充模拟数据(可选)

如果您想用模拟数据测试应用,请运行:

```bash
npm run populate-mock-data
```

注意:运行此命令前,请确保您已在 `.env.local` 中设置了正确的 Supabase 凭据。

## 项目结构

```
supabase-demo-stock/
├── app/ # Next.js 14+ App Router 页面
├── components/ # React 组件
├── lib/ # 工具函数和数据访问层
├── scripts/ # 实用脚本
├── supabase/ # Supabase 配置和迁移
└── ...
```

## 数据库模式

系统包含以下核心表:

- `stocks` - 股票基础信息
- `stock_prices` - 股票实时价格
- `profiles` - 用户资料
- `watchlists` - 自选股列表
- `portfolios` - 模拟投资组合
- `portfolio_positions` - 持仓信息
- `portfolio_transactions` - 交易记录

## 安全特性

- 使用 Supabase 行级安全 (RLS) 保护用户数据
- 用户只能访问自己的数据
- 所有数据库操作都经过适当验证

## 部署

要构建生产版本:

```bash
npm run build
npm start
```

或者使用 Vercel 一键部署:

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/your-repo/stock-visualization-demo)
69 changes: 69 additions & 0 deletions apps/qwen-stock-visualization/app/about/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import Link from 'next/link';

export default function AboutPage() {
return (
<div className="container py-5">
<div className="row">
<div className="col-12">
<h1 className="display-4 mb-4">关于我们</h1>
<p className="lead">
股票数据实时可视化系统是一个直观、交互性强且个性化的股票数据可视化与模拟投资平台。
</p>
<p>
我们的平台提供以下功能:
</p>
<ul className="list-group list-group-flush">
<li className="list-group-item d-flex align-items-start">
<div className="flex-shrink-0 me-3">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" className="bi bi-graph-up text-success" viewBox="0 0 16 16">
<path fillRule="evenodd" d="M0 0h1v15h15v1H0V0Zm14.817 3.113a.5.5 0 0 1 .07.704l-4.5 5.5a.5.5 0 0 1-.74.037L7.06 6.767l-3.656 5.027a.5.5 0 0 1-.808-.588l4-5.5a.5.5 0 0 1 .758-.06l2.609 2.61 4.15-5.073a.5.5 0 0 1 .704-.07Z"/>
</svg>
</div>
<div className="flex-grow-1">
<h5 className="mt-0">实时股票数据</h5>
<p className="mb-0">获取最新的股票价格和市场数据</p>
</div>
</li>
<li className="list-group-item d-flex align-items-start">
<div className="flex-shrink-0 me-3">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" className="bi bi-bar-chart-line text-info" viewBox="0 0 16 16">
<path d="M11 2a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v12h.5a.5.5 0 0 1 0 1H.5a.5.5 0 0 1 0-1H1v-3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3h1V7a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7h1V2zM6 4v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4h2zm2 5V4h2v5a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1z"/>
</svg>
</div>
<div className="flex-grow-1">
<h5 className="mt-0">交互式图表</h5>
<p className="mb-0">通过交互式图表分析股票趋势</p>
</div>
</li>
<li className="list-group-item d-flex align-items-start">
<div className="flex-shrink-0 me-3">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" className="bi bi-wallet2 text-warning" viewBox="0 0 16 16">
<path d="M12.136.326A1.5 1.5 0 0 1 14 1.78V3h.5A1.5 1.5 0 0 1 16 4.5v9a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 0 13.5v-9a1.5 1.5 0 0 1 1.432-1.499L12.136.326zM5.562 3H13V1.78a.5.5 0 0 0-.621-.484L5.562 3zM1.5 4a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h13a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-13z"/>
</svg>
</div>
<div className="flex-grow-1">
<h5 className="mt-0">投资组合管理</h5>
<p className="mb-0">创建和管理多个投资组合</p>
</div>
</li>
<li className="list-group-item d-flex align-items-start">
<div className="flex-shrink-0 me-3">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" className="bi bi-star text-primary" viewBox="0 0 16 16">
<path d="M2.866 14.85c-.078.444.36.791.746.593l4.39-2.256 4.39 2.256c.386.198.824-.149.746-.592l-.83-4.73 3.522-3.356c.33-.314.16-.888-.282-.95l-4.898-.696L8.465.792a.5.5 0 0 0-.89.029l-4.898.696-3.522 3.356c-.442.062-.612.636-.283.95l.83 4.73zM8 12.027c.08 0 .16.016.23.044l4.217 2.108-1.46-4.055a.5.5 0 0 1 .22-.579l3.523-3.356-4.217-2.108a.5.5 0 0 1-.22-.579L11.5 2.929l-4.217 2.108a.5.5 0 0 1-.22.579l-3.523 3.356 1.46 4.055a.5.5 0 0 1 .22.579L8 12.027z"/>
</svg>
</div>
<div className="flex-grow-1">
<h5 className="mt-0">自选股跟踪</h5>
<p className="mb-0">跟踪您关注的股票</p>
</div>
</li>
</ul>
<div className="mt-4">
<Link href="/" className="btn btn-primary me-2">返回首页</Link>
<Link href="/contact" className="btn btn-outline-secondary">联系我们</Link>
</div>
</div>
</div>
</div>
);
}
151 changes: 151 additions & 0 deletions apps/qwen-stock-visualization/app/contact/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
'use client';

import { useState } from 'react';

export default function ContactPage() {
const [formData, setFormData] = useState({
name: '',
email: '',
subject: '',
message: ''
});
const [submitted, setSubmitted] = useState(false);

const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log('提交的表单数据:', formData);
setSubmitted(true);

// 重置表单
setTimeout(() => {
setFormData({
name: '',
email: '',
subject: '',
message: ''
});
setSubmitted(false);
}, 3000);
};

return (
<div className="container py-5">
<div className="row">
<div className="col-lg-8 mx-auto">
<h1 className="display-4 mb-4 text-center">联系我们</h1>
<p className="lead text-center mb-5">
如果您有任何问题或建议,请随时与我们联系
</p>

{submitted && (
<div className="alert alert-success alert-dismissible fade show" role="alert">
<strong>感谢您的消息!</strong> 我们已收到您的信息,会尽快回复您。
<button type="button" className="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
)}

<div className="card shadow-sm">
<div className="card-body p-5">
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-md-6 mb-3">
<label htmlFor="name" className="form-label fw-bold">姓名</label>
<input
type="text"
className="form-control"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
required
/>
</div>
<div className="col-md-6 mb-3">
<label htmlFor="email" className="form-label fw-bold">邮箱</label>
<input
type="email"
className="form-control"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
required
/>
</div>
</div>
<div className="mb-3">
<label htmlFor="subject" className="form-label fw-bold">主题</label>
<input
type="text"
className="form-control"
id="subject"
name="subject"
value={formData.subject}
onChange={handleChange}
required
/>
</div>
<div className="mb-3">
<label htmlFor="message" className="form-label fw-bold">消息</label>
<textarea
className="form-control"
id="message"
name="message"
rows={5}
value={formData.message}
onChange={handleChange}
required
></textarea>
</div>
<div className="d-grid">
<button type="submit" className="btn btn-primary btn-lg">
发送消息
</button>
</div>
</form>
</div>
</div>

<div className="row mt-5">
<div className="col-md-4 text-center">
<div className="mb-3">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" className="bi bi-envelope text-primary" viewBox="0 0 16 16">
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4Zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1H2Zm13 2.383-4.708 2.825L15 11.105V5.383Zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741ZM1 11.105l4.708-2.897L1 5.383v5.722Z"/>
</svg>
</div>
<h5>邮箱</h5>
<p className="text-muted">support@stockdashboard.com</p>
</div>
<div className="col-md-4 text-center">
<div className="mb-3">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" className="bi bi-geo-alt text-success" viewBox="0 0 16 16">
<path d="M12.166 8.94c-.524 1.062-1.234 2.12-2.564 2.44-1.17.293-2.052-.096-2.574-.869l-.047-.081c-.293-.565-.198-1.36.223-2.02.435-.676.935-1.16 1.405-1.685.092-.103.183-.21.273-.318.334-.393.67-.804 1.023-1.21a.98.98 0 0 1 1.384 0c.354.406.69.817 1.023 1.21.09.108.18.215.273.318.537.612.64 1.425.223 2.02-.422.597-.847 1.115-1.344 1.692m-6.616-3.334l1.01 2.02 2.02.002-1.01 2.02 1.415 1.414-2.02-1.01-2.02 1.01-1.415-1.414 1.01-2.02.002-2.02 2.02 1.01z"/>
</svg>
</div>
<h5>地址</h5>
<p className="text-muted">中国上海市浦东新区<br/>世纪大道1001号</p>
</div>
<div className="col-md-4 text-center">
<div className="mb-3">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" className="bi bi-clock text-warning" viewBox="0 0 16 16">
<path d="M8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71V3.5z"/>
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm7-8A7 7 0 1 1 1 8a7 7 0 0 1 14 0z"/>
</svg>
</div>
<h5>工作时间</h5>
<p className="text-muted">周一至周五: 9:00 AM - 6:00 PM<br/>周末: 关闭</p>
</div>
</div>
</div>
</div>
</div>
);
}
Loading