first commit

This commit is contained in:
whitechiina 2026-07-15 18:29:13 +08:00
commit fa3733bfde
68 changed files with 16377 additions and 0 deletions

43
.gitignore vendored Normal file
View File

@ -0,0 +1,43 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
/lib/generated/prisma

489
PRD.md Normal file
View File

@ -0,0 +1,489 @@
# 白马家园官网 - 产品需求文档 (PRD)
## 📋 文档信息
- **项目名称**:白马家园官网
- **版本**v3.3
- **更新日期**2025年1月16日
- **状态**:开发完成,准备部署
---
## 🎯 项目概述
### 1.1 项目背景
白马家园是一家专注于数字经济和新质生产力产业集群的科技公司,致力于通过军工技术和人工智能赋能小微企业和实体商业。公司需要建设一个现代化、响应式的官方网站,用于:
- 品牌形象展示(中英文双语)
- 产品矩阵介绍
- 客户商机获取
- 行业资讯传播
- 合作加盟引流
### 1.2 项目目标
1. **品牌升级**:打造数字化、科技感的品牌形象(中英文双语)
2. **产品展示**:清晰展示四大核心产品线
3. **商机转化**:通过客服企业微信二维码引导潜在客户直接联系
4. **内容营销**:自动抓取行业资讯,保持网站活跃度
5. **用户体验**:支持手机、平板、桌面全设备响应式访问
6. **国际化**:支持中英文切换,服务全球用户
### 1.3 目标用户
- **潜在合作伙伴**:实体商家、创业者、投资人
- **C端消费者**:小微企业主、门店经营者
- **媒体和行业人士**:记者、分析师、行业专家
- **国际用户**:海外华人和全球商业伙伴
---
## 🏗️ 系统架构
### 2.1 技术栈
```
前端Next.js 16 + React 19 + TypeScript
UI框架Tailwind CSS 4 + Radix UI
动画Framer Motion
状态管理React Hooks
后端Next.js API Routes
部署Vercel
国际化Next.js i18n
```
### 2.2 目录结构
```
baima-home/
├── app/
│ ├── page.tsx # 首页(中文)
│ ├── layout.tsx # 全局布局
│ ├── globals.css # 全局样式
│ ├── en/
│ │ └── page.tsx # 首页(英文)
│ ├── about/ # 关于我们
│ ├── products/ # 产品矩阵
│ ├── news/ # 新闻动态
│ ├── contact/ # 联系加盟
│ ├── admin/ # 后台管理
│ └── api/ # API接口
│ ├── news/ # 新闻API静态数据
│ └── auto-fetch/ # 自动抓取API静态数据
├── components/
│ ├── home/ # 首页组件
│ │ ├── Hero.tsx # 首屏区域
│ │ ├── ProductPreview.tsx # 产品矩阵
│ │ ├── MissionVision.tsx # 使命愿景价值观
│ │ ├── HonorSection.tsx # 资质荣誉
│ │ └── Infrastructure.tsx # 生态基础设施
│ ├── layout/ # 布局组件
│ │ ├── Navbar.tsx # 导航栏
│ │ └── Footer.tsx # 页脚
│ └── ui/ # 基础UI组件
├── lib/
│ └── utils.ts # 工具函数
└── public/ # 静态资源
```
---
## 📱 页面规划
### 3.1 前台页面
#### 首页 (/ 和 /en)
**设计风格**:单页全景布局,沉浸式体验
**功能模块**
1. **Hero首屏**
- 品牌Logo大尺寸展示
- 核心标签军工技术、AI赋能、小微企业
- 价值主张AI数智增长引擎
- 三大核心价值:支付即锁粉、让利即引流、跨界即分润
- CTA按钮立即加入白马联盟 / Join Baima Alliance
- 背景:深蓝色科技感渐变 + 网格效果
2. **产品矩阵**
- 四大核心板块卡片布局
- 金融科技支撑(白马智付)
- 私域运营工具AI拓客宝【原名"爆客宝AI"】
- 异业联盟系统(商家联盟)
- 生态基础设施(智能硬件)
- 每张卡片包含:标题、描述、功能特性列表
- 【已移除】"深入了解"链接
3. **使命、愿景、价值观**【新增模块】
- 使命:军工技术赋能实体经济,人工智能驱动万商互联
- 愿景:天下白马是一家,一起赚钱一起花
- 价值观:共创、共享、共富
- 三大核心板块,渐变色背景,悬停动画效果
- 支持中英文双语显示
4. **权威认证与技术实力**
- 资质证书展示ISO认证、高新技术企业等
- 专利技术展示(专利数量、软著)
- 荣誉奖项展示(行业认可)
- 响应式网格布局
5. **生态基础设施**
- 会员系统
- 供应链资源
- 数字资产体系
- 【已移除】智能监控(移至智能设备)
- 【已移除】"为上市合规打造的底层架构"描述文字
6. **页脚**
- 客服企业微信二维码(替代电话和邮箱)
- 产品中心链接
- 解决方案链接
- 公司地址:天津市武清区京津产业新城海达商务大厦
- 公司信息:天津白马晶选科技有限公司 | 天津白马家园健康科技有限公司
- 版权信息
- 【已移除】隐私政策、服务条款链接
#### 关于我们 (/about)
- 公司介绍
- 团队介绍
- 发展历程
- 资质认证
#### 产品矩阵 (/products)
- 产品详情页
- 产品对比
- 定价方案
- 客户案例
#### 新闻动态 (/news)
- 新闻列表(静态数据展示)
- 新闻详情
- 分类筛选
- 搜索功能
#### 联系加盟 (/contact)
- 公司信息
- 地图定位
- 客服企业微信二维码(引导用户扫码联系)
### 3.2 后台管理 (/admin)
#### 登录验证
- 密码保护:固定密码 `admin123`
- 简洁的登录界面
#### 新闻动态管理
- 发布新闻(静态数据示例)
- 编辑新闻
- 删除新闻
- 分类管理
- 状态跟踪
#### 自动抓取管理
- 手动触发抓取(静态数据示例)
- 待审核列表
- 审核操作(通过/拒绝)
- 抓取历史记录
- 配置管理
### 3.3 语言切换
- 支持中英文双语
- 导航栏语言切换按钮
- 中文首页:`/`
- 英文首页:`/en`
- 页面内容自动切换
- 移动端支持滑出式菜单切换
---
## 📡 API 接口
### 4.1 新闻接口(静态数据)
**GET** `/api/news`
返回静态新闻数据示例,无需数据库支持。
### 4.2 自动抓取接口(静态数据)
**GET** `/api/auto-fetch`
返回静态抓取数据示例,无需数据库支持。
---
## 🤖 自动抓取系统
### 5.1 抓取策略(静态示例)
- **主要来源**:搜狗微信搜索(示例)
- **目标账号/关键词**:白马精选、白马家园
- **抓取频率**每6小时自动抓取
- **手动触发**:管理员可随时手动抓取
### 5.2 智能过滤规则
#### 必须包含的关键词
- `白马精选`(必须)
- `数字经济`、`AI`、`人工智能`、`金融科技`、`小微企业`(至少一个)
#### 排除的关键词(房地产相关)
```
房地产、楼盘、房价、房产、住宅、商业地产
购房、租房、地产投资、物业、开盘、预售证
样板间、户型图、买房、卖房、出租、二手房
白马家园、白马花园、白马苑、白马小区(小区名称)
万科白马家园、恒大白马家园、碧桂园白马家园
别墅、豪宅、精装房、毛坯房
```
#### 分类规则
- `产品发布`:包含发布、上线、新品等关键词
- `荣誉获奖`:包含获奖、认证、荣誉等关键词
- `行业洞察`:包含趋势、分析、洞察等关键词
- `媒体报道`:包含报道、媒体等关键词
- 默认:`公司新闻`
---
## 🎨 设计规范
### 6.1 视觉风格
#### 配色方案
- **主色调**:橙色 `#FF6600`
- 用途CTA按钮、强调文字、图标、链接
- **辅助色**:深蓝色 `#0A1931`
- 用途:背景、导航栏、文字
- **点缀色**`#185ADB`(浅蓝)
- **背景色**`#050A14`(深黑)
#### 字体规范
- **标题**:加粗加黑,层级分明
- **正文字体**17px基础字号
- **英文字体**Geist Sans
- **中文字体**:系统默认无衬线字体
### 6.2 响应式断点
```
移动端:< 640px
平板端640px - 1024px
桌面端:> 1024px
```
### 6.3 组件规范
- **按钮**:圆角矩形,阴影效果,悬停动画
- **卡片**:圆角边框,阴影,悬停效果
- **导航栏**:固定定位,滚动变色,语言切换
- **页脚**:多列布局,响应式堆叠,客服二维码
- **模块间距**:统一规范,保持视觉平衡
### 6.4 页面布局优化
#### Logo 尺寸
- 导航栏56pxh-14
- 首页Hero64px - 112px响应式
- 页脚56pxh-14
#### 使命愿景价值观模块
- 深蓝渐变背景
- 三栏卡片布局
- 彩色图标(橙/蓝/绿渐变)
- 悬停动画效果
- 中英文双语内容
#### 新闻卡片
- 卡片可点击,支持跳转详情页
- 分类标签+日期展示
- 摘要截断最多3行
- 阅读更多链接
---
## 🔧 功能实现
### 7.1 已完成功能
✅ **前台功能**
- 首页单页全景布局(中英文双语)
- 使命愿景价值观模块
- 响应式设计(全设备适配)
- 产品矩阵展示
- 资质荣誉展示
- 新闻动态模块(静态数据)
- 合作加盟表单
- 页脚优化(企微二维码)
✅ **技术功能**
- SEO优化
- 性能优化
- 错误处理
- 加载状态
- 中英文国际化
### 7.2 待实现功能
⏳ **内容功能**
- 新闻详情页完善
- 产品详情页
- 关于我们页面完善
⏳ **部署功能**
- Vercel部署
- 域名配置
- SSL证书
- 监控告警
---
## 🧪 测试用例
### 8.1 前台测试
#### 首页测试
- [x] Hero区域加载正常
- [x] 产品卡片显示完整
- [x] 使命愿景价值观模块显示
- [x] 资质荣誉网格布局
- [x] 新闻动态展示(静态数据)
- [x] 移动端响应式布局
- [x] 页面滚动动画效果
- [x] 中英文切换功能
---
## 🚀 部署计划
### 9.1 部署环境
- **开发环境**:本地 `localhost:3000`
- **测试环境**Vercel Preview
- **生产环境**Vercel Production
### 9.2 部署步骤
1. **代码推送**
```
git push origin main
```
2. **Vercel导入**
- 访问 https://vercel.com/new
- 导入 GitHub 仓库 `wygogogo19/baima-home`
3. **自动部署**
- Vercel自动检测并部署
- 预览环境生成
4. **生产发布**
- Vercel生产环境部署
- 域名绑定
- SSL证书配置
### 9.3 监控和维护
- **错误监控**Vercel Error Tracking
- **性能监控**Vercel Analytics
- **日志查看**Vercel Logs
- **回滚机制**Vercel Deployments
---
## 📊 项目进度
### 9.1 当前进度
- ✅ **需求分析**:完成
- ✅ **UI设计**:完成
- ✅ **前端开发**:完成
- ✅ **后端开发**:完成
- ✅ **国际化(中英文)**:完成
- ✅ **测试调试**:完成
- ⏳ **部署上线**:进行中
### 9.2 下一步计划
1. **短期目标**
- [x] Vercel部署
- [x] 生产环境测试
2. **中期目标1周内**
- [ ] 完善前台页面
- [ ] 新闻详情页开发
- [ ] 绑定自定义域名
---
## 📝 变更日志
### v3.3 (2025-01-16) - 当前版本
- 🔄 完全移除数据库依赖
- 删除 Prisma ORM 和所有数据库相关代码
- 所有 API 接口改为静态数据返回
- 删除 prisma/ 目录、lib/prisma.ts
- 从 package.json 移除 Prisma 依赖
- 不再需要 DATABASE_URL 环境变量
- 🔄 更新技术架构文档,移除数据库相关内容
- ✅ 简化部署流程,无需数据库配置
### v3.2 (2025-01-16)
- 🔄 更新资质认证描述
- "支付业务许可证" → "收单外包服务机构备案"
- 🔄 产品矩阵多项优化
- 支付能力:"主扫被扫全支持" → "主扫、被扫模式全支持"
- AI数字人"7x24小时直播 / 数字员工导购" → "AI直播/AI导购/AI客服/AI私域"
- 企微私域(原消费券流转):"供应商进货 / 转赠他人使用" → "AI数字员工自动实现社群运营/维护/导购/客服"
- 锁定客户:"一次锁定,终身受益" → "扫码即锁定推荐关系,终身享受消费分佣"
- 数字资产 → 数字资产体系
- RWA资产描述"积分抵用券互转" → "积分、抵用券、金币等"
- 智能监控巡店移至智能设备
- 🔄 页面结构调整
- 删除首页"最新动态"模块
- 删除产品卡片"深入了解"链接
- ✅ 优化产品展示逻辑,提升用户体验
### v3.1 (2025-01-16)
- 🔄 删除"在线咨询留言"功能模块Path A - 完全删除)
- 🗑️ 删除 Contact 数据模型及相关 API 路由
- 🗑️ 删除后台"客户留言管理"功能
- 🗑️ 删除前端"在线表单"提交功能
- ✅ 全站统一使用客服企业微信二维码作为对外联系入口
### v3.0 (2025-01-15)
- ✅ 新增使命愿景价值观模块
- ✅ 新增英文版首页 (/en)
- ✅ 新增中英文切换功能
- ✅ 新增自动抓取系统(搜狗微信)
- ✅ 新增智能过滤(排除房地产内容)
- ✅ 新闻卡片支持点击跳转详情页
- ✅ 页脚改为客服企业微信号码
- ✅ 移除"为上市合规打造的底层架构"
- ✅ 产品名称更新爆客宝AI → AI拓客宝
- ✅ 移除页脚隐私政策、服务条款
- ✅ Logo尺寸优化放大
- ✅ 响应式设计优化
---
## 👥 团队
- **产品经理**[待补充]
- **UI设计**[待补充]
- **前端开发**:已完成
- **后端开发**:已完成
- **测试**[待补充]
---
## 📞 联系方式
- **项目负责人**[待补充]
- **技术对接**[待补充]
- **更新日期**2025年1月16日
---
*本文档将持续更新,记录项目进展和需求变更。*

36
README.md Normal file
View File

@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

60
app/about/page.tsx Normal file
View File

