diff --git a/.gitignore b/.gitignore index 5ef6a52..45254b6 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +/app/generated/prisma diff --git a/README.md b/README.md index e215bc4..d3dcc03 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,187 @@ -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). +# 南京市肥鹅信息技术有限公司 - Next.js 版本 -## Getting Started +这是南京市肥鹅信息技术有限公司官网的 Next.js 版本,包含完整的在线留言功能和 QQ 消息推送。 -First, run the development server: +## 功能特性 + +- ✅ 响应式设计,支持移动端和桌面端 +- ✅ 3D 动画背景(使用 Three.js 和 React Three Fiber) +- ✅ 在线留言表单,带表单验证 +- ✅ Prisma ORM 连接 PostgreSQL 数据库 +- ✅ QQ Bot 消息推送,实时通知管理员 +- ✅ Tailwind CSS 自定义主题配色 + +## 技术栈 + +- **框架**: Next.js 16 +- **UI 库**: React 19 +- **样式**: Tailwind CSS 4 +- **3D 渲染**: Three.js + React Three Fiber +- **数据库**: PostgreSQL + Prisma ORM +- **图标**: Lucide React +- **运行时**: Bun + +## 环境要求 + +- Node.js 18+ 或 Bun +- PostgreSQL 数据库 +- QQ Bot(可选,用于消息推送) + +## 安装和运行 + +### 1. 安装依赖 ```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +bun install +``` + +### 2. 配置环境变量 + +项目根目录已有 `.env` 文件,包含以下配置: + +```env +# 数据库连接 +DATABASE_URL="postgresql://feie9454:zjh94544549OK%3F@100.64.0.5:5432/feietech" + +# QQ Bot 配置 +QQ_BOT_URL=http://localhost:30000/send_private_msg +QQ_BOT_TARGET_ID=3258359726 +``` + +### 3. 初始化数据库 + +数据库已经通过 Prisma 迁移创建,如需重新初始化: + +```bash +# 运行数据库迁移 +bunx prisma migrate dev + +# 生成 Prisma Client +bunx prisma generate +``` + +### 4. 启动开发服务器 + +```bash +bun run 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. +### 5. 构建生产版本 -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. +```bash +bun run build +bun run start +``` + +## 数据库结构 + +### Message 表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Int | 主键,自增 | +| name | String | 留言人姓名 | +| phone | String | 联系电话 | +| email | String | 电子邮箱 | +| description | Text | 需求描述 | +| createdAt | DateTime | 创建时间 | + +## API 端点 + +### POST /api/messages + +提交在线留言 + +**请求体:** +```json +{ + "name": "张三", + "phone": "13800138000", + "email": "zhangsan@example.com", + "description": "需要定制软件开发服务" +} +``` + +**成功响应:** +```json +{ + "success": true, + "message": "留言提交成功,我们会尽快与您联系!", + "data": { + "id": 1 + } +} +``` + +**错误响应:** +```json +{ + "error": "错误信息" +} +``` + +## QQ 消息推送 + +当有新留言提交时,系统会自动通过 QQ Bot 发送消息通知管理员,包含以下信息: + +- 留言人姓名 +- 联系电话 +- 电子邮箱 +- 需求描述 +- 提交时间 + +## 项目结构 + +``` +nanjing-feie-info-tech-neo/ +├── app/ +│ ├── api/ +│ │ └── messages/ +│ │ └── route.ts # 留言 API 路由 +│ ├── globals.css # 全局样式 +│ ├── layout.tsx # 根布局 +│ └── page.tsx # 首页 +├── components/ +│ ├── About.tsx # 关于我们组件 +│ ├── Contact.tsx # 联系方式组件(含表单) +│ ├── Footer.tsx # 页脚组件 +│ ├── Hero.tsx # 首屏组件(含 3D 背景) +│ ├── Navbar.tsx # 导航栏组件 +│ └── Services.tsx # 服务介绍组件 +├── lib/ +│ ├── prisma.ts # Prisma 客户端 +│ └── qqbot.ts # QQ Bot 推送工具 +├── prisma/ +│ ├── schema.prisma # 数据库模型定义 +│ └── migrations/ # 数据库迁移文件 +├── public/ +│ └── assets/ # 静态资源 +├── types.ts # TypeScript 类型定义 +├── .env # 环境变量 +└── package.json # 项目配置 +``` + +## 开发说明 + +### 添加新的留言字段 + +1. 修改 `prisma/schema.prisma` 中的 `Message` 模型 +2. 运行 `bunx prisma migrate dev --name add_new_field` +3. 更新 `components/Contact.tsx` 表单 +4. 更新 `app/api/messages/route.ts` API 逻辑 + +### 自定义主题颜色 + +在 `app/globals.css` 中修改 `@theme` 块中的颜色变量: + +```css +--color-feie-white: #FFFFFF; +--color-feie-cream: #F5F4F0; +--color-feie-dark: #1C1917; +--color-feie-gold: #C5A059; +``` ## Learn More @@ -29,6 +192,11 @@ To learn more about Next.js, take a look at the following resources: You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +## 许可证 + +© 2025 南京市肥鹅信息技术有限公司. All rights reserved. + + ## 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. diff --git a/app/api/messages/route.ts b/app/api/messages/route.ts new file mode 100644 index 0000000..0a2cc84 --- /dev/null +++ b/app/api/messages/route.ts @@ -0,0 +1,77 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { prisma } from '@/lib/prisma'; +import { sendMsg } from '@/lib/qqbot'; + +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { name, phone, email, description } = body; + + // 验证必填字段 + if (!name || !phone || !email || !description) { + return NextResponse.json( + { error: '所有字段都是必填的' }, + { status: 400 } + ); + } + + // 验证邮箱格式 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + return NextResponse.json( + { error: '邮箱格式不正确' }, + { status: 400 } + ); + } + + // 验证手机号格式(中国手机号) + const phoneRegex = /^1[3-9]\d{9}$/; + if (!phoneRegex.test(phone)) { + return NextResponse.json( + { error: '手机号格式不正确' }, + { status: 400 } + ); + } + + // 保存到数据库 + const message = await prisma.message.create({ + data: { + name, + phone, + email, + description, + }, + }); + + // 发送 QQ 消息通知管理员 + const qqBotUrl = process.env.QQ_BOT_URL; + const qqBotTargetId = process.env.QQ_BOT_TARGET_ID; + + if (qqBotUrl && qqBotTargetId) { + const notificationMsg = `【新留言通知】\n姓名:${name}\n电话:${phone}\n邮箱:${email}\n需求描述:${description}\n时间:${new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}`; + + try { + await sendMsg(notificationMsg, qqBotTargetId, qqBotUrl); + } catch (qqError) { + console.error('QQ消息发送失败:', qqError); + // QQ消息发送失败不影响留言保存 + } + } + + return NextResponse.json( + { + success: true, + message: '留言提交成功,我们会尽快与您联系!', + data: { id: message.id } + }, + { status: 201 } + ); + + } catch (error) { + console.error('留言提交失败:', error); + return NextResponse.json( + { error: '服务器错误,请稍后再试' }, + { status: 500 } + ); + } +} diff --git a/app/globals.css b/app/globals.css index a2dc41e..5ea54c2 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,26 +1,38 @@ @import "tailwindcss"; :root { - --background: #ffffff; - --foreground: #171717; + --background: #F5F4F0; + --foreground: #1C1917; } @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } + --color-feie-white: #FFFFFF; + --color-feie-cream: #F5F4F0; + --color-feie-dark: #1C1917; + --color-feie-gold: #C5A059; + --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + --font-serif: "Playfair Display", "Noto Serif SC", serif; } body { background: var(--background); color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; + font-family: var(--font-sans); } + +html { + scroll-behavior: smooth; +} + +.antialiased { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.selection\:bg-feie-gold *::selection { + background-color: var(--color-feie-gold); + color: white; +} + diff --git a/app/layout.tsx b/app/layout.tsx index f7fa87e..207b82e 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,20 +1,25 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; +import { Playfair_Display, Noto_Serif_SC } from "next/font/google"; import "./globals.css"; +import Navbar from "@/components/Navbar"; +import Footer from "@/components/Footer"; -const geistSans = Geist({ - variable: "--font-geist-sans", +const playfairDisplay = Playfair_Display({ + variable: "--font-serif", subsets: ["latin"], + weight: ["400", "600", "700"], + style: ["normal", "italic"], }); -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", +const notoSerifSC = Noto_Serif_SC({ + variable: "--font-serif-sc", subsets: ["latin"], + weight: ["300", "400", "500", "700"], }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "南京市肥鹅信息技术有限公司 | Nanjing Feie Information Technology", + description: "南京市肥鹅信息技术有限公司致力于为企业提供卓越的数字化解决方案,以创新技术驱动商业价值。", }; export default function RootLayout({ @@ -23,12 +28,15 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + + {children} +