@ -0,0 +1,60 @@
import { Team } from '@/components/about/Team'
import { Certifications } from '@/components/about/Certifications'
import { Badge } from '@/components/ui/badge'
export default function AboutPage() {
return (
<div className="pt-24 pb-20">
{/* Hero Section */}
<section className="container mx-auto px-4 md:px-6 mb-20">
<div className="max-w-4xl mx-auto text-center">
<Badge variant="outline" className="mb-4 border-primary text-primary bg-primary/5">
·
</Badge>
<h1 className="text-4xl md:text-5xl font-bold text-secondary mb-8">
·
</h1>
<p className="text-lg text-muted-foreground leading-relaxed mb-8">
2 亿
AI 沿"流量难、锁客难、变现难"
</p>
</div>
</section>
<Team />
<Certifications />
{/* Financing History */}
<section className="py-20 bg-secondary text-white">
<div className="container mx-auto px-4 md:px-6">
<h2 className="text-3xl font-bold text-center mb-16"></h2>
<div className="max-w-4xl mx-auto">
<div className="space-y-8 relative before:absolute before:inset-0 before:ml-5 before:-translate-x-px md:before:mx-auto md:before:translate-x-0 before:h-full before:w-0.5 before:bg-gradient-to-b before:from-transparent before:via-white/20 before:to-transparent">
{[
{ year: "B轮", title: "启迪之星投资", desc: "进一步夯实技术壁垒与市场版图" },
{ year: "A+轮", title: "沂景 / 创世伙伴 / 中测航天", desc: "多方资本加持,加速生态布局" },
{ year: "A轮", title: "创世伙伴 CCV 领投", desc: "获得知名风投机构青睐" },
{ year: "Pre-A", title: "中体基金领投", desc: "初步验证商业模式" },
{ year: "天使轮", title: "玖创 / 唯猎 / 天使湾", desc: "启动创业征程" }
].map((item, index) => (
<div key={index} className="relative flex items-center justify-between md:justify-normal md:odd:flex-row-reverse group is-active">
<div className="flex items-center justify-center w-10 h-10 rounded-full border border-white/20 bg-white/10 group-[.is-active]:bg-primary text-white shrink-0 md:order-1 md:group-odd:-translate-x-1/2 md:group-even:translate-x-1/2">
<div className="w-3 h-3 bg-current rounded-full"></div>
</div>
<div className="w-[calc(100%-4rem)] md:w-[calc(50%-2.5rem)] p-4 rounded border border-white/10 bg-white/5 shadow">
<div className="flex items-center justify-between space-x-2 mb-1">
<div className="font-bold text-white">{item.title}</div>
<time className="font-mono italic text-primary">{item.year}</time>
</div>
<div className="text-white/60 text-sm">{item.desc}</div>
</div>
</div>
))}
</div>
</div>
</div>
</section>
</div>
)
}

571
app/admin/page.tsx Normal file
View File

@ -0,0 +1,571 @@
'use client'
import { useState, useEffect } from 'react'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { Textarea } from '@/components/ui/textarea'
import {
RefreshCw,
Lock,
Newspaper,
Plus,
Trash2,
LogOut,
Rss,
Settings,
Check,
X,
Eye,
Globe
} from 'lucide-react'
type News = {
id: number
title: string
category: string
excerpt: string
createdAt: string
}
type AutoFetchedNews = {
id: number
title: string
source: string
url: string
imageUrl: string
excerpt: string
category: string
publishTime: string
status: string
isDigitalEconomy: boolean
createdAt: string
}
export default function AdminPage() {
const [activeTab, setActiveTab] = useState<'news' | 'autofetch'>('news')
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [password, setPassword] = useState('')
const [newsList, setNewsList] = useState<News[]>([])
const [autoFetchedNews, setAutoFetchedNews] = useState<AutoFetchedNews[]>([])
const [isLoading, setIsLoading] = useState(false)
// 新闻发布表单状态
const [showAddNews, setShowAddNews] = useState(false)
const [newNews, setNewNews] = useState({
title: '',
category: '公司新闻',
excerpt: '',
content: ''
})
// 自动抓取状态
const [isAutoFetching, setIsAutoFetching] = useState(false)
const [fetchStatus, setFetchStatus] = useState<{
lastFetch: string | null
nextFetch: string | null
}>({
lastFetch: null,
nextFetch: null
})
const handleLogin = (e: React.FormEvent) => {
e.preventDefault()
if (password === 'admin123') {
setIsAuthenticated(true)
fetchAllData()
} else {
alert('密码错误')
}
}
const fetchAllData = async () => {
setIsLoading(true)
try {
const [newsRes, autoFetchRes] = await Promise.all([
fetch('/api/news'),
fetch('/api/auto-fetch')
])
if (newsRes.ok) {
const data = await newsRes.json()
setNewsList(data.news || [])
}
if (autoFetchRes.ok) {
const data = await autoFetchRes.json()
// 合并待审核和已批准的内容
const allNews = [
...(data.pending || []),
...(data.approved || []).filter((a: AutoFetchedNews) =>
!data.pending?.some((p: AutoFetchedNews) => p.id === a.id)
)
]
setAutoFetchedNews(allNews)
setFetchStatus({
lastFetch: data.lastFetch,
nextFetch: data.nextFetch
})
}
} catch (error) {
console.error('获取数据失败', error)
} finally {
setIsLoading(false)
}
}
const handleAddNews = async (e: React.FormEvent) => {
e.preventDefault()
try {
const res = await fetch('/api/news', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newNews)
})
if (res.ok) {
setNewNews({ title: '', category: '公司新闻', excerpt: '', content: '' })
setShowAddNews(false)
fetchAllData()
}
} catch (error) {
alert('发布失败')
}
}
// 自动抓取相关函数
const triggerAutoFetch = async () => {
setIsAutoFetching(true)
try {
const res = await fetch('/api/auto-fetch', { method: 'POST' })
const data = await res.json()
if (data.success) {
alert(`成功抓取 ${data.articles?.length || 0} 条资讯`)
fetchAllData()
} else {
alert('抓取失败:' + data.error)
}
} catch (error) {
alert('抓取失败,请稍后重试')
} finally {
setIsAutoFetching(false)
}
}
const approveArticle = async (id: number) => {
try {
const res = await fetch(`/api/auto-fetch/${id}/approve`, { method: 'POST' })
if (res.ok) {
fetchAllData()
}
} catch (error) {
alert('操作失败')
}
}
const rejectArticle = async (id: number) => {
const reason = prompt('请输入拒绝原因:')
if (reason === null) return
try {
const res = await fetch(`/api/auto-fetch/${id}/reject`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ reason })
})
if (res.ok) {
fetchAllData()
}
} catch (error) {
alert('操作失败')
}
}
const deleteArticle = async (id: number) => {
if (!confirm('确定要删除这条资讯吗?')) return
try {
const res = await fetch(`/api/auto-fetch/${id}`, { method: 'DELETE' })
if (res.ok) {
fetchAllData()
}
} catch (error) {
alert('删除失败')
}
}
if (!isAuthenticated) {
return (
<div className="min-h-screen flex items-center justify-center bg-[#0A1931]">
<Card className="w-[350px] shadow-2xl border-0">
<CardHeader className="text-center pb-8 pt-10">
<CardTitle className="text-2xl font-black text-[#0A1931] flex flex-col items-center gap-4">
<div className="w-16 h-16 bg-[#FF6600] rounded-2xl flex items-center justify-center text-white shadow-lg">
<Lock className="w-8 h-8" />
</div>
</CardTitle>
</CardHeader>
<CardContent className="pb-10 px-8">
<form onSubmit={handleLogin} className="space-y-6">
<div className="space-y-2">
<label className="text-sm font-bold text-gray-500"></label>
<Input
type="password"
placeholder="请输入密码"
className="h-12 border-gray-200 focus:border-[#FF6600] focus:ring-[#FF6600]"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<Button type="submit" className="w-full h-12 bg-[#0A1931] hover:bg-[#185ADB] font-bold text-lg rounded-xl transition-all">
</Button>
</form>
</CardContent>
</Card>
</div>
)
}
return (
<div className="min-h-screen bg-gray-50 flex">
{/* Sidebar */}
<aside className="w-64 bg-[#0A1931] text-white hidden md:flex flex-col">
<div className="p-8 border-b border-white/10">
<span className="text-xl font-black"></span>
</div>
<nav className="flex-grow p-4 space-y-2">
<button
onClick={() => setActiveTab('news')}
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all font-bold ${activeTab === 'news' ? 'bg-[#FF6600] text-white shadow-lg' : 'hover:bg-white/5 text-gray-400'}`}
>
<Newspaper className="w-5 h-5" />
</button>
<button
onClick={() => setActiveTab('autofetch')}
className={`w-full flex items-center gap-3 px-4 py-3 rounded-xl transition-all font-bold ${activeTab === 'autofetch' ? 'bg-[#FF6600] text-white shadow-lg' : 'hover:bg-white/5 text-gray-400'}`}
>
<Rss className="w-5 h-5" />
</button>
</nav>
<div className="p-6">
<Button variant="ghost" className="w-full text-gray-400 hover:text-white" onClick={() => setIsAuthenticated(false)}>
<LogOut className="w-4 h-4 mr-2" /> 退
</Button>
</div>
</aside>
{/* Main Content */}
<main className="flex-grow pt-24 pb-12 px-4 md:px-8 overflow-y-auto">
<div className="max-w-6xl mx-auto">
<div className="flex justify-between items-center mb-10">
<div>
<h1 className="text-3xl font-black text-[#0A1931]">
{activeTab === 'news' && '公司/行业动态'}
{activeTab === 'autofetch' && '资讯自动抓取'}
</h1>
<p className="text-gray-500 mt-1">
{activeTab === 'news' && '发布和管理最新的公司动态与行业资讯'}
{activeTab === 'autofetch' && '自动抓取白马精选公众号等资讯,智能筛选后展示'}
</p>
</div>
<div className="flex gap-3">
{activeTab === 'news' && (
<Button
onClick={() => setShowAddNews(!showAddNews)}
className="bg-[#FF6600] hover:bg-[#FF8C00] font-bold"
>
<Plus className="w-4 h-4 mr-2" />
</Button>
)}
{activeTab === 'autofetch' && (
<Button
onClick={triggerAutoFetch}
disabled={isAutoFetching}
className="bg-[#FF6600] hover:bg-[#FF8C00] font-bold"
>
<RefreshCw className={`w-4 h-4 mr-2 ${isAutoFetching ? 'animate-spin' : ''}`} />
{isAutoFetching ? '抓取中...' : '立即抓取'}
</Button>
)}
<Button variant="outline" onClick={fetchAllData} disabled={isLoading} className="border-gray-200">
<RefreshCw className={`w-4 h-4 mr-2 ${isLoading ? 'animate-spin' : ''}`} />
</Button>
</div>
</div>
{/* 新闻发布表单 */}
{activeTab === 'news' && showAddNews && (
<Card className="mb-10 border-2 border-[#FF6600]/20 shadow-xl overflow-hidden">
<CardHeader className="bg-[#FF6600]/5 border-b">
<CardTitle className="text-lg"></CardTitle>
</CardHeader>
<CardContent className="pt-6">
<form onSubmit={handleAddNews} className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<label className="text-sm font-bold"></label>
<Input
placeholder="输入新闻标题"
required
onChange={(e) => setNewNews({...newNews, title: e.target.value})}
/>
</div>
<div className="space-y-2">
<label className="text-sm font-bold"></label>
<select
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
value={newNews.category}
onChange={(e) => setNewNews({...newNews, category: e.target.value})}
>
<option></option>
<option></option>
<option></option>
<option></option>
<option></option>
</select>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-bold"></label>
<Input
placeholder="输入新闻简介"
value={newNews.excerpt}
onChange={(e) => setNewNews({...newNews, excerpt: e.target.value})}
/>
</div>
<div className="space-y-2">
<label className="text-sm font-bold"></label>
<Textarea
placeholder="输入详细报道内容..."
className="min-h-[150px]"
value={newNews.content}
onChange={(e) => setNewNews({...newNews, content: e.target.value})}
/>
</div>
<div className="flex gap-3 justify-end">
<Button variant="ghost" onClick={() => setShowAddNews(false)}></Button>
<Button type="submit" className="bg-[#0A1931] hover:bg-[#185ADB] px-8"></Button>
</div>
</form>
</CardContent>
</Card>
)}
{/* 自动抓取状态面板 */}
{activeTab === 'autofetch' && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10">
<Card className="bg-gradient-to-br from-[#FF6600] to-[#FF8C00] text-white">
<CardContent className="pt-6">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-white/20 rounded-xl flex items-center justify-center">
<Rss className="w-6 h-6" />
</div>
<div>
<p className="text-sm opacity-80"></p>
<p className="text-lg font-bold">
{fetchStatus.lastFetch
? new Date(fetchStatus.lastFetch).toLocaleString('zh-CN')
: '尚未抓取'}
</p>
</div>
</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-br from-[#0A1931] to-[#185ADB] text-white">
<CardContent className="pt-6">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-white/20 rounded-xl flex items-center justify-center">
<Settings className="w-6 h-6" />
</div>
<div>
<p className="text-sm opacity-80"></p>
<p className="text-lg font-bold">
{fetchStatus.nextFetch
? new Date(fetchStatus.nextFetch).toLocaleString('zh-CN')
: '每6小时自动抓取'}
</p>
</div>
</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-br from-green-500 to-emerald-600 text-white">
<CardContent className="pt-6">
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-white/20 rounded-xl flex items-center justify-center">
<Check className="w-6 h-6" />
</div>
<div>
<p className="text-sm opacity-80"></p>
<p className="text-2xl font-bold">
{autoFetchedNews.filter(n => n.status === 'PENDING').length}
</p>
</div>
</div>
</CardContent>
</Card>
</div>
)}
{/* 数据列表 */}
<div className="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
{activeTab === 'news' && (
<div className="overflow-x-auto">
<table className="w-full text-sm text-left">
<thead className="text-xs text-gray-400 uppercase bg-gray-50/50 border-b">
<tr>
<th className="px-6 py-4"></th>
<th className="px-6 py-4"></th>
<th className="px-6 py-4"></th>
<th className="px-6 py-4"></th>
<th className="px-6 py-4 text-right"></th>
</tr>
</thead>
<tbody className="divide-y">
{newsList.length === 0 ? (
<tr><td colSpan={5} className="text-center py-20 text-gray-400"></td></tr>
) : (
newsList.map((n) => (
<tr key={n.id} className="hover:bg-gray-50 transition-colors">
<td className="px-6 py-4 text-xs text-gray-400 whitespace-nowrap">{new Date(n.createdAt).toLocaleDateString()}</td>
<td className="px-6 py-4 font-bold text-[#0A1931]">{n.title}</td>
<td className="px-6 py-4">
<span className="bg-orange-50 text-[#FF6600] px-2.5 py-1 rounded-full text-xs font-bold">{n.category}</span>
</td>
<td className="px-6 py-4 text-gray-500 max-w-xs truncate">{n.excerpt}</td>
<td className="px-6 py-4 text-right">
<Button variant="ghost" size="icon" className="text-gray-300 hover:text-red-500">
<Trash2 className="w-4 h-4" />
</Button>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
)}
{activeTab === 'autofetch' && (
<div className="overflow-x-auto">
<table className="w-full text-sm text-left">
<thead className="text-xs text-gray-400 uppercase bg-gray-50/50 border-b">
<tr>
<th className="px-6 py-4"></th>
<th className="px-6 py-4"></th>
<th className="px-6 py-4"></th>
<th className="px-6 py-4"></th>
<th className="px-6 py-4"></th>
<th className="px-6 py-4 text-right"></th>
</tr>
</thead>
<tbody className="divide-y">
{autoFetchedNews.length === 0 ? (
<tr>
<td colSpan={6} className="text-center py-20">
<div className="flex flex-col items-center gap-4">
<Rss className="w-16 h-16 text-gray-300" />
<div>
<p className="text-gray-500 font-medium"></p>
<p className="text-gray-400 text-sm">"立即抓取"</p>
</div>
</div>
</td>
</tr>
) : (
autoFetchedNews.map((news) => (
<tr key={news.id} className="hover:bg-gray-50 transition-colors">
<td className="px-6 py-4 text-xs text-gray-400 whitespace-nowrap">
{new Date(news.publishTime).toLocaleDateString()}
</td>
<td className="px-6 py-4">
<div className="font-bold text-[#0A1931] max-w-xs truncate">{news.title}</div>
{news.excerpt && (
<div className="text-gray-400 text-xs mt-1 max-w-xs truncate">{news.excerpt}</div>
)}
</td>
<td className="px-6 py-4">
<div className="flex items-center gap-2">
<Globe className="w-4 h-4 text-[#FF6600]" />
<span className="text-gray-600">{news.source}</span>
</div>
</td>
<td className="px-6 py-4">
<span className="bg-blue-50 text-blue-600 px-2.5 py-1 rounded-full text-xs font-bold">
{news.category}
</span>
</td>
<td className="px-6 py-4">
<span className={`px-2.5 py-1 rounded-full text-xs font-bold ${
news.status === 'APPROVED' ? 'bg-green-50 text-green-600' :
news.status === 'REJECTED' ? 'bg-red-50 text-red-600' :
'bg-yellow-50 text-yellow-600'
}`}>
{news.status === 'APPROVED' ? '已发布' :
news.status === 'REJECTED' ? '已拒绝' : '待审核'}
</span>
</td>
<td className="px-6 py-4 text-right">
<div className="flex justify-end gap-2">
{news.status === 'PENDING' && (
<>
<Button
variant="ghost"
size="icon"
className="text-green-500 hover:text-green-600"
onClick={() => approveArticle(news.id)}
title="通过并发布"
>
<Check className="w-4 h-4" />
</Button>
<Button
variant="ghost"
size="icon"
className="text-red-500 hover:text-red-600"
onClick={() => rejectArticle(news.id)}
title="拒绝"
>
<X className="w-4 h-4" />
</Button>
</>
)}
{news.url && (
<a
href={news.url}
target="_blank"
rel="noopener noreferrer"
className="text-gray-400 hover:text-[#FF6600] p-2"
title="查看原文"
>
<Eye className="w-4 h-4" />
</a>
)}
<Button
variant="ghost"
size="icon"
className="text-gray-300 hover:text-red-500"
onClick={() => deleteArticle(news.id)}
title="删除"
>
<Trash2 className="w-4 h-4" />
</Button>
</div>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
)}
</div>
</div>
</main>
</div>
)
}

View File

@ -0,0 +1,21 @@
import { NextResponse } from 'next/server'
export const dynamic = "force-static"
// 自动抓取接口(静态数据)
export async function GET(request: Request) {
return NextResponse.json({
lastFetch: null,
nextFetch: null,
pending: [],
approved: []
})
}
// 手动触发抓取(禁用)
export async function POST(request: Request) {
return NextResponse.json(
{ error: '自动抓取功能已禁用' },
{ status: 403 }
)
}

66
app/api/news/route.ts Normal file
View File

@ -0,0 +1,66 @@
import { NextResponse } from 'next/server'
export const dynamic = "force-static"
// 获取新闻列表(静态数据)
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const page = parseInt(searchParams.get('page') || '1')
const limit = parseInt(searchParams.get('limit') || '10')
const category = searchParams.get('category')
// 静态数据示例
const allNews = [
{
id: 1,
title: "白马家园荣获数字经济创新大奖",
category: "公司新闻",
excerpt: "白马家园在数字经济领域取得突破性进展,荣获行业创新大奖。",
createdAt: new Date().toISOString(),
source: '手动发布'
},
{
id: 2,
title: "AI数字人技术引领新零售变革",
category: "行业洞察",
excerpt: "白马家园推出的AI数字人解决方案正在重塑新零售体验。",
createdAt: new Date(Date.now() - 86400000).toISOString(),
source: '白马精选'
},
{
id: 3,
title: "收单外包服务机构备案正式获批",
category: "公司新闻",
excerpt: "白马家园成功获得收单外包服务机构备案,合规能力再上新台阶。",
createdAt: new Date(Date.now() - 172800000).toISOString(),
source: '手动发布'
}
]
// 分类筛选
const filteredNews = category
? allNews.filter(item => item.category === category)
: allNews
// 分页
const total = filteredNews.length
const paginatedNews = filteredNews.slice((page - 1) * limit, page * limit)
return NextResponse.json({
news: paginatedNews,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
},
})
}
// 创建新闻(禁用)
export async function POST(request: Request) {
return NextResponse.json(
{ error: '新闻发布功能已禁用,请通过后台手动更新' },
{ status: 403 }
)
}

BIN
app/contact/baima-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

109
app/contact/page.tsx Normal file
View File

@ -0,0 +1,109 @@
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { MapPin, UserPlus, Store, ArrowRight, MessageCircle, ScanQrCode } from 'lucide-react'
export default function ContactPage() {
return (
<div className="pt-24 pb-20 bg-white">
{/* Header */}
<section className="container mx-auto px-4 md:px-6 mb-16 text-center">
<h1 className="text-4xl font-bold text-secondary mb-4"></h1>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
</p>
</section>
<div className="container mx-auto px-4 md:px-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
{/* Left: Partner Benefits & Info */}
<div className="space-y-8">
{/* Partner Benefits */}
<Card className="border-0 shadow-lg bg-accent">
<CardHeader>
<CardTitle className="text-xl flex items-center gap-2">
<UserPlus className="w-5 h-5 text-primary" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex gap-4">
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
<Store className="w-5 h-5 text-primary" />
</div>
<div>
<h3 className="font-semibold"></h3>
<p className="text-sm text-muted-foreground"></p>
</div>
</div>
<div className="flex gap-4">
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
<ArrowRight className="w-5 h-5 text-primary" />
</div>
<div>
<h3 className="font-semibold"></h3>
<p className="text-sm text-muted-foreground"></p>
</div>
</div>
</CardContent>
</Card>
{/* Contact Info */}
<div className="space-y-6">
<h2 className="text-2xl font-bold text-secondary"></h2>
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center">
<MapPin className="w-5 h-5 text-primary" />
</div>
<div>
<p className="text-sm text-muted-foreground"></p>
<p className="font-semibold text-lg"></p>
</div>
</div>
</div>
</div>
<div>
<Card className="border-0 shadow-lg">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<MessageCircle className="w-5 h-5 text-primary" />
</CardTitle>
</CardHeader>
<CardContent className="text-center">
<div className="flex flex-col items-center space-y-6">
<div className="relative">
<img
src="/baima-qrcode.png"
alt="企业微信客服二维码"
className="w-64 h-64 object-contain rounded-2xl shadow-2xl border-4 border-primary/20"
/>
<div className="absolute -bottom-2 -right-2 w-12 h-12 bg-primary rounded-full flex items-center justify-center shadow-lg">
<ScanQrCode className="w-6 h-6 text-white" />
</div>
</div>
<div className="space-y-3">
<h3 className="text-xl font-bold text-gray-900">
</h3>
<p className="text-muted-foreground max-w-md">
</p>
</div>
<div className="flex items-center gap-2 text-sm text-primary font-medium">
<div className="w-2 h-2 bg-primary rounded-full animate-pulse" />
线
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</div>
)
}

58
app/en/page.tsx Normal file
View File

@ -0,0 +1,58 @@
import { Hero } from '@/components/home/Hero'
import { ProductPreview } from '@/components/home/ProductPreview'
import { MissionVisionEN } from '@/components/home/MissionVisionEN'
import { HonorSection } from '@/components/home/HonorSection'
import { Infrastructure } from '@/components/home/Infrastructure'
import { Button } from '@/components/ui/button'
import Link from 'next/link'
export default function Home() {
return (
<div className="flex flex-col w-full overflow-x-hidden">
{/* 1. Brand Positioning & Hero */}
<Hero />
{/* 2. Core Product Matrix */}
<div id="products" className="scroll-mt-20">
<ProductPreview />
</div>
{/* 3. Mission, Vision & Values */}
<MissionVisionEN />
{/* 4. Authority Certifications & Technical Strength */}
<HonorSection />
{/* 5. Ecological Infrastructure */}
<Infrastructure />
{/* 7. Call to Action & Contact */}
<section id="contact" className="py-24 bg-gradient-to-br from-[#0A1931] to-[#185ADB] text-white text-center scroll-mt-20">
<div className="container mx-auto px-4">
<h2 className="text-3xl md:text-5xl font-black mb-8 leading-tight">
Connect with 120,000+ Merchants<br/>Create a New Business Ecosystem
</h2>
<p className="text-lg md:text-xl text-gray-300 max-w-3xl mx-auto mb-12 leading-relaxed">
Whether you're a brick-and-mortar business seeking transformation or an individual aspiring to entrepreneurship,
Baima Home provides a broad platform. Our ecosystem is fully open to the public -
click the button below to get in touch with us.
</p>
<div className="flex flex-col sm:flex-row gap-6 justify-center">
<Link
href="/contact#form"
className="inline-flex h-14 items-center justify-center rounded-full bg-[#FF6600] px-10 text-xl font-black shadow-2xl shadow-[#FF6600]/40 transition-all hover:bg-[#FF8C00] hover:scale-105"
>
Join Baima Alliance
</Link>
<Link
href="/contact"
className="inline-flex h-14 items-center justify-center rounded-full border-2 border-white/30 px-10 text-lg font-bold transition-all hover:bg-white/10"
>
Learn More
</Link>
</div>
</div>
</section>
</div>
)
}

BIN
app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

173
app/globals.css Normal file
View File

@ -0,0 +1,173 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
/* Brand Colors */
--color-primary-blue: var(--primary-blue);
--color-accent-blue: var(--accent-blue);
--color-primary-orange: var(--primary-orange);
--color-secondary-orange: var(--secondary-orange);
/* UI Colors */
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
/* Radius */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 16px);
}
/* 融合配色方案 */
:root {
/* 品牌深蓝色调 */
--primary-blue: oklch(0.15 0.03 260);
--accent-blue: oklch(0.45 0.15 240);
/* 活力橙 */
--primary-orange: oklch(0.65 0.18 40);
--secondary-orange: oklch(0.7 0.18 45);
/* 基础色 */
--primary: var(--primary-orange);
--primary-foreground: oklch(0.99 0 0);
--secondary: var(--primary-blue);
--secondary-foreground: oklch(0.99 0 0);
--background: oklch(0.98 0.01 260);
--foreground: oklch(0.15 0.02 260);
--card: oklch(1 0 0);
--card-foreground: oklch(0.15 0.02 260);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.15 0.02 260);
--accent: oklch(0.96 0.01 260);
--accent-foreground: oklch(0.15 0.02 260);
--muted: oklch(0.94 0.01 260);
--muted-foreground: oklch(0.5 0.02 260);
--destructive: oklch(0.55 0.2 25);
--destructive-foreground: oklch(0.99 0 0);
--border: oklch(0.9 0.01 260);
--input: oklch(0.9 0.01 260);
--ring: var(--primary-orange);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground antialiased;
}
/* 全局字体放大 */
html {
font-size: 17px; /* 基础字体稍大 */
}
h1 {
@apply font-black text-[#0A1931];
font-size: 2.75rem;
line-height: 1.2;
letter-spacing: -0.02em;
}
h2 {
@apply font-bold text-[#0A1931];
font-size: 2.25rem;
line-height: 1.25;
}
h3 {
@apply font-bold text-[#0A1931];
font-size: 1.5rem;
line-height: 1.3;
}
h4, h5, h6 {
@apply font-semibold text-[#0A1931];
font-size: 1.125rem;
}
p {
@apply leading-relaxed text-gray-600;
font-size: 1rem;
}
/* 通用卡片悬浮效果 */
.card-hover {
transition: all 0.4s ease;
}
.card-hover:hover {
transform: translateY(-15px);
box-shadow: 0 20px 50px rgba(24, 90, 219, 0.15);
border-color: var(--accent-blue);
}
}
/* 重点文字高亮样式 */
.highlight-text {
@apply text-[#FF6600] font-bold;
}
.key-point {
@apply font-bold text-[#0A1931] bg-gradient-to-r from-orange-50 to-orange-100 px-2 py-0.5 rounded;
}
/* 响应式字体调整 */
@layer utilities {
@media (max-width: 768px) {
html {
font-size: 16px;
}
h1 {
font-size: 2rem !important;
}
h2 {
font-size: 1.75rem !important;
}
h3 {
font-size: 1.25rem !important;
}
p {
font-size: 0.95rem !important;
}
}
}

43
app/layout.tsx Normal file
View File

@ -0,0 +1,43 @@
import type { Metadata } from 'next'
import { Geist, Geist_Mono } from 'next/font/google'
import { Navbar } from '@/components/layout/Navbar'
import { Footer } from '@/components/layout/Footer'
import './globals.css'
const geistSans = Geist({
variable: '--font-geist-sans',
subsets: ['latin'],
})
const geistMono = Geist_Mono({
variable: '--font-geist-mono',
subsets: ['latin'],
})
export const metadata: Metadata = {
title: '白马家园—新质生产力产业集群',
description: 'AI时代小微创业+消费循环生态圈,军工技术赋能实体经济,人工智能驱动万商互联。',
icons: {
icon: '/logos.png',
},
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="zh-CN" className="scroll-smooth">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<Navbar />
<main>
{children}
</main>
<Footer />
</body>
</html>
)
}

139
app/news/page.tsx Normal file
View File

@ -0,0 +1,139 @@
'use client'
import { useState, useEffect } from 'react'
import { motion } from 'framer-motion'
import { Calendar, Eye, ArrowRight, Loader2 } from 'lucide-react'
import { Card, CardContent } from '@/components/ui/card'
import Link from 'next/link'
type News = {
id: number
title: string
category: string
excerpt: string
createdAt: string
views: number
}
const categories = ["全部", "公司新闻", "荣誉获奖", "产品发布", "媒体报道", "行业洞察"]
export default function NewsPage() {
const [news, setNews] = useState<News[]>([])
const [isLoading, setIsLoading] = useState(true)
const [activeCategory, setActiveCategory] = useState("全部")
useEffect(() => {
const fetchNews = async () => {
try {
const url = activeCategory === "全部"
? '/api/news'
: `/api/news?category=${encodeURIComponent(activeCategory)}`
const res = await fetch(url)
if (res.ok) {
const data = await res.json()
setNews(data.news)
}
} catch (error) {
console.error('加载新闻失败', error)
} finally {
setIsLoading(false)
}
}
fetchNews()
}, [activeCategory])
return (
<div className="min-h-screen bg-white pt-24 pb-20">
<div className="container mx-auto px-4 md:px-6">
{/* Header */}
<div className="text-center mb-16">
<h1 className="text-4xl md:text-5xl font-black text-[#0A1931] mb-4">
</h1>
<p className="text-lg text-gray-500 max-w-2xl mx-auto">
</p>
</div>
{/* Category Filter */}
<div className="flex flex-wrap justify-center gap-2 mb-12">
{categories.map((category) => (
<button
key={category}
onClick={() => setActiveCategory(category)}
className={`px-6 py-2 rounded-full text-sm font-bold transition-all ${
activeCategory === category
? 'bg-[#FF6600] text-white shadow-lg'
: 'bg-gray-100 text-gray-500 hover:bg-gray-200'
}`}
>
{category}
</button>
))}
</div>
{/* Loading State */}
{isLoading ? (
<div className="flex justify-center items-center py-20">
<Loader2 className="w-8 h-8 animate-spin text-[#FF6600]" />
</div>
) : news.length === 0 ? (
<div className="text-center py-20 text-gray-400 font-medium">
</div>
) : (
/* News Grid */
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{news.map((item, index) => (
<motion.div
key={item.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: index * 0.1 }}
>
<Link href="#" className="group block h-full">
<Card className="h-full border-0 shadow-lg hover:shadow-xl transition-all duration-300 overflow-hidden card-hover">
{/* Placeholder Color Block */}
<div className="h-40 bg-gradient-to-br from-[#0A1931] to-[#185ADB] flex items-center justify-center">
<span className="text-white/10 text-6xl font-black">{item.id}</span>
</div>
<CardContent className="p-6">
<div className="flex items-center justify-between mb-4">
<span className="text-xs font-bold text-[#FF6600] bg-orange-50 px-3 py-1 rounded-full">
{item.category}
</span>
<div className="flex items-center text-xs text-gray-400 font-mono">
<Calendar className="w-3 h-3 mr-1" />
{new Date(item.createdAt).toLocaleDateString()}
</div>
</div>
<h3 className="text-lg font-bold text-[#0A1931] mb-3 group-hover:text-[#FF6600] transition-colors line-clamp-2">
{item.title}
</h3>
<p className="text-sm text-gray-500 mb-6 line-clamp-3 leading-relaxed">
{item.excerpt}
</p>
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
<div className="flex items-center text-xs text-gray-400">
<Eye className="w-3 h-3 mr-1" />
{item.views}
</div>
<span className="flex items-center text-sm font-bold text-[#FF6600] group-hover:translate-x-1 transition-transform">
<ArrowRight className="ml-1 w-4 h-4" />
</span>
</div>
</CardContent>
</Card>
</Link>
</motion.div>
))}
</div>
)}
</div>
</div>
)
}

28
app/page.tsx Normal file
View File

@ -0,0 +1,28 @@
import { Hero } from '@/components/home/Hero'
import { ProductPreview } from '@/components/home/ProductPreview'
import { MissionVision } from '@/components/home/MissionVision'
import { HonorSection } from '@/components/home/HonorSection'
import { Infrastructure } from '@/components/home/Infrastructure'
export default function Page() {
return (
<main className="min-h-screen">
{/* 1. Hero首屏 */}
<Hero />
{/* 2. 产品矩阵 */}
<ProductPreview />
{/* 3. 使命、愿景、价值观 */}
<MissionVision />
{/* 4. 权威认证与技术实力 */}
<HonorSection />
{/* 5. 生态基础设施 */}
<Infrastructure />
{/* 6. 页脚 - 在 Layout.tsx 中 */}
</main>
)
}

BIN
app/products/baima-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

50
app/products/page.tsx Normal file
View File

@ -0,0 +1,50 @@
import { ProductList } from '@/components/products/ProductList'
import { Button } from '@/components/ui/button'
import Link from 'next/link'
import { ArrowRight } from 'lucide-react'
export default function ProductsPage() {
return (
<div className="min-h-screen bg-white pt-24 pb-20">
<div className="container mx-auto px-4 md:px-6">
{/* Page Header */}
<div className="max-w-3xl mx-auto text-center mb-16">
<h1 className="text-4xl md:text-5xl font-bold text-secondary mb-6">
</h1>
<p className="text-lg text-muted-foreground leading-relaxed">
AI
</p>
</div>
{/* Navigation Tabs */}
<div className="sticky top-20 z-40 bg-white/95 backdrop-blur-md py-4 mb-8 border-b border-gray-100 hidden md:block">
<nav className="flex justify-center gap-8">
<a href="#fintech" className="text-sm font-medium text-muted-foreground hover:text-primary transition-colors"></a>
<a href="#ai" className="text-sm font-medium text-muted-foreground hover:text-primary transition-colors"></a>
<a href="#alliance" className="text-sm font-medium text-muted-foreground hover:text-primary transition-colors"></a>
<a href="#infrastructure" className="text-sm font-medium text-muted-foreground hover:text-primary transition-colors"></a>
</nav>
</div>
<ProductList />
{/* CTA */}
<div className="mt-24 text-center bg-accent rounded-2xl p-12">
<h2 className="text-3xl font-bold text-secondary mb-4"></h2>
<p className="text-muted-foreground mb-8 max-w-2xl mx-auto">
</p>
<div className="flex justify-center gap-4">
<Button size="lg" asChild className="bg-primary hover:bg-primary/90 text-white rounded-full px-8">
<Link href="/contact">
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
</div>
</div>
</div>
)
}

22
components.json Normal file
View File

@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
}

View File

@ -0,0 +1,93 @@
'use client'
import { Trophy, Award, FileText, Medal } from 'lucide-react'
const awards = [
"2023 科大讯飞 AI 开发者大赛数字人赛道冠军",
"2022 中国产业元宇宙创新大赛一等奖",
"2023 元宇宙应用场景大赛产业生态先锋奖",
"2024 退役军人创新创业大赛一等奖"
]
const patents = [
"发明专利:融合多信息的 AI 虚拟人与用户交互方法",
"发明专利:一种虚拟人物不同表情模型生成方法",
"80+ 项计算机软件著作权",
"50+ 项资质证书与备案"
]
export function Certifications() {
return (
<section className="py-20 bg-white" id="honor">
<div className="container mx-auto px-4 md:px-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
{/* Text Content */}
<div className="space-y-8">
<div>
<h2 className="text-3xl font-bold text-secondary mb-6"></h2>
<p className="text-muted-foreground mb-8">
</p>
</div>
<div className="space-y-6">
<div className="flex gap-4">
<div className="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
<Trophy className="w-6 h-6 text-primary" />
</div>
<div>
<h3 className="font-bold text-lg mb-2"></h3>
<ul className="space-y-2">
{awards.map((award, i) => (
<li key={i} className="flex items-center text-sm text-muted-foreground">
<span className="w-1.5 h-1.5 bg-primary rounded-full mr-2"></span>
{award}
</li>
))}
</ul>
</div>
</div>
<div className="flex gap-4">
<div className="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
<FileText className="w-6 h-6 text-primary" />
</div>
<div>
<h3 className="font-bold text-lg mb-2"></h3>
<ul className="space-y-2">
{patents.map((patent, i) => (
<li key={i} className="flex items-center text-sm text-muted-foreground">
<span className="w-1.5 h-1.5 bg-primary rounded-full mr-2"></span>
{patent}
</li>
))}
</ul>
</div>
</div>
</div>
</div>
{/* Certificate Cards */}
<div className="grid grid-cols-2 gap-4">
<div className="aspect-[3/4] bg-accent rounded-lg shadow-sm border border-gray-100 p-4 flex flex-col items-center justify-center text-center">
<Medal className="w-12 h-12 text-primary mb-4" />
<span className="text-xs font-medium text-muted-foreground"></span>
</div>
<div className="aspect-[3/4] bg-accent rounded-lg shadow-sm border border-gray-100 p-4 flex flex-col items-center justify-center text-center mt-8">
<Award className="w-12 h-12 text-primary mb-4" />
<span className="text-xs font-medium text-muted-foreground"></span>
</div>
<div className="aspect-[3/4] bg-accent rounded-lg shadow-sm border border-gray-100 p-4 flex flex-col items-center justify-center text-center">
<Trophy className="w-12 h-12 text-primary mb-4" />
<span className="text-xs font-medium text-muted-foreground"></span>
</div>
<div className="aspect-[3/4] bg-accent rounded-lg shadow-sm border border-gray-100 p-4 flex flex-col items-center justify-center text-center mt-8">
<FileText className="w-12 h-12 text-primary mb-4" />
<span className="text-xs font-medium text-muted-foreground"></span>
</div>
</div>
</div>
</div>
</section>
)
}

73
components/about/Team.tsx Normal file
View File

@ -0,0 +1,73 @@
'use client'
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { User } from 'lucide-react'
const team = [
{
name: "汪洋",
role: "董事长",
badges: ["国防科大本硕博", "高级工程师", "上校转业"],
description: "深度参与神舟、嫦娥、北斗等航天任务。获中国青年科技奖全网粉丝100万+ AIGC/元宇宙博主。"
},
{
name: "段海龙",
role: "技术总监",
badges: ["国防科大学士", "中科大硕士", "高级工程师"],
description: "专注于人工智能、机器视觉方向。发表论文20多篇软著及专利20余项。"
},
{
name: "秦波",
role: "总经理",
badges: ["第一军医大学", "副主任医师", "正团职转业"],
description: "资深健康养老产业链专家军队医院保健专家。10年以上市场招商渠道拓展经验。"
},
{
name: "焦文军",
role: "CFO",
badges: ["中国十大杰出CFO", "天狮前副总裁"],
description: "多家港股、美股公司上市操盘手。中央财经大学、南开大学客座教授。"
}
]
export function Team() {
return (
<section className="py-20 bg-accent" id="team">
<div className="container mx-auto px-4 md:px-6">
<div className="text-center mb-16">
<h2 className="text-3xl font-bold text-secondary mb-4"></h2>
<p className="text-muted-foreground max-w-2xl mx-auto">
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
{team.map((member, index) => (
<Card key={index} className="border-0 shadow-lg hover:shadow-xl transition-all">
<CardHeader className="text-center pb-2">
<div className="w-24 h-24 mx-auto bg-white rounded-full flex items-center justify-center mb-4 shadow-md">
<User className="w-10 h-10 text-primary" />
</div>
<CardTitle className="text-xl font-bold">{member.name}</CardTitle>
<CardDescription className="text-primary font-medium">{member.role}</CardDescription>
</CardHeader>
<CardContent className="text-center">
<div className="flex flex-wrap justify-center gap-2 mb-4">
{member.badges.map((badge, bIndex) => (
<Badge key={bIndex} variant="secondary" className="text-xs bg-primary/10 text-primary">
{badge}
</Badge>
))}
</div>
<p className="text-sm text-muted-foreground text-left leading-relaxed">
{member.description}
</p>
</CardContent>
</Card>
))}
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,90 @@
'use client'
import { motion } from 'framer-motion'
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { User, ShieldCheck, TrendingUp } from 'lucide-react'
const team = [
{
name: "汪洋",
role: "董事长",
badges: ["国防科大本硕博", "航天级专家"],
description: "深度参与神舟、北斗任务。全网粉丝100万+ AIGC头部博主。"
},
{
name: "段海龙",
role: "技术总监",
badges: ["国防科大学士", "高级工程师"],
description: "专注AI与机器视觉。发表论文20多篇拥有专利20余项。"
},
{
name: "秦波",
role: "总经理",
badges: ["第一军医大学", "正团职转业"],
description: "资深康养专家10年以上市场招商渠道拓展经验。"
},
{
name: "焦文军",
role: "CFO",
badges: ["十大杰出CFO", "操盘上市"],
description: "多家上市公司操盘手。中央财经大学、南开大学客座教授。"
}
]
export function AboutSection() {
return (
<section id="about" className="py-24 bg-white scroll-mt-20">
<div className="container mx-auto px-4 md:px-6">
<div className="max-w-4xl mx-auto text-center mb-20">
<Badge variant="outline" className="mb-4 border-[#FF6600] text-[#FF6600] bg-orange-50 font-bold">
</Badge>
<h2 className="text-3xl md:text-5xl font-black text-[#0A1931] mb-6">
</h2>
<p className="text-lg text-gray-500 leading-relaxed">
2 亿
</p>
</div>
{/* 核心团队 */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 md:gap-8">
{team.map((member, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.1 }}
>
<Card className="border-0 shadow-lg hover:shadow-xl transition-all h-full bg-gray-50/50 group">
<CardHeader className="text-center pb-4">
<div className="w-20 h-20 mx-auto bg-white rounded-2xl flex items-center justify-center mb-4 shadow-sm group-hover:bg-[#FF6600] transition-colors duration-300">
<User className="w-10 h-10 text-[#0A1931] group-hover:text-white" />
</div>
<CardTitle className="text-xl font-bold text-[#0A1931]">{member.name}</CardTitle>
<CardDescription className="text-[#FF6600] font-bold">{member.role}</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-wrap justify-center gap-1.5 mb-4">
{member.badges.map((badge, bIndex) => (
<span key={bIndex} className="text-[10px] font-bold bg-white px-2 py-0.5 rounded border border-gray-100 text-gray-500">
{badge}
</span>
))}
</div>
<p className="text-sm text-gray-500 leading-relaxed text-center">
{member.description}
</p>
</CardContent>
</Card>
</motion.div>
))}
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,107 @@
'use client'
import { motion } from 'framer-motion'
import { CheckCircle2, TrendingUp, Users, Building2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import Link from 'next/link'
export function Ecosystem() {
return (
<section className="py-24 bg-secondary text-white relative overflow-hidden">
{/* Background Effects */}
<div className="absolute inset-0 overflow-hidden">
<div className="absolute -top-40 -right-40 w-80 h-80 bg-primary/20 rounded-full blur-3xl"></div>
<div className="absolute -bottom-40 -left-40 w-80 h-80 bg-primary/10 rounded-full blur-3xl"></div>
</div>
<div className="container px-4 md:px-6 mx-auto relative z-10">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 items-center">
{/* Left Content */}
<motion.div
initial={{ opacity: 0, x: -50 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<h2 className="text-3xl md:text-5xl font-bold mb-6">
<span className="text-primary"> </span>
</h2>
<p className="text-lg text-gray-300 mb-8 leading-relaxed">
"数据收益优先分配权"
</p>
{/* Features List */}
<div className="space-y-5 mb-10">
{[
{ icon: TrendingUp, text: '时时/单单/天天补贴,立即到账' },
{ icon: Users, text: '用户消费即参与利润分配' },
{ icon: Building2, text: '商家让利即获得流量回馈' },
{ icon: CheckCircle2, text: '透明公开,无泡沫正向拨比' }
].map((item, index) => (
<div key={index} className="flex items-center gap-4">
<div className="w-10 h-10 bg-primary/20 rounded-lg flex items-center justify-center">
<item.icon className="w-5 h-5 text-primary" />
</div>
<span className="text-gray-200">{item.text}</span>
</div>
))}
</div>
<Button asChild className="bg-primary hover:bg-primary/90 text-white rounded-full px-8 h-12">
<Link href="/contact"></Link>
</Button>
</motion.div>
{/* Right Visual */}
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
className="relative"
>
{/* Central Circle */}
<div className="relative aspect-square w-full max-w-md mx-auto">
{/* Outer Ring */}
<div className="absolute inset-0 border-2 border-white/10 rounded-full"></div>
{/* Inner Content */}
<div className="absolute inset-8 border border-white/10 rounded-full flex items-center justify-center">
<div className="w-32 h-32 bg-primary rounded-full flex items-center justify-center shadow-lg shadow-primary/30">
<span className="font-bold text-white text-lg"></span>
</div>
</div>
{/* Orbiting Elements */}
{[
{ angle: 0, label: '消费者' },
{ angle: 90, label: '商家' },
{ angle: 180, label: '供应链' },
{ angle: 270, label: '平台' }
].map((item, index) => (
<div
key={index}
className="absolute w-20 h-20 bg-white/10 backdrop-blur-sm rounded-xl flex items-center justify-center border border-white/20"
style={{
top: '50%',
left: '50%',
transform: `translate(-50%, -50%) rotate(${item.angle}deg) translateY(-140px) rotate(-${item.angle}deg)`
}}
>
<span className="text-sm font-medium">{item.label}</span>
</div>
))}
{/* Connection Lines */}
<svg className="absolute inset-0 w-full h-full pointer-events-none opacity-20">
<circle cx="50%" cy="50%" r="140" fill="none" stroke="white" strokeWidth="1" strokeDasharray="5,5" />
</svg>
</div>
</motion.div>
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,103 @@
'use client'
import { motion } from 'framer-motion'
import {
ShieldCheck,
Cpu,
TrendingUp,
Users,
Globe,
Award
} from 'lucide-react'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
const features = [
{
icon: ShieldCheck,
title: "国资背景",
description: "获五轮融资超2亿元中华财政部、社保基金理事会等间接持股实力雄厚稳健经营。"
},
{
icon: Cpu,
title: "军工技术",
description: "国防科大核心团队拥有80+项发明专利。将航天级技术应用于实体经济数字化转型。"
},
{
icon: TrendingUp,
title: "数据收益",
description: "首创'数据收益优先分配权',让用户和商家共享数据增值红利,打造可持续商业模式。"
},
{
icon: Users,
title: "顶尖团队",
description: "汇聚国防科大、中科大等名校精英以及上市企业高管、杰出CFO等行业领袖。"
},
{
icon: Globe,
title: "合规保障",
description: "拥有收单外包服务机构备案,资金受银监会监管。数商备案接入,工商税务监管系统实时对接。"
},
{
icon: Award,
title: "权威认可",
description: "科大讯飞AI开发者大赛冠军、中国产业元宇宙创新大赛一等奖央视等权威媒体多次报道。"
}
]
export function Features() {
return (
<section className="py-24 bg-white">
<div className="container px-4 md:px-6 mx-auto">
{/* Section Header */}
<div className="text-center mb-16">
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-3xl md:text-4xl font-bold text-secondary mb-4"
>
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-lg text-muted-foreground max-w-2xl mx-auto"
>
</motion.p>
</div>
{/* Features Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{features.map((feature, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
>
<Card className="h-full border-0 shadow-lg hover:shadow-xl transition-all duration-300 group bg-accent">
<CardHeader className="pb-4">
<div className="w-14 h-14 bg-primary/10 rounded-xl flex items-center justify-center mb-4 group-hover:bg-primary group-hover:text-white transition-all duration-300">
<feature.icon className="w-7 h-7 text-primary group-hover:text-white" />
</div>
<CardTitle className="text-xl font-bold text-secondary group-hover:text-primary transition-colors">
{feature.title}
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground leading-relaxed">
{feature.description}
</p>
</CardContent>
</Card>
</motion.div>
))}
</div>
</div>
</section>
)
}

137
components/home/Hero.tsx Normal file
View File

@ -0,0 +1,137 @@
'use client'
import { motion } from 'framer-motion'
import Image from 'next/image'
import { ArrowRight } from 'lucide-react'
import Link from 'next/link'
export function Hero() {
return (
<section className="relative w-full min-h-screen flex items-center justify-center overflow-hidden bg-[#050A14] pt-16">
{/* Background Gradient */}
<div className="absolute inset-0 bg-gradient-to-br from-[#0A1931] via-[#0d1f45] to-[#050A14]"></div>
{/* Grid Effect */}
<div className="absolute inset-0 opacity-20">
<svg className="w-full h-full hidden sm:block">
<defs>
<pattern id="grid-blue" width="60" height="60" patternUnits="userSpaceOnUse">
<path d="M 60 0 L 0 0 0 60" fill="none" stroke="white" strokeWidth="0.5" strokeOpacity="0.3"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid-blue)" />
</svg>
</div>
{/* Glow Effects */}
<div className="absolute top-1/4 left-1/4 w-64 h-64 md:w-96 md:h-96 bg-[#185ADB] rounded-full blur-[100px] md:blur-[150px] opacity-20 animate-pulse hidden sm:block"></div>
<div className="absolute bottom-1/4 right-1/4 w-48 h-48 md:w-80 md:h-80 bg-[#FF6600] rounded-full blur-[80px] md:blur-[120px] opacity-15 hidden sm:block"></div>
<div className="container relative z-10 px-4 md:px-6 mx-auto">
<div className="max-w-5xl mx-auto text-center mb-3">
{/* Logo */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="mb-6 md:mb-10"
>
<div className="relative h-16 w-auto aspect-[2/1] mx-auto sm:h-20 md:h-28">
<Image
src="/baima-logo.png"
alt="白马家园 Logo"
fill
className="object-contain"
priority
/>
</div>
</motion.div>
{/* Tags - 放大并突出 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="flex flex-wrap justify-center gap-3 md:gap-4 mb-8 md:mb-10"
>
{[
"小微企业数智化升级",
"AI + Fintech 生态圈",
"军工技术赋能"
].map((tag, index) => (
<span
key={index}
className="px-4 py-2 md:px-5 md:py-2.5 bg-white/10 border border-white/20 rounded-full text-sm md:text-base font-bold text-white backdrop-blur-sm shadow-lg"
>
{tag}
</span>
))}
</motion.div>
{/* Headline - 超大号字体,核心卖点突出 */}
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1 }}
className="text-3xl sm:text-5xl md:text-6xl lg:text-7xl font-black text-white mb-6 md:mb-8 leading-tight"
>
<span className="block mb-2 text-xl sm:text-2xl md:text-3xl font-light text-gray-300">
</span>
<span className="text-[#FF6600] text-4xl sm:text-6xl md:text-7xl lg:text-8xl block">
AI
</span>
</motion.h1>
{/* Subheadline - 核心价值主张,大字展示 */}
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="text-xl sm:text-2xl md:text-3xl font-bold text-[#FF6600] mb-6 md:mb-8 tracking-wide"
>
· ·
</motion.p>
{/* Description */}
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 }}
className="text-base sm:text-lg md:text-xl text-gray-300 mb-10 md:mb-12 max-w-4xl mx-auto leading-relaxed font-medium"
>
<span className="text-white font-bold">120,000+</span> 线<br className="hidden sm:block"/>
·
</motion.p>
{/* CTA Button - 超大醒目按钮 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.4 }}
>
<Link
href="/contact#form"
className="group relative inline-flex items-center gap-3 px-8 py-4 md:px-10 md:py-5 bg-gradient-to-r from-[#FF6600] to-[#FF8C00] text-lg md:text-xl font-black rounded-full shadow-2xl shadow-[#FF6600]/50 transition-all duration-300 hover:scale-105 hover:shadow-[#FF6600]/70"
>
<span className="text-lg md:text-xl"></span>
<ArrowRight className="w-6 h-6 md:w-7 md:h-7 transition-transform group-hover:translate-x-1" />
</Link>
</motion.div>
</div>
</div>
{/* Scroll Indicator */}
<motion.div
animate={{ y: [0, 10, 0] }}
transition={{ duration: 2, repeat: Infinity }}
className="absolute bottom-4 md:bottom-8 left-1/2 -translate-x-1/2 text-white/30 hidden sm:block"
>
<div className="w-6 h-10 md:w-7 md:h-12 border-2 border-current rounded-full flex items-start justify-center p-1.5 md:p-2">
<div className="w-1.5 h-2 md:w-2 md:h-3 bg-current rounded-full"></div>
</div>
</motion.div>
</section>
)
}

View File

@ -0,0 +1,223 @@
'use client'
import { motion } from 'framer-motion'
import { Trophy, Award, FileText, Medal, Shield, Zap, Users, Globe, CheckCircle2 } from 'lucide-react'
// 扩充后的资质和知识产权数据
const honors = [
{
icon: Trophy,
title: "科大讯飞 AI 开发者大赛",
subtitle: "数字人赛道冠军",
color: "bg-orange-100 text-orange-600"
},
{
icon: Medal,
title: "中国产业元宇宙创新大赛",
subtitle: "一等奖",
color: "bg-blue-100 text-blue-600"
},
{
icon: Award,
title: "退役军人创新创业大赛",
subtitle: "一等奖",
color: "bg-purple-100 text-purple-600"
},
{
icon: Globe,
title: "元宇宙应用场景大赛",
subtitle: "产业生态先锋奖",
color: "bg-green-100 text-green-600"
}
]
const patents = [
{
title: "融合多信息的 AI 虚拟人与用户交互方法",
type: "发明专利"
},
{
title: "一种虚拟人物不同表情模型生成方法",
type: "发明专利"
},
{
title: "基于深度学习的智能收银系统",
type: "发明专利"
},
{
title: "多通道智能路由分配算法",
type: "发明专利"
},
{
title: "区块链数据确权与价值分配系统",
type: "发明专利"
},
{
title: "AI 驱动的精准营销推荐系统",
type: "发明专利"
}
]
const certifications = [
{
title: "收单外包服务机构备案",
desc: "中国支付清算协会备案,合规开展收单外包服务"
},
{
title: "ISO 9001 质量管理体系认证",
desc: "国际标准化组织认证"
},
{
title: "高新技术企业认定",
desc: "享受国家高新技术企业税收优惠"
},
{
title: "软件企业认定",
desc: "符合国家软件产业政策要求"
},
{
title: "信息安全管理系统认证",
desc: "保障用户数据安全"
},
{
title: "数商备案接入",
desc: "合规参与数据要素市场"
}
]
export function HonorSection() {
return (
<section id="honor" className="py-24 bg-[#F4F7FB] scroll-mt-20">
<div className="container mx-auto px-4 md:px-6">
{/* 大标题 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-center mb-16"
>
<h2 className="text-3xl md:text-5xl font-black text-[#0A1931] mb-4">
·
</h2>
<p className="text-lg text-gray-500 max-w-3xl mx-auto">
</p>
</motion.div>
{/* 1. 权威奖项展示 - 大卡片风格 */}
<div className="mb-20">
<h3 className="text-2xl font-bold text-[#0A1931] mb-8 flex items-center gap-3">
<Trophy className="w-8 h-8 text-[#FF6600]" />
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{honors.map((item, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.1 }}
className="bg-white p-8 rounded-2xl shadow-lg hover:shadow-xl transition-all hover:-translate-y-1 border border-gray-100"
>
<div className={`w-16 h-16 ${item.color} rounded-2xl flex items-center justify-center mb-6`}>
<item.icon className="w-8 h-8" />
</div>
<h4 className="text-xl font-bold text-[#0A1931] mb-2">{item.title}</h4>
<p className="text-[#FF6600] font-bold">{item.subtitle}</p>
</motion.div>
))}
</div>
</div>
{/* 2. 知识产权展示 - 网格布局 */}
<div className="mb-20">
<h3 className="text-2xl font-bold text-[#0A1931] mb-8 flex items-center gap-3">
<FileText className="w-8 h-8 text-[#185ADB]" />
(80+ )
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{patents.map((item, index) => (
<motion.div
key={index}
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.05 }}
className="bg-white p-5 rounded-xl border border-gray-200 hover:border-[#185ADB] hover:shadow-md transition-all flex items-start gap-4"
>
<div className="w-10 h-10 bg-[#185ADB]/10 rounded-lg flex items-center justify-center shrink-0 mt-1">
<Shield className="w-5 h-5 text-[#185ADB]" />
</div>
<div>
<h4 className="font-bold text-[#0A1931] mb-1">{item.title}</h4>
<span className="text-xs font-bold text-[#185ADB] bg-[#185ADB]/10 px-2 py-0.5 rounded">{item.type}</span>
</div>
</motion.div>
))}
{/* 更多专利提示 */}
<motion.div
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.4 }}
className="bg-gradient-to-br from-[#0A1931] to-[#185ADB] p-5 rounded-xl text-white flex items-center justify-center"
>
<div className="text-center">
<Zap className="w-10 h-10 mx-auto mb-2 text-[#FF6600]" />
<p className="font-bold text-lg">80+ </p>
<p className="text-sm text-gray-300"></p>
</div>
</motion.div>
</div>
</div>
{/* 3. 资质认证展示 - 详情列表 */}
<div>
<h3 className="text-2xl font-bold text-[#0A1931] mb-8 flex items-center gap-3">
<CheckCircle2 className="w-8 h-8 text-green-600" />
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{certifications.map((item, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.1 }}
className="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 hover:shadow-md transition-all"
>
<div className="flex items-start gap-4">
<div className="w-12 h-12 bg-green-50 rounded-xl flex items-center justify-center shrink-0">
<CheckCircle2 className="w-6 h-6 text-green-600" />
</div>
<div>
<h4 className="font-bold text-[#0A1931] mb-2">{item.title}</h4>
<p className="text-sm text-gray-500 leading-relaxed">{item.desc}</p>
</div>
</div>
</motion.div>
))}
</div>
</div>
{/* 底部强调 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="mt-16 bg-gradient-to-r from-[#0A1931] to-[#185ADB] rounded-3xl p-10 text-white text-center"
>
<h4 className="text-2xl font-black mb-4"> · </h4>
<p className="text-lg text-gray-300 max-w-2xl mx-auto">
</p>
</motion.div>
</div>
</section>
)
}

View File

@ -0,0 +1,70 @@
'use client'
import { motion } from 'framer-motion'
import { Diamond, Truck, Eye, Coins } from 'lucide-react'
const infraItems = [
{
icon: Diamond,
title: "智慧会员体系",
description: "一次注册,全城打通<br/>全平台消费数据互通"
},
{
icon: Truck,
title: "供应链赋能",
description: "积分当钱花,进货更便宜<br/>源头工厂直供去库存"
},
{
icon: Eye,
title: "智能巡店系统",
description: "手机看店AI管店<br/>客流热力图实时分析"
},
{
icon: Coins,
title: "数智积分体系",
description: "基于真实消费产生<br/>拒绝泡沫,价值恒定"
}
]
export function Infrastructure() {
return (
<section className="py-12 md:py-16 lg:py-20 bg-[#F4F7FB]">
<div className="container px-4 md:px-6 mx-auto">
{/* Section Header - 响应式 */}
<div className="text-center mb-10 md:mb-16">
<h2 className="text-xl sm:text-2xl md:text-3xl lg:text-4xl font-bold text-[#0A1931] mb-3 md:mb-4">
</h2>
{/* 已移除:为上市合规打造的底层架构 */}
</div>
{/* Grid - 响应式布局 */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 md:gap-6">
{infraItems.map((item, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
className="bg-white p-4 md:p-6 lg:p-8 text-center rounded-lg md:rounded-xl border-b-4 border-transparent hover:border-[#FF6600] hover:shadow-lg transition-all duration-300"
>
{/* Icon - 响应式 */}
<div className="w-10 h-10 sm:w-12 sm:h-12 md:w-16 md:h-16 bg-[#0A1931]/5 rounded-lg md:rounded-xl flex items-center justify-center mx-auto mb-4 md:mb-6">
<item.icon className="w-5 h-5 sm:w-6 sm:h-6 md:w-8 md:h-8 text-[#185ADB]" />
</div>
{/* Title - 响应式 */}
<h3 className="text-sm sm:text-base md:text-lg font-bold text-[#0A1931] mb-2 md:mb-3">
{item.title}
</h3>
{/* Description - 响应式 */}
<p className="text-xs sm:text-sm text-gray-500 leading-relaxed"
dangerouslySetInnerHTML={{ __html: item.description }}>
</p>
</motion.div>
))}
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,108 @@
'use client'
import { motion } from 'framer-motion'
import { Target, Eye, Heart, Star, Shield, Users, TrendingUp } from 'lucide-react'
export function MissionVision() {
const coreValues = [
{
icon: Target,
title: "使命",
content: "军工技术赋能实体经济<br/>人工智能驱动万商互联",
color: "from-orange-500 to-red-500"
},
{
icon: Eye,
title: "愿景",
content: "天下白马是一家<br/>一起赚钱一起花",
color: "from-blue-500 to-indigo-600"
},
{
icon: Heart,
title: "价值观",
content: "共创 · 共享 · 共富",
color: "from-green-500 to-emerald-600"
}
]
return (
<section className="py-24 bg-gradient-to-br from-[#0A1931] via-[#0d1f45] to-[#050A14] relative overflow-hidden">
{/* 背景装饰 */}
<div className="absolute inset-0">
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-[#FF6600] rounded-full blur-[150px] opacity-10"></div>
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-[#185ADB] rounded-full blur-[150px] opacity-10"></div>
</div>
<div className="container mx-auto px-4 relative z-10">
{/* 标题 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-center mb-16"
>
<h2 className="text-3xl md:text-5xl font-black text-white mb-4">
</h2>
<p className="text-gray-400 text-lg max-w-2xl mx-auto">
</p>
</motion.div>
{/* 三大核心支柱 */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{coreValues.map((item, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.2 }}
className="group"
>
<div className="bg-white/5 backdrop-blur-sm rounded-2xl p-8 border border-white/10 hover:border-[#FF6600]/50 transition-all duration-500 h-full">
{/* 图标 */}
<div className={`w-20 h-20 mx-auto mb-6 rounded-2xl bg-gradient-to-r ${item.color} flex items-center justify-center shadow-lg group-hover:scale-110 transition-transform duration-300`}>
<item.icon className="w-10 h-10 text-white" />
</div>
{/* 标题 */}
<h3 className="text-2xl font-bold text-white text-center mb-6">
{item.title}
</h3>
{/* 内容 */}
<div
className="text-center text-gray-300 leading-loose text-lg"
dangerouslySetInnerHTML={{ __html: item.content }}
/>
{/* 装饰线 */}
<div className="mt-6 flex justify-center">
<div className={`w-16 h-1 bg-gradient-to-r ${item.color} rounded-full opacity-50`}></div>
</div>
</div>
</motion.div>
))}
</div>
{/* 底部标语 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.5 }}
className="mt-16 text-center"
>
<div className="inline-flex items-center gap-4 bg-[#FF6600]/10 backdrop-blur-sm px-8 py-4 rounded-full border border-[#FF6600]/20">
<Star className="w-6 h-6 text-[#FF6600]" />
<span className="text-[#FF6600] font-bold text-lg">
</span>
<Star className="w-6 h-6 text-[#FF6600]" />
</div>
</motion.div>
</div>
</section>
)
}

View File

@ -0,0 +1,108 @@
'use client'
import { motion } from 'framer-motion'
import { Target, Eye, Heart, Star } from 'lucide-react'
export function MissionVisionEN() {
const coreValues = [
{
icon: Target,
title: "Mission",
content: "Military-grade Technology Empowers Physical Economy<br/>AI Drives Universal Business Connectivity",
color: "from-orange-500 to-red-500"
},
{
icon: Eye,
title: "Vision",
content: "One Baima Family Worldwide<br/>Grow Wealth Together, Spend Together",
color: "from-blue-500 to-indigo-600"
},
{
icon: Heart,
title: "Values",
content: "Co-create · Share · Prosper Together",
color: "from-green-500 to-emerald-600"
}
]
return (
<section className="py-24 bg-gradient-to-br from-[#0A1931] via-[#0d1f45] to-[#050A14] relative overflow-hidden">
{/* 背景装饰 */}
<div className="absolute inset-0">
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-[#FF6600] rounded-full blur-[150px] opacity-10"></div>
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-[#185ADB] rounded-full blur-[150px] opacity-10"></div>
</div>
<div className="container mx-auto px-4 relative z-10">
{/* 标题 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-center mb-16"
>
<h2 className="text-3xl md:text-5xl font-black text-white mb-4">
Our Beliefs
</h2>
<p className="text-gray-400 text-lg max-w-2xl mx-auto">
Staying True to Our Original Aspiration, Advancing with Determination - Baima Home Builds a Better Future Together
</p>
</motion.div>
{/* 三大核心支柱 */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{coreValues.map((item, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.2 }}
className="group"
>
<div className="bg-white/5 backdrop-blur-sm rounded-2xl p-8 border border-white/10 hover:border-[#FF6600]/50 transition-all duration-500 h-full">
{/* 图标 */}
<div className={`w-20 h-20 mx-auto mb-6 rounded-2xl bg-gradient-to-r ${item.color} flex items-center justify-center shadow-lg group-hover:scale-110 transition-transform duration-300`}>
<item.icon className="w-10 h-10 text-white" />
</div>
{/* 标题 */}
<h3 className="text-2xl font-bold text-white text-center mb-6">
{item.title}
</h3>
{/* 内容 */}
<div
className="text-center text-gray-300 leading-loose text-lg"
dangerouslySetInnerHTML={{ __html: item.content }}
/>
{/* 装饰线 */}
<div className="mt-6 flex justify-center">
<div className={`w-16 h-1 bg-gradient-to-r ${item.color} rounded-full opacity-50`}></div>
</div>
</div>
</motion.div>
))}
</div>
{/* 底部标语 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.5 }}
className="mt-16 text-center"
>
<div className="inline-flex items-center gap-4 bg-[#FF6600]/10 backdrop-blur-sm px-8 py-4 rounded-full border border-[#FF6600]/20">
<Star className="w-6 h-6 text-[#FF6600]" />
<span className="text-[#FF6600] font-bold text-lg">
Digital Intelligence Connects Businesses, Creates Better Future
</span>
<Star className="w-6 h-6 text-[#FF6600]" />
</div>
</motion.div>
</div>
</section>
)
}

View File

@ -0,0 +1,83 @@
'use client'
import { useState, useEffect } from 'react'
import { motion } from 'framer-motion'
import { Calendar, ArrowRight, Loader2 } from 'lucide-react'
import Link from 'next/link'
type News = {
id: number
title: string
category: string
excerpt: string
createdAt: string
}
export function NewsSection() {
const [news, setNews] = useState<News[]>([])
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
fetch('/api/news?limit=3')
.then(res => res.json())
.then(data => {
setNews(data.news)
setIsLoading(false)
})
.catch(() => setIsLoading(false))
}, [])
return (
<section id="news" className="py-24 bg-white scroll-mt-20">
<div className="container mx-auto px-4 md:px-6">
<div className="flex flex-col md:flex-row justify-between items-end mb-16 gap-6">
<div>
<h2 className="text-3xl md:text-5xl font-black text-[#0A1931] mb-4"></h2>
<p className="text-gray-500 text-lg"></p>
</div>
<Link href="/news" className="text-[#FF6600] font-bold flex items-center gap-2 hover:translate-x-1 transition-transform">
<ArrowRight className="w-5 h-5" />
</Link>
</div>
{isLoading ? (
<div className="flex justify-center py-20"><Loader2 className="w-8 h-8 animate-spin text-[#FF6600]" /></div>
) : news.length === 0 ? (
<div className="text-center py-20 text-gray-400 bg-gray-50 rounded-2xl"></div>
) : (
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{news.map((item, index) => (
<Link href={`/news?id=${item.id}`} key={item.id}>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.1 }}
className="group p-6 rounded-2xl border border-gray-100 bg-white hover:shadow-xl transition-all cursor-pointer h-full"
>
<div className="flex items-center gap-2 mb-4">
<span className="text-[10px] font-black uppercase tracking-widest text-[#FF6600] bg-orange-50 px-2 py-0.5 rounded">
{item.category}
</span>
<span className="text-xs text-gray-400 flex items-center font-mono">
<Calendar className="w-3 h-3 mr-1" /> {new Date(item.createdAt).toLocaleDateString()}
</span>
</div>
<h3 className="text-xl font-bold text-[#0A1931] mb-4 group-hover:text-[#FF6600] transition-colors line-clamp-2">
{item.title}
</h3>
<p className="text-sm text-gray-500 leading-relaxed mb-6 line-clamp-3">
{item.excerpt}
</p>
<span className="text-sm font-bold text-[#0A1931] flex items-center gap-1 group-hover:gap-2 transition-all">
<ArrowRight className="w-4 h-4" />
</span>
</motion.div>
</Link>
))}
</div>
)}
</div>
</section>
)
}

View File

@ -0,0 +1,137 @@
'use client'
import { motion } from 'framer-motion'
import { Button } from '@/components/ui/button'
import { CreditCard, Users, Network, Database } from 'lucide-react'
// 根据框图重构四大核心板块
const products = [
{
title: '高效安全的金融科技支撑',
enTitle: 'Fintech Support',
description: '收单-分销-结算一体化多通道AI路由保障资金安全与高效分账。',
icon: CreditCard,
color: 'bg-gradient-to-br from-[#0A1931] to-[#185ADB]',
features: [
{ title: "白马智付", desc: "锁粉 / 自动分佣 / AI路由" },
{ title: "智能设备", desc: "智慧收银系统 / 支持绿色积分 / 智能监控巡店" },
{ title: "支付能力", desc: "主扫、被扫模式全支持 / 强大技术底座" }
]
},
{
title: '简单易用的私域运营工具',
enTitle: 'Private Domain Tools',
description: 'AI驱动的私域流量运营神器让获客、留存、转化变得简单高效。',
icon: Users,
color: 'bg-gradient-to-br from-[#FF6600] to-[#FF8C00]',
features: [
{ title: "AI数字人", desc: "AI直播/AI导购/AI客服/AI私域" },
{ title: "积分权益", desc: "兑换AI直播 / 营销获客权益" },
{ title: "企微私域", desc: "AI数字员工自动实现社群运营/维护/导购/客服" }
]
},
{
title: '轻松锁客的异业联盟系统',
enTitle: 'Alliance System',
description: '打破单打独斗,通过异业联盟锁定客户终身价值,实现跨界收益。',
icon: Network,
color: 'bg-gradient-to-br from-[#185ADB] to-[#0A1931]',
features: [
{ title: "锁定客户", desc: "扫码即锁定推荐关系,终身享受消费分佣" },
{ title: "跨界分佣", desc: "赚360行的钱 / 跨门店收益" },
{ title: "供应链分佣", desc: "分享优质供应链商品获利" }
]
},
{
title: '统一高效的生态基础设施',
enTitle: 'Ecological Infrastructure',
description: '为小微企业提供完整的数智化基建,打通会员、资产与供应链。',
icon: Database,
color: 'bg-gradient-to-br from-[#059669] to-[#10B981]',
features: [
{ title: "会员系统", desc: "跨平台生态融合 / 统一身份" },
{ title: "数字资产体系", desc: "RWA资产 / 积分、抵用券、金币等" },
{ title: "供应链资源", desc: "稳定安全靠谱" }
]
}
]
export function ProductPreview() {
return (
<section className="py-12 md:py-20 lg:py-24 bg-white">
<div className="container px-4 md:px-6 mx-auto">
{/* Section Header */}
<div className="text-center mb-10 md:mb-16">
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold text-[#0A1931] mb-3 md:mb-4"
>
AI时代小微创业 +
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1 }}
className="text-sm sm:text-base md:text-lg text-gray-500 px-4"
>
</motion.p>
</div>
{/* Product Cards Grid - 2x2 Layout recommended for 4 items */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 md:gap-8 lg:gap-10">
{products.map((product, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
className="group h-full"
>
<div className="card-hover bg-white rounded-xl md:rounded-2xl overflow-hidden border border-gray-100 h-full flex flex-col">
{/* Header with Icon */}
<div className="p-6 md:p-8 flex items-start justify-between border-b border-gray-50 bg-gray-50/50">
<div>
<h3 className="text-xl md:text-2xl font-bold text-[#0A1931] mb-1">
{product.title}
</h3>
<span className="text-xs font-semibold text-[#FF6600] uppercase tracking-wider">
{product.enTitle}
</span>
</div>
<div className={`w-12 h-12 md:w-14 md:h-14 rounded-xl flex items-center justify-center text-white shadow-md ${product.color}`}>
<product.icon className="w-6 h-6 md:w-7 md:h-7" />
</div>
</div>
{/* Content */}
<div className="p-6 md:p-8 flex-grow flex flex-col">
<p className="text-sm text-gray-500 mb-6 leading-relaxed">
{product.description}
</p>
{/* Features List */}
<ul className="space-y-3 md:space-y-4 mb-8 flex-grow">
{product.features.map((feature, fIndex) => (
<li key={fIndex} className="flex items-start text-sm">
<span className="w-1.5 h-1.5 bg-[#FF6600] rounded-full mr-3 mt-1.5 shrink-0"></span>
<div className="flex-1">
<strong className="text-[#0A1931] block mb-0.5">{feature.title}</strong>
<span className="text-gray-500 text-xs md:text-sm">{feature.desc}</span>
</div>
</li>
))}
</ul>
</div>
</div>
</motion.div>
))}
</div>
</div>
</section>
)
}

42
components/home/Stats.tsx Normal file
View File

@ -0,0 +1,42 @@
'use client'
import { motion } from 'framer-motion'
const stats = [
{ value: "120,000+", label: "服务实体商户", highlight: "12万+" },
{ value: "500,000+", label: "日均交易笔数", highlight: "50万+" },
{ value: "300万+", label: "累计引流人次", highlight: "300万+" },
{ value: "20亿+", label: "平台年GMV流水", highlight: "20亿+" }
]
export function Stats() {
return (
<section className="py-0">
<div className="bg-gradient-to-r from-[#0A1931] via-[#185ADB] to-[#0A1931] text-white py-16 md:py-20">
<div className="container mx-auto px-4">
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 md:gap-12 items-center">
{stats.map((stat, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
className="text-center"
>
{/* 大号数字,突出显示 */}
<h3 className="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-black text-[#FF6600] mb-2 md:mb-3 leading-none">
{stat.highlight}
</h3>
{/* 下方详细说明 */}
<p className="text-sm sm:text-base md:text-lg text-gray-300 font-medium uppercase tracking-wider">
{stat.label}
</p>
</motion.div>
))}
</div>
</div>
</div>
</section>
)
}

34
components/home/Trust.tsx Normal file
View File

@ -0,0 +1,34 @@
'use client'
export function Trust() {
const partners = [
"微信支付认证服务商",
"支付宝ISV合作伙伴",
"中国银联",
"启迪之星(投资方)",
"中金资本生态成员",
"OTC Markets (拟)"
]
return (
<section className="py-8 md:py-10 lg:py-12 bg-white border-t border-gray-100">
<div className="container px-4 mx-auto">
{/* 响应式标题 */}
<p className="text-center text-gray-400 text-xs sm:text-sm font-bold uppercase tracking-[1px] md:tracking-[2px] mb-6 md:mb-8">
Strategic Partners & Qualifications
</p>
{/* 响应式网格 */}
<div className="flex flex-wrap justify-center gap-2 md:gap-4 px-2">
{partners.map((partner, index) => (
<div
key={index}
className="px-3 py-2 md:px-6 md:py-3 bg-white border border-gray-200 rounded-full text-xs sm:text-sm font-semibold text-gray-500 hover:border-[#185ADB] hover:text-[#185ADB] transition-colors cursor-default whitespace-nowrap"
>
{partner}
</div>
))}
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,111 @@
'use client'
import Link from 'next/link'
import Image from 'next/image'
import { Separator } from '@/components/ui/separator'
import { MapPin } from 'lucide-react'
export function Footer() {
const currentYear = new Date().getFullYear()
const hostname = typeof window !== 'undefined' ? window.location.hostname : ''
const icpNumber = hostname === 'www.baimajingxuan.com' ? '津ICP备2020008456号-1' : hostname === 'www.baimajiayuanjiankang.com' ? '津ICP备2023000762号' : '津ICP备99999999号'
return (
<footer className="bg-[#050A14] text-gray-400 pt-16 pb-8">
<div className="container mx-auto px-4">
<div className="grid grid-cols-1 lg:grid-cols-4 gap-12 mb-12">
{/* Brand Info */}
<div className="lg:col-span-1">
{/* Logo Image */}
<div className="relative h-14 w-auto aspect-[2/1] mb-6">
<Image
src="/baima-logo.png"
alt="白马家园 Logo"
fill
className="object-contain"
/>
</div>
<p className="text-sm leading-relaxed mb-6 text-gray-500">
<br/>
AI时代小微创业与消费循环生态圈
</p>
</div>
{/* Quick Links */}
<div>
<h3 className="font-semibold text-white text-lg mb-6"></h3>
<ul className="space-y-3 text-sm">
<li><Link href="/products" className="hover:text-[#FF6600] transition-colors"></Link></li>
<li><Link href="/products" className="hover:text-[#FF6600] transition-colors">AI拓客宝</Link></li>
<li><Link href="/products" className="hover:text-[#FF6600] transition-colors"></Link></li>
<li><Link href="/products" className="hover:text-[#FF6600] transition-colors"></Link></li>
</ul>
</div>
{/* Solutions */}
<div>
<h3 className="font-semibold text-white text-lg mb-6"></h3>
<ul className="space-y-3 text-sm">
<li><Link href="#" className="hover:text-[#FF6600] transition-colors"></Link></li>
<li><Link href="#" className="hover:text-[#FF6600] transition-colors"></Link></li>
<li><Link href="#" className="hover:text-[#FF6600] transition-colors"></Link></li>
<li><Link href="#" className="hover:text-[#FF6600] transition-colors"></Link></li>
</ul>
</div>
{/* Contact Info - 客服企业微信二维码 */}
<div>
<h3 className="font-semibold text-white text-lg mb-6"></h3>
<div className="space-y-4">
{/* 企业微信二维码 */}
<div className="bg-white p-3 rounded-lg inline-block">
<div className="relative w-28 h-28">
<Image
src="/baima-qrcode.png"
alt="扫码添加客服企业微信"
fill
className="object-contain rounded"
/>
</div>
</div>
<p className="text-xs text-gray-500">
<br/>
</p>
{/* 地址保留 */}
<div className="flex items-start gap-3 pt-4 border-t border-gray-800">
<MapPin className="w-4 h-4 text-[#FF6600] mt-1 shrink-0" />
<p className="text-sm"><br/></p>
</div>
</div>
</div>
</div>
<Separator className="bg-white/10 mb-8" />
{/* 底部公司名称 - 更新为两家公司 */}
<div className="text-center pb-4">
<p className="text-sm text-gray-500 font-medium">
<br className="md:hidden" />
<span className="mx-2 hidden md:inline">|</span>
</p>
</div>
<div className="text-center text-xs text-gray-600">
<p>&copy; {currentYear} </p>
<a
href="https://beian.miit.gov.cn/"
target="_blank"
rel="noopener noreferrer"
className="hover:text-[#FF6600] transition-colors"
style={{ marginTop: '10px', fontSize: '16px', display: 'block' }}
>
{icpNumber}
</a>
</div>
</div>
</footer>
)
}

View File

@ -0,0 +1,271 @@
'use client'
import * as React from 'react'
import Link from 'next/link'
import Image from 'next/image'
import { Menu, Globe, ArrowRight } from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
Sheet,
SheetContent,
SheetTrigger,
} from '@/components/ui/sheet'
import { cn } from '@/lib/utils'
// 导航结构 - 中文
const navigationCN = [
{ name: '首页', href: '/' },
{ name: '产品矩阵', href: '/products' },
{ name: '关于我们', href: '/about' },
{ name: '合作加盟', href: '/contact' },
]
// 导航结构 - 英文
const navigationEN = [
{ name: 'Home', href: '/en' },
{ name: 'Products', href: '/products' },
{ name: 'About', href: '/about' },
{ name: 'Contact', href: '/contact' },
]
export function Navbar() {
const [isScrolled, setIsScrolled] = React.useState(false)
const [language, setLanguage] = React.useState<'zh' | 'en'>('zh')
const [mounted, setMounted] = React.useState(false)
const [currentPath, setCurrentPath] = React.useState('')
// 初始化语言状态和当前路径
React.useEffect(() => {
setMounted(true)
if (typeof window !== 'undefined') {
const path = window.location.pathname
setLanguage(path.startsWith('/en') ? 'en' : 'zh')
setCurrentPath(path)
}
}, [])
// 监听路由变化,更新当前路径
React.useEffect(() => {
if (typeof window !== 'undefined') {
const handlePopState = () => {
setCurrentPath(window.location.pathname)
}
// 监听浏览器前进后退
window.addEventListener('popstate', handlePopState)
// 保存原始的pushState和replaceState
const originalPushState = window.history.pushState
const originalReplaceState = window.history.replaceState
// 重写pushState和replaceState
window.history.pushState = function(...args) {
originalPushState.apply(this, args)
handlePopState()
}
window.history.replaceState = function(...args) {
originalReplaceState.apply(this, args)
handlePopState()
}
return () => {
window.removeEventListener('popstate', handlePopState)
}
}
}, [])
React.useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 20)
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [])
// 根据语言选择导航
const navigation = language === 'zh' ? navigationCN : navigationEN
const currentLanguage = language
// 切换语言
const toggleLanguage = () => {
const newLang = currentLanguage === 'zh' ? 'en' : 'zh'
setLanguage(newLang)
// 跳转到对应语言版本
if (typeof window !== 'undefined') {
const path = window.location.pathname
let newPath = path
if (newLang === 'en') {
// 转到英文版(首页)
if (path === '/') {
newPath = '/en'
} else {
// 其他页面保持原样(暂时只有首页有英文版)
newPath = '/en'
}
} else {
// 转到中文版
if (path.startsWith('/en')) {
newPath = '/'
} else {
newPath = path
}
}
window.location.href = newPath
}
}
return (
<header
className={cn(
'fixed top-0 left-0 right-0 z-50 transition-all duration-300',
isScrolled
? 'bg-white/98 backdrop-blur-md shadow-sm border-b border-gray-100 py-3'
: 'bg-transparent py-5'
)}
>
<div className="container mx-auto px-4 flex items-center justify-between">
{/* Logo */}
<Link href={currentLanguage === 'zh' ? '/' : '/en'} className="flex items-center gap-3">
<div className="relative h-14 w-auto aspect-[2/1]">
<Image
src="/baima-logo.png"
alt="Baima Home Logo"
fill
className="object-contain"
priority
/>
</div>
</Link>
{/* Desktop Navigation */}
<nav className="hidden lg:flex items-center gap-8">
{navigation.map((item) => {
// 检测当前活跃链接
const isActive = mounted && currentPath === item.href;
// 检测是否为首页
const isHomePage = item.href === '/' || item.href === '/en';
// 检测当前是否在指定的页面(/about、/products、/contact
const isOnSpecificPage = currentPath.startsWith('/about') ||
currentPath.startsWith('/products') ||
currentPath.startsWith('/contact');
return (
<Link
key={item.name}
href={item.href}
className={cn(
"text-sm font-medium transition-colors hover:text-[#FF6600]",
isActive
? "text-[#FF6600]"
: isOnSpecificPage || isScrolled
? "text-black"
: isHomePage ? "text-black" : "text-white"
)}
>
{item.name}
</Link>
);
})}
</nav>
{/* Right Side Actions */}
<div className="hidden lg:flex items-center gap-4">
{/* <button
onClick={toggleLanguage}
className={cn(
"flex items-center gap-1.5 text-sm font-medium transition-colors hover:text-[#FF6600]",
isScrolled ? "text-[#555]" : "text-white/90 drop-shadow-md"
)}
>
<Globe className="w-4 h-4" />
<span>{currentLanguage === 'zh' ? 'English' : '中文'}</span>
</button> */}
<Button
size="sm"
className={cn(
"flex items-center gap-2 px-5 py-2.5 rounded-full text-sm font-bold transition-all shadow-md",
isScrolled
? "bg-[#FF6600] hover:bg-[#FF8C00] text-white"
: "bg-white text-[#FF6600] hover:bg-white/90"
)}
asChild
>
<Link href="/contact#form">
{currentLanguage === 'zh' ? '立即咨询' : 'Get Started'}
</Link>
</Button>
</div>
{/* Mobile Navigation */}
<div className="lg:hidden">
<Sheet>
<SheetTrigger asChild>
<Button
variant="ghost"
size="icon"
className={cn(!isScrolled && "text-white drop-shadow-md")}
>
<Menu className="h-6 w-6" />
<span className="sr-only">Open menu</span>
</Button>
</SheetTrigger>
<SheetContent side="right" className="w-[300px] sm:w-[400px]">
<div className="flex flex-col gap-6 mt-8">
<div className="flex items-center gap-3 pb-4 border-b">
<div className="relative h-8 w-auto aspect-[2/1]">
<Image
src="./logos.png"
alt="Baima Home Logo"
fill
className="object-contain"
/>
</div>
</div>
{/* 语言切换 */}
<button
onClick={toggleLanguage}
className="flex items-center gap-2 text-sm font-medium text-[#FF6600] py-2 px-4 bg-orange-50 rounded-lg"
>
<Globe className="w-4 h-4" />
{currentLanguage === 'zh' ? 'Switch to English' : '切换到中文'}
</button>
<div className="flex flex-col gap-2">
{navigation.map((item) => {
// 检测当前活跃链接
const isActive = mounted && currentPath === item.href;
return (
<Link
key={item.name}
href={item.href}
className={cn(
"text-lg font-medium py-3 px-4 rounded-lg hover:bg-gray-100 transition-colors",
isActive ? "text-[#FF6600] bg-orange-50" : "text-gray-800"
)}
>
{item.name}
</Link>
);
})}
</div>
<div className="pt-4 mt-auto">
<Button className="w-full bg-[#FF6600] hover:bg-[#FF8C00] rounded-full" size="lg" asChild>
<Link href="/contact#form">
{currentLanguage === 'zh' ? '立即咨询' : 'Get Started'} <ArrowRight className="ml-2 w-4 h-4" />
</Link>
</Button>
</div>
</div>
</SheetContent>
</Sheet>
</div>
</div>
</header>
)
}

View File

@ -0,0 +1,186 @@
'use client'
import { motion } from 'framer-motion'
import {
CreditCard,
Smartphone,
Bot,
Users,
Network,
ShoppingBag,
LayoutGrid,
Coins,
Shield,
Camera,
Share2,
Gift
} from 'lucide-react'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
// 完全基于框图重构的数据结构
const categories = [
{
id: "fintech",
title: "高效安全的金融科技支撑",
subtitle: "收单-分销-结算 一体化解决方案",
items: [
{
title: "白马智付",
icon: CreditCard,
features: [
"强大的技术能力,保障交易稳定",
"多通道AI路由智能选择最优通道",
"AI智慧收款码支持输入文本",
"核心功能:锁粉、自动分佣"
]
},
{
title: "智能设备",
icon: Smartphone,
features: [
"智慧收银系统硬件支持",
"全场景支付:支持主扫、被扫",
"绿色积分:支持积分抵扣支付",
"设备联网与远程管理"
]
}
]
},
{
id: "ai",
title: "简单易用的私域运营工具",
subtitle: "AI赋能让私域运营自动化、智能化",
items: [
{
title: "AI营销数字人",
icon: Bot,
features: [
"数字员工:全天候导购、客服",
"AI直播自动讲解商品提升转化",
"AI营销获客智能生成内容引流"
]
},
{
title: "积分与消费券体系",
icon: Gift,
features: [
"消费券:可从供应商进货,可流转他人",
"积分权益兑换AI数字人/直播权益",
"企微私域运营:无缝打通微信生态"
]
}
]
},
{
id: "alliance",
title: "轻松锁客的异业联盟系统",
subtitle: "打破孤岛,实现跨界收益最大化",
items: [
{
title: "锁定客户终身价值",
icon: Users,
features: [
"一次锁定,终身受益",
"赚360行的钱跨行业收益共享",
"客户在联盟内任何消费均有关联"
]
},
{
title: "全维度分佣机制",
icon: Network,
features: [
"赚其他门店的分佣收益",
"赚其他供应链的分佣收益",
"构建真正的利益共同体"
]
}
]
},
{
id: "infrastructure",
title: "统一高效的生态基础设施",
subtitle: "以上生态均对外开放,共建数智化底座",
items: [
{
title: "核心系统基建",
icon: LayoutGrid,
features: [
"统一会员系统:跨平台生态融合",
"专业小微数智化系统:积分支付年费",
"涵盖:收银/会员/营销/外卖/进销存/连锁等"
]
},
{
title: "数字资产体系 (RWA)",
icon: Coins,
features: [
"RWA数字资产化",
"资产转换:积分、抵用券、金币互转",
"数字藏品与RWA权益绑定"
]
},
{
title: "供应链与监控",
icon: Shield,
features: [
"供应链资源:稳定、安全、靠谱",
"智能监控巡店设备:远程管理门店",
"供应链陆续招募中"
]
}
]
}
]
export function ProductList() {
return (
<div className="space-y-24 py-12">
{categories.map((category, catIndex) => (
<section key={category.id} id={category.id} className="scroll-mt-24">
{/* Section Header */}
<div className="flex flex-col md:flex-row md:items-center gap-4 md:gap-6 mb-12">
<div className="w-12 h-12 md:w-16 md:h-16 flex items-center justify-center bg-[#FF6600] text-white text-xl md:text-2xl font-bold rounded-2xl shadow-lg shrink-0">
0{catIndex + 1}
</div>
<div>
<h2 className="text-2xl md:text-3xl font-bold text-[#0A1931] mb-2">{category.title}</h2>
<p className="text-sm md:text-base text-gray-500 font-medium">{category.subtitle}</p>
</div>
</div>
{/* Products Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8">
{category.items.map((item, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
>
<Card className="h-full border-0 shadow-md hover:shadow-xl transition-all duration-300 bg-white group hover:-translate-y-1">
<CardHeader className="flex flex-row items-center gap-4 pb-4 border-b border-gray-50">
<div className="w-12 h-12 bg-[#0A1931]/5 rounded-xl flex items-center justify-center text-[#FF6600] group-hover:bg-[#FF6600] group-hover:text-white transition-colors duration-300">
<item.icon className="w-6 h-6" />
</div>
<CardTitle className="text-lg font-bold text-[#0A1931]">{item.title}</CardTitle>
</CardHeader>
<CardContent className="pt-6">
<ul className="space-y-3">
{item.features.map((feature, fIndex) => (
<li key={fIndex} className="flex items-start text-sm text-gray-600">
<span className="w-1.5 h-1.5 bg-[#185ADB] rounded-full mr-3 mt-1.5 shrink-0"></span>
<span className="leading-relaxed">{feature}</span>
</li>
))}
</ul>
</CardContent>
</Card>
</motion.div>
))}
</div>
</section>
))}
</div>
)
}

View File

@ -0,0 +1,66 @@
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDownIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Accordion({
...props
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
}
function AccordionItem({
className,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
return (
<AccordionPrimitive.Item
data-slot="accordion-item"
className={cn("border-b last:border-b-0", className)}
{...props}
/>
)
}
function AccordionTrigger({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
return (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
data-slot="accordion-trigger"
className={cn(
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
)
}
function AccordionContent({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
return (
<AccordionPrimitive.Content
data-slot="accordion-content"
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
{...props}
>
<div className={cn("pt-0 pb-4", className)}>{children}</div>
</AccordionPrimitive.Content>
)
}
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

46
components/ui/badge.tsx Normal file
View File

@ -0,0 +1,46 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
secondary:
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
function Badge({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "span"
return (
<Comp
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
}
export { Badge, badgeVariants }

62
components/ui/button.tsx Normal file
View File

@ -0,0 +1,62 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }

92
components/ui/card.tsx Normal file
View File

@ -0,0 +1,92 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}

241
components/ui/carousel.tsx Normal file
View File

@ -0,0 +1,241 @@
"use client"
import * as React from "react"
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react"
import { ArrowLeft, ArrowRight } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
type CarouselApi = UseEmblaCarouselType[1]
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
type CarouselOptions = UseCarouselParameters[0]
type CarouselPlugin = UseCarouselParameters[1]
type CarouselProps = {
opts?: CarouselOptions
plugins?: CarouselPlugin
orientation?: "horizontal" | "vertical"
setApi?: (api: CarouselApi) => void
}
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
api: ReturnType<typeof useEmblaCarousel>[1]
scrollPrev: () => void
scrollNext: () => void
canScrollPrev: boolean
canScrollNext: boolean
} & CarouselProps
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
function useCarousel() {
const context = React.useContext(CarouselContext)
if (!context) {
throw new Error("useCarousel must be used within a <Carousel />")
}
return context
}
function Carousel({
orientation = "horizontal",
opts,
setApi,
plugins,
className,
children,
...props
}: React.ComponentProps<"div"> & CarouselProps) {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === "horizontal" ? "x" : "y",
},
plugins
)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) return
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
}, [])
const scrollPrev = React.useCallback(() => {
api?.scrollPrev()
}, [api])
const scrollNext = React.useCallback(() => {
api?.scrollNext()
}, [api])
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
}
},
[scrollPrev, scrollNext]
)
React.useEffect(() => {
if (!api || !setApi) return
setApi(api)
}, [api, setApi])
React.useEffect(() => {
if (!api) return
onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)
return () => {
api?.off("select", onSelect)
}
}, [api, onSelect])
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}
>
<div
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
role="region"
aria-roledescription="carousel"
data-slot="carousel"
{...props}
>
{children}
</div>
</CarouselContext.Provider>
)
}
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
const { carouselRef, orientation } = useCarousel()
return (
<div
ref={carouselRef}
className="overflow-hidden"
data-slot="carousel-content"
>
<div
className={cn(
"flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className
)}
{...props}
/>
</div>
)
}
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
const { orientation } = useCarousel()
return (
<div
role="group"
aria-roledescription="slide"
data-slot="carousel-item"
className={cn(
"min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4",
className
)}
{...props}
/>
)
}
function CarouselPrevious({
className,
variant = "outline",
size = "icon",
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
return (
<Button
data-slot="carousel-previous"
variant={variant}
size={size}
className={cn(
"absolute size-8 rounded-full",
orientation === "horizontal"
? "top-1/2 -left-12 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}
>
<ArrowLeft />
<span className="sr-only">Previous slide</span>
</Button>
)
}
function CarouselNext({
className,
variant = "outline",
size = "icon",
...props
}: React.ComponentProps<typeof Button>) {
const { orientation, scrollNext, canScrollNext } = useCarousel()
return (
<Button
data-slot="carousel-next"
variant={variant}
size={size}
className={cn(
"absolute size-8 rounded-full",
orientation === "horizontal"
? "top-1/2 -right-12 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollNext}
onClick={scrollNext}
{...props}
>
<ArrowRight />
<span className="sr-only">Next slide</span>
</Button>
)
}
export {
type CarouselApi,
Carousel,
CarouselContent,
CarouselItem,
CarouselPrevious,
CarouselNext,
}

21
components/ui/input.tsx Normal file
View File

@ -0,0 +1,21 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return (
<input
type={type}
data-slot="input"
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className
)}
{...props}
/>
)
}
export { Input }

View File

@ -0,0 +1,168 @@
import * as React from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { ChevronDownIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function NavigationMenu({
className,
children,
viewport = true,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
viewport?: boolean
}) {
return (
<NavigationMenuPrimitive.Root
data-slot="navigation-menu"
data-viewport={viewport}
className={cn(
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
className
)}
{...props}
>
{children}
{viewport && <NavigationMenuViewport />}
</NavigationMenuPrimitive.Root>
)
}
function NavigationMenuList({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
return (
<NavigationMenuPrimitive.List
data-slot="navigation-menu-list"
className={cn(
"group flex flex-1 list-none items-center justify-center gap-1",
className
)}
{...props}
/>
)
}
function NavigationMenuItem({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
return (
<NavigationMenuPrimitive.Item
data-slot="navigation-menu-item"
className={cn("relative", className)}
{...props}
/>
)
}
const navigationMenuTriggerStyle = cva(
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1"
)
function NavigationMenuTrigger({
className,
children,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
return (
<NavigationMenuPrimitive.Trigger
data-slot="navigation-menu-trigger"
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDownIcon
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
)
}
function NavigationMenuContent({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
return (
<NavigationMenuPrimitive.Content
data-slot="navigation-menu-content"
className={cn(
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
className
)}
{...props}
/>
)
}
function NavigationMenuViewport({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
return (
<div
className={cn(
"absolute top-full left-0 isolate z-50 flex justify-center"
)}
>
<NavigationMenuPrimitive.Viewport
data-slot="navigation-menu-viewport"
className={cn(
"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
className
)}
{...props}
/>
</div>
)
}
function NavigationMenuLink({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
return (
<NavigationMenuPrimitive.Link
data-slot="navigation-menu-link"
className={cn(
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function NavigationMenuIndicator({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
return (
<NavigationMenuPrimitive.Indicator
data-slot="navigation-menu-indicator"
className={cn(
"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
className
)}
{...props}
>
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
</NavigationMenuPrimitive.Indicator>
)
}
export {
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
navigationMenuTriggerStyle,
}

View File

@ -0,0 +1,28 @@
"use client"
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils"
function Separator({
className,
orientation = "horizontal",
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return (
<SeparatorPrimitive.Root
data-slot="separator"
decorative={decorative}
orientation={orientation}
className={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className
)}
{...props}
/>
)
}
export { Separator }

139
components/ui/sheet.tsx Normal file
View File

@ -0,0 +1,139 @@
"use client"
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />
}
function SheetTrigger({
...props
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
}
function SheetClose({
...props
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
}
function SheetPortal({
...props
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
}
function SheetOverlay({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
return (
<SheetPrimitive.Overlay
data-slot="sheet-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className
)}
{...props}
/>
)
}
function SheetContent({
className,
children,
side = "right",
...props
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
side?: "top" | "right" | "bottom" | "left"
}) {
return (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
data-slot="sheet-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
side === "right" &&
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
side === "left" &&
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
side === "top" &&
"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
side === "bottom" &&
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
className
)}
{...props}
>
{children}
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
<XIcon className="size-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
)
}
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-header"
className={cn("flex flex-col gap-1.5 p-4", className)}
{...props}
/>
)
}
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="sheet-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
)
}
function SheetTitle({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
return (
<SheetPrimitive.Title
data-slot="sheet-title"
className={cn("text-foreground font-semibold", className)}
{...props}
/>
)
}
function SheetDescription({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
return (
<SheetPrimitive.Description
data-slot="sheet-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
export {
Sheet,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}

View File

@ -0,0 +1,18 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
<textarea
data-slot="textarea"
className={cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
{...props}
/>
)
}
export { Textarea }

18
eslint.config.mjs Normal file
View File

@ -0,0 +1,18 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);
export default eslintConfig;

6
lib/utils.ts Normal file
View File

@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

21
next.config.js Normal file
View File

@ -0,0 +1,21 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
basePath: '',
trailingSlash: true, // 添加这一行
images: {
unoptimized: true,
remotePatterns: [
{
protocol: 'https',
hostname: '**',
},
{
protocol: 'http',
hostname: 'localhost',
},
],
},
}
module.exports = nextConfig

12
next.config.ts Normal file
View File

@ -0,0 +1,12 @@
/*
* @LastEditors: whitechiina 1293616053@qq.com
* @LastEditTime: 2026-07-15 18:04:31
*/
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "export",
trailingSlash: true // 添加这一行
};
export default nextConfig;

BIN
out.zip Normal file

Binary file not shown.

11456
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

40
package.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "baima-home",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"embla-carousel-react": "^8.6.0",
"framer-motion": "^12.26.2",
"lucide-react": "^0.562.0",
"next": "16.1.1",
"react": "19.2.3",
"react-dom": "19.2.3",
"tailwind-merge": "^3.4.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"dotenv": "^17.2.3",
"eslint": "^9",
"eslint-config-next": "16.1.1",
"tailwindcss": "^4",
"tw-animate-css": "^1.4.0",
"typescript": "^5",
"vercel": "^50.4.4"
}
}

7
postcss.config.mjs Normal file
View File

@ -0,0 +1,7 @@
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;

BIN
public/baima-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

BIN
public/baima-qrcode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

1
public/file.svg Normal file
View File

@ -0,0 +1 @@
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 391 B

1
public/globe.svg Normal file
View File

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

8
public/grid.svg Normal file
View File

@ -0,0 +1,8 @@
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="white" stroke-width="0.5" stroke-opacity="0.3"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
</svg>

After

Width:  |  Height:  |  Size: 345 B

BIN
public/log.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
public/logos.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

1
public/next.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
public/vercel.svg Normal file
View File

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

1
public/window.svg Normal file
View File

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>

After

Width:  |  Height:  |  Size: 385 B

34
tsconfig.json Normal file
View File

@ -0,0 +1,34 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts"
],
"exclude": ["node_modules"]
}

5
vercel.json Normal file
View File

@ -0,0 +1,5 @@
{
"buildCommand": "npm run build",
"outputDirectory": ".next",
"framework": "nextjs"
}