docs: 更新项目文档,完善需求和技术细节
This commit is contained in:
46
CHANGELOG.md
Normal file
46
CHANGELOG.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# 变更日志
|
||||
|
||||
爱鉴花项目遵循[语义化版本](https://semver.org/lang/zh-CN/)。
|
||||
|
||||
## [未发布]
|
||||
|
||||
### 新增
|
||||
- 初始化项目结构和文档
|
||||
- 创建微信小程序、后端服务、后台管理系统、官方网站四个子项目
|
||||
- 完善需求文档、详细设计文档、项目总览文档
|
||||
- 添加数据库配置和连接工具
|
||||
- 创建部署指南和贡献指南
|
||||
|
||||
### 变更
|
||||
- 优化项目文档结构和内容
|
||||
- 统一技术栈和开发规范
|
||||
- 完善开发环境配置说明
|
||||
|
||||
### 修复
|
||||
- 文档中的错误和遗漏信息
|
||||
- 开发环境配置问题
|
||||
|
||||
## 版本规范
|
||||
|
||||
### 版本格式
|
||||
`主版本号.次版本号.修订号`
|
||||
- **主版本号**: 不兼容的API修改
|
||||
- **次版本号**: 向下兼容的功能性新增
|
||||
- **修订号**: 向下兼容的问题修正
|
||||
|
||||
### 发布周期
|
||||
- 主版本号: 重大功能更新或架构调整
|
||||
- 次版本号: 每月或每季度发布
|
||||
- 修订号: 根据需要随时发布
|
||||
|
||||
## 版本历史
|
||||
|
||||
### v0.1.0 - 2024-01-01
|
||||
- 项目初始版本
|
||||
- 基础框架搭建完成
|
||||
- 核心文档编写完成
|
||||
- 开发环境配置就绪
|
||||
|
||||
---
|
||||
|
||||
*变更日志格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)*
|
||||
171
CONTRIBUTING.md
Normal file
171
CONTRIBUTING.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# 贡献指南
|
||||
|
||||
欢迎参与爱鉴花项目的开发!本文档将指导您如何为项目做出贡献。
|
||||
|
||||
## 🎯 开发流程
|
||||
|
||||
### 1. 环境准备
|
||||
- 确保已安装 Node.js 14.0.0+
|
||||
- 安装 Git 版本控制工具
|
||||
- 配置开发工具(HBuilderX、VSCode等)
|
||||
|
||||
### 2. 获取代码
|
||||
```bash
|
||||
git clone https://github.com/your-username/aijianhua.git
|
||||
cd aijianhua
|
||||
```
|
||||
|
||||
### 3. 分支管理
|
||||
我们使用 Git Flow 工作流:
|
||||
- `main` - 主分支,用于生产环境
|
||||
- `develop` - 开发分支,集成所有功能
|
||||
- `feature/*` - 功能开发分支
|
||||
- `bugfix/*` - bug修复分支
|
||||
- `release/*` - 发布分支
|
||||
|
||||
### 4. 开发步骤
|
||||
1. 从 `develop` 分支创建功能分支
|
||||
2. 在分支上开发新功能或修复bug
|
||||
3. 提交代码并推送到远程仓库
|
||||
4. 创建 Pull Request 到 `develop` 分支
|
||||
5. 代码审查通过后合并
|
||||
|
||||
## 📝 代码规范
|
||||
|
||||
### JavaScript/TypeScript
|
||||
- 使用 ESLint + Prettier 进行代码格式化
|
||||
- 遵循 Airbnb JavaScript 风格指南
|
||||
- 使用 async/await 代替回调函数
|
||||
- 添加必要的注释和文档
|
||||
|
||||
### Vue/uni-app
|
||||
- 使用 Composition API
|
||||
- 组件命名使用 PascalCase
|
||||
- Props 定义使用 TypeScript 类型
|
||||
- 使用 Vue Router 进行路由管理
|
||||
|
||||
### Node.js
|
||||
- 使用 Express.js 框架
|
||||
- 中间件使用统一的错误处理
|
||||
- API 响应格式标准化
|
||||
- 使用 Winston 进行日志记录
|
||||
|
||||
## 🔧 提交规范
|
||||
|
||||
### Commit Message 格式
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
<body>
|
||||
|
||||
<footer>
|
||||
```
|
||||
|
||||
### Type 类型
|
||||
- `feat`: 新功能
|
||||
- `fix`: bug修复
|
||||
- `docs`: 文档更新
|
||||
- `style`: 代码格式调整
|
||||
- `refactor`: 代码重构
|
||||
- `test`: 测试相关
|
||||
- `chore`: 构建过程或辅助工具变动
|
||||
|
||||
### 示例
|
||||
```
|
||||
feat(user): 添加用户注册功能
|
||||
|
||||
- 实现用户注册API接口
|
||||
- 添加手机号验证功能
|
||||
- 完善错误处理机制
|
||||
|
||||
Closes #123
|
||||
```
|
||||
|
||||
## 🧪 测试要求
|
||||
|
||||
### 单元测试
|
||||
- 后端API接口需要编写单元测试
|
||||
- 测试覆盖率要求达到80%以上
|
||||
- 使用 Jest 测试框架
|
||||
|
||||
### 集成测试
|
||||
- 主要业务流程需要集成测试
|
||||
- 测试数据库操作和API调用
|
||||
- 模拟各种边界情况
|
||||
|
||||
### E2E测试
|
||||
- 关键用户流程需要端到端测试
|
||||
- 使用 Cypress 或 Puppeteer
|
||||
- 覆盖主要业务场景
|
||||
|
||||
## 📖 文档要求
|
||||
|
||||
### 代码注释
|
||||
- 公共方法和复杂逻辑需要添加注释
|
||||
- 使用 JSDoc 格式注释
|
||||
- 注释说明参数、返回值、异常情况
|
||||
|
||||
### API文档
|
||||
- 使用 Swagger/OpenAPI 规范
|
||||
- 文档需要与代码同步更新
|
||||
- 包含请求示例和响应示例
|
||||
|
||||
### 项目文档
|
||||
- README 文件需要保持最新
|
||||
- 添加必要的配置说明
|
||||
- 更新部署和运维指南
|
||||
|
||||
## 🐛 Issue 报告
|
||||
|
||||
### Bug报告格式
|
||||
```
|
||||
## 问题描述
|
||||
清晰描述遇到的问题
|
||||
|
||||
## 重现步骤
|
||||
1. 第一步
|
||||
2. 第二步
|
||||
3. 第三步
|
||||
|
||||
## 预期行为
|
||||
期望的正常表现
|
||||
|
||||
## 实际行为
|
||||
实际发生的异常表现
|
||||
|
||||
## 环境信息
|
||||
- 操作系统:
|
||||
- 浏览器:
|
||||
- 版本:
|
||||
```
|
||||
|
||||
### 功能请求格式
|
||||
```
|
||||
## 功能描述
|
||||
详细描述请求的功能
|
||||
|
||||
## 使用场景
|
||||
在什么情况下需要这个功能
|
||||
|
||||
## 解决方案建议
|
||||
如果有的话,提供实现建议
|
||||
|
||||
## 附加信息
|
||||
任何其他相关信息
|
||||
```
|
||||
|
||||
## 🤝 行为准则
|
||||
|
||||
- 尊重其他贡献者
|
||||
- 建设性的代码审查
|
||||
- 及时响应问题和请求
|
||||
- 遵守开源协议
|
||||
|
||||
## 📞 联系方式
|
||||
|
||||
如有问题可以通过以下方式联系:
|
||||
- GitHub Issues
|
||||
- 项目邮箱: aijianhua@example.com
|
||||
- 开发者微信群
|
||||
|
||||
感谢您的贡献!🎉
|
||||
18
admin_website/.env.development
Normal file
18
admin_website/.env.development
Normal file
@@ -0,0 +1,18 @@
|
||||
# 爱鉴花后台管理系统 - 开发环境配置
|
||||
|
||||
# 应用配置
|
||||
VUE_APP_TITLE=爱鉴花后台管理系统
|
||||
VUE_APP_VERSION=1.0.0
|
||||
VUE_APP_PORT=3250
|
||||
|
||||
# API配置
|
||||
VUE_APP_API_BASE_URL=http://localhost:3200/api/v1
|
||||
VUE_APP_API_TIMEOUT=30000
|
||||
|
||||
# 功能开关
|
||||
VUE_APP_FEATURE_ANALYTICS=true
|
||||
VUE_APP_FEATURE_WEBSOCKET=false
|
||||
|
||||
# 开发工具配置
|
||||
VUE_APP_DEBUG=true
|
||||
VUE_APP_DEVTOOLS=true
|
||||
33
admin_website/package.json
Normal file
33
admin_website/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "aijianhua-admin",
|
||||
"version": "1.0.0",
|
||||
"description": "爱鉴花后台管理系统",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve --port 3250",
|
||||
"build": "vue-cli-service build",
|
||||
"dev": "vue-cli-service serve --port 3250",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4",
|
||||
"vuex": "^4.0.2",
|
||||
"element-plus": "^2.3.12",
|
||||
"axios": "^1.4.0",
|
||||
"@element-plus/icons-vue": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"@vue/eslint-config-standard": "^8.0.1",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"sass": "^1.64.1",
|
||||
"sass-loader": "^13.3.2"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
||||
51
admin_website/vue.config.js
Normal file
51
admin_website/vue.config.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const { defineConfig } = require('@vue/cli-service')
|
||||
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: true,
|
||||
|
||||
// 开发服务器配置
|
||||
devServer: {
|
||||
port: 3250,
|
||||
host: 'localhost',
|
||||
open: true,
|
||||
hot: true,
|
||||
compress: true,
|
||||
|
||||
// 代理配置,解决跨域问题
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:3200',
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
'^/api': '/api/v1'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 构建配置
|
||||
configureWebpack: {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': require('path').resolve(__dirname, 'src')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 生产环境配置
|
||||
productionSourceMap: false,
|
||||
|
||||
// CSS配置
|
||||
css: {
|
||||
extract: process.env.NODE_ENV === 'production',
|
||||
sourceMap: false,
|
||||
loaderOptions: {
|
||||
sass: {
|
||||
additionalData: `
|
||||
@import "@/styles/variables.scss";
|
||||
@import "@/styles/mixins.scss";
|
||||
`
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
46
backend/.env
Normal file
46
backend/.env
Normal file
@@ -0,0 +1,46 @@
|
||||
# 应用配置
|
||||
NODE_ENV=development
|
||||
PORT=3000
|
||||
JWT_SECRET=your-super-secret-jwt-key-change-in-production
|
||||
|
||||
# 数据库配置
|
||||
DB_HOST=129.211.213.226
|
||||
DB_PORT=9527
|
||||
DB_USER=root
|
||||
DB_PASSWORD=aiotAiot123!
|
||||
DB_NAME=ajhdata
|
||||
DB_CHARSET=utf8mb4
|
||||
|
||||
# 连接池配置
|
||||
DB_CONNECTION_LIMIT=10
|
||||
DB_QUEUE_LIMIT=0
|
||||
|
||||
# 文件上传配置
|
||||
MAX_FILE_SIZE=10485760
|
||||
UPLOAD_PATH=./uploads
|
||||
|
||||
# CORS配置
|
||||
CORS_ORIGIN=http://localhost:9000,http://127.0.0.1:9000
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL=info
|
||||
LOG_FILE=./logs/app.log
|
||||
|
||||
# Redis配置(可选)
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
|
||||
# 邮件配置(可选)
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your-email@gmail.com
|
||||
SMTP_PASS=your-app-password
|
||||
|
||||
# AI服务配置(花卉识别)
|
||||
AI_SERVICE_ENDPOINT=https://api.plant.id/v2/identify
|
||||
AI_SERVICE_KEY=your-plant-id-api-key
|
||||
|
||||
# 微信小程序配置
|
||||
WX_APPID=your-wechat-appid
|
||||
WX_SECRET=your-wechat-secret
|
||||
39
backend/.env.development
Normal file
39
backend/.env.development
Normal file
@@ -0,0 +1,39 @@
|
||||
# 环境配置文件 - 开发环境
|
||||
|
||||
# 应用配置
|
||||
NODE_ENV=development
|
||||
PORT=3200
|
||||
APP_NAME=爱鉴花小程序后端
|
||||
|
||||
# MySQL数据库配置
|
||||
DB_HOST=192.168.0.240
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=aiot$Aiot123
|
||||
DB_DATABASE=ajhdata
|
||||
|
||||
# Redis配置
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
|
||||
# 腾讯云配置
|
||||
TENCENT_CLOUD_SECRET_ID=your_secret_id
|
||||
TENCENT_CLOUD_SECRET_KEY=your_secret_key
|
||||
COS_BUCKET=your_bucket_name
|
||||
COS_REGION=ap-beijing
|
||||
|
||||
# 微信小程序配置
|
||||
WX_APPID=your_wechat_appid
|
||||
WX_APPSECRET=your_wechat_appsecret
|
||||
|
||||
# JWT配置
|
||||
JWT_SECRET=your_jwt_secret_key
|
||||
JWT_EXPIRE=7d
|
||||
|
||||
# 文件上传配置
|
||||
UPLOAD_MAX_SIZE=10485760
|
||||
UPLOAD_ALLOW_TYPES=image/jpeg,image/png,image/gif
|
||||
|
||||
# 跨域配置
|
||||
CORS_ORIGIN=http://localhost:8080
|
||||
40
backend/.env.example
Normal file
40
backend/.env.example
Normal file
@@ -0,0 +1,40 @@
|
||||
# 环境配置文件示例
|
||||
# 请复制为 .env.development 或 .env.production 并根据环境修改
|
||||
|
||||
# 应用配置
|
||||
NODE_ENV=development
|
||||
PORT=3000
|
||||
APP_NAME=爱鉴花小程序后端
|
||||
|
||||
# MySQL数据库配置
|
||||
DB_HOST=192.168.0.240
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=aiot$Aiot123
|
||||
DB_DATABASE=ajhdata
|
||||
|
||||
# Redis配置
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
|
||||
# 腾讯云配置
|
||||
TENCENT_CLOUD_SECRET_ID=your_secret_id
|
||||
TENCENT_CLOUD_SECRET_KEY=your_secret_key
|
||||
COS_BUCKET=your_bucket_name
|
||||
COS_REGION=ap-beijing
|
||||
|
||||
# 微信小程序配置
|
||||
WX_APPID=your_wechat_appid
|
||||
WX_APPSECRET=your_wechat_appsecret
|
||||
|
||||
# JWT配置
|
||||
JWT_SECRET=your_jwt_secret_key
|
||||
JWT_EXPIRE=7d
|
||||
|
||||
# 文件上传配置
|
||||
UPLOAD_MAX_SIZE=10485760
|
||||
UPLOAD_ALLOW_TYPES=image/jpeg,image/png,image/gif
|
||||
|
||||
# 跨域配置
|
||||
CORS_ORIGIN=http://localhost:8080
|
||||
275
backend/API接口文档.md
Normal file
275
backend/API接口文档.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# 爱鉴花小程序后端API接口文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档详细描述了爱鉴花小程序后端RESTful API接口规范,基于OpenAPI 3.0标准。
|
||||
|
||||
## 基础信息
|
||||
|
||||
- **基础URL**: `http://localhost:3000/api/v1`
|
||||
- **认证方式**: Bearer Token (JWT)
|
||||
- **数据格式**: JSON
|
||||
- **字符编码**: UTF-8
|
||||
|
||||
## 认证接口
|
||||
|
||||
### 用户注册
|
||||
|
||||
```http
|
||||
POST /auth/register
|
||||
```
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| username | string | 是 | 用户名 |
|
||||
| password | string | 是 | 密码(最少6位) |
|
||||
| phone | string | 是 | 手机号 |
|
||||
| email | string | 否 | 邮箱 |
|
||||
| user_type | string | 否 | 用户类型(farmer/buyer/admin) |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 201,
|
||||
"message": "注册成功",
|
||||
"data": {
|
||||
"user_id": 1,
|
||||
"username": "testuser",
|
||||
"phone": "13800138000",
|
||||
"email": "user@example.com",
|
||||
"user_type": "farmer",
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 用户登录
|
||||
|
||||
```http
|
||||
POST /auth/login
|
||||
```
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| login | string | 是 | 用户名/手机号/邮箱 |
|
||||
| password | string | 是 | 密码 |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "登录成功",
|
||||
"data": {
|
||||
"user_id": 1,
|
||||
"username": "testuser",
|
||||
"phone": "13800138000",
|
||||
"email": "user@example.com",
|
||||
"user_type": "farmer",
|
||||
"avatar_url": "/uploads/avatars/avatar.jpg",
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 用户接口
|
||||
|
||||
### 获取用户信息
|
||||
|
||||
```http
|
||||
GET /users/me
|
||||
```
|
||||
|
||||
**请求头**:
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"username": "testuser",
|
||||
"phone": "13800138000",
|
||||
"email": "user@example.com",
|
||||
"user_type": "farmer",
|
||||
"avatar_url": "/uploads/avatars/avatar.jpg",
|
||||
"created_at": "2023-01-01T00:00:00Z",
|
||||
"last_login": "2023-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 更新用户信息
|
||||
|
||||
```http
|
||||
PUT /users/{id}
|
||||
```
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| email | string | 否 | 邮箱 |
|
||||
| real_name | string | 否 | 真实姓名 |
|
||||
| avatar_url | string | 否 | 头像URL |
|
||||
|
||||
## 商品接口
|
||||
|
||||
### 获取商品列表
|
||||
|
||||
```http
|
||||
GET /products
|
||||
```
|
||||
|
||||
**查询参数**:
|
||||
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| page | integer | 页码(默认1) |
|
||||
| limit | integer | 每页数量(默认12) |
|
||||
| category_id | integer | 分类ID |
|
||||
| keyword | string | 搜索关键词 |
|
||||
| min_price | number | 最低价格 |
|
||||
| max_price | number | 最高价格 |
|
||||
| sort_by | string | 排序字段(name/price/created_at/stock) |
|
||||
| sort_order | string | 排序方向(asc/desc) |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取成功",
|
||||
"data": {
|
||||
"products": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "玫瑰花",
|
||||
"category_id": 1,
|
||||
"price": 29.9,
|
||||
"stock": 100,
|
||||
"image": "/uploads/products/rose.jpg",
|
||||
"description": "新鲜玫瑰花,香气浓郁",
|
||||
"category_name": "鲜花"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"limit": 12,
|
||||
"total": 50,
|
||||
"pages": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 获取商品详情
|
||||
|
||||
```http
|
||||
GET /products/{id}
|
||||
```
|
||||
|
||||
## 订单接口
|
||||
|
||||
### 创建订单
|
||||
|
||||
```http
|
||||
POST /orders
|
||||
```
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| items | array | 是 | 商品列表 |
|
||||
| shipping_address | string | 是 | 收货地址 |
|
||||
|
||||
**items数组结构**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"product_id": 1,
|
||||
"quantity": 2
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 花卉识别接口
|
||||
|
||||
### 花卉识别
|
||||
|
||||
```http
|
||||
POST /identifications/identify
|
||||
```
|
||||
|
||||
**请求格式**: `multipart/form-data`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| image | file | 是 | 花卉图片文件 |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "识别成功",
|
||||
"data": {
|
||||
"identification_id": 1,
|
||||
"image_url": "/uploads/identifications/identify-123.jpg",
|
||||
"results": [
|
||||
{
|
||||
"name": "玫瑰",
|
||||
"confidence": 0.95,
|
||||
"scientificName": "Rosa rugosa",
|
||||
"description": "玫瑰是蔷薇科蔷薇属的植物,具有浓郁的芳香和美丽的花朵。"
|
||||
}
|
||||
],
|
||||
"best_result": {
|
||||
"name": "玫瑰",
|
||||
"confidence": 0.95,
|
||||
"scientificName": "Rosa rugosa",
|
||||
"description": "玫瑰是蔷薇科蔷薇属的植物,具有浓郁的芳香和美丽的花朵。"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 获取识别历史
|
||||
|
||||
```http
|
||||
GET /identifications
|
||||
```
|
||||
|
||||
## 错误码说明
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 200 | 成功 |
|
||||
| 201 | 创建成功 |
|
||||
| 400 | 参数错误 |
|
||||
| 401 | 未授权 |
|
||||
| 403 | 禁止访问 |
|
||||
| 404 | 资源不存在 |
|
||||
| 409 | 资源冲突 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 安全要求
|
||||
|
||||
1. 所有敏感接口必须使用HTTPS
|
||||
2. JWT token有效期7天
|
||||
3. 密码使用bcrypt加密存储
|
||||
4. 文件上传限制10MB
|
||||
5. 支持CORS跨域访问
|
||||
|
||||
## 版本历史
|
||||
|
||||
- v1.0.0 (2024-01-01): 初始版本发布
|
||||
- 包含用户认证、商品管理、订单管理、花卉识别等核心功能
|
||||
124
backend/README_DATABASE.md
Normal file
124
backend/README_DATABASE.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# 数据库配置和使用指南
|
||||
|
||||
## 📋 数据库连接信息
|
||||
|
||||
### 测试环境
|
||||
- **主机**: 192.168.0.240
|
||||
- **端口**: 3306
|
||||
- **用户名**: root
|
||||
- **密码**: aiot$Aiot123
|
||||
- **数据库**: ajhdata
|
||||
|
||||
### 生产环境
|
||||
- **主机**: 129.211.213.226
|
||||
- **端口**: 9527
|
||||
- **用户名**: root
|
||||
- **密码**: aiotAiot123!
|
||||
- **数据库**: ajhdata
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 安装依赖
|
||||
```bash
|
||||
cd backend
|
||||
npm install
|
||||
```
|
||||
|
||||
### 2. 配置环境变量
|
||||
复制环境变量模板文件:
|
||||
```bash
|
||||
cp .env.example .env.development
|
||||
```
|
||||
|
||||
编辑 `.env.development` 文件,根据实际环境修改配置:
|
||||
```env
|
||||
NODE_ENV=development
|
||||
DB_HOST=192.168.0.240
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=aiot$Aiot123
|
||||
DB_DATABASE=ajhdata
|
||||
```
|
||||
|
||||
### 3. 验证数据库连接
|
||||
```bash
|
||||
# 完整初始化验证
|
||||
npm run db:init
|
||||
|
||||
# 只检查连接状态
|
||||
npm run db:check
|
||||
```
|
||||
|
||||
## 📁 配置文件说明
|
||||
|
||||
### `config/database.js`
|
||||
主数据库配置文件,根据环境变量自动选择配置:
|
||||
- 开发环境: `NODE_ENV=development`
|
||||
- 生产环境: `NODE_ENV=production`
|
||||
|
||||
### `.env.example`
|
||||
环境变量配置模板,包含所有可配置参数。
|
||||
|
||||
### `utils/dbConnector.js`
|
||||
数据库连接工具类,提供:
|
||||
- 连接池管理
|
||||
- SQL查询执行
|
||||
- 事务支持
|
||||
- 健康检查
|
||||
|
||||
### `scripts/initDatabase.js`
|
||||
数据库初始化脚本,功能:
|
||||
- 验证数据库连接
|
||||
- 检查数据库版本
|
||||
- 执行SQL文件(预留)
|
||||
|
||||
## 🔧 可用脚本命令
|
||||
|
||||
| 命令 | 描述 |
|
||||
|------|------|
|
||||
| `npm run db:init` | 完整数据库初始化验证 |
|
||||
| `npm run db:check` | 只检查数据库连接状态 |
|
||||
| `npm run db:migrate` | 执行数据库迁移(预留) |
|
||||
| `npm run db:seed` | 填充初始数据(预留) |
|
||||
|
||||
## 🛡️ 安全注意事项
|
||||
|
||||
1. **密码保护**: 数据库密码已配置在环境变量中,不要硬编码在代码里
|
||||
2. **连接池**: 使用连接池避免频繁创建连接
|
||||
3. **错误处理**: 所有数据库操作都有完整的错误处理
|
||||
4. **SQL注入**: 使用参数化查询防止SQL注入
|
||||
|
||||
## 📊 性能优化
|
||||
|
||||
- **连接池配置**: 最大20连接,最小5连接
|
||||
- **超时设置**: 获取连接超时60秒,空闲连接超时30秒
|
||||
- **字符编码**: UTF8MB4支持中文和emoji
|
||||
- **时区设置**: 东八区(+08:00)
|
||||
|
||||
## 🔍 故障排除
|
||||
|
||||
### 连接失败
|
||||
1. 检查网络是否能访问数据库服务器
|
||||
2. 验证用户名密码是否正确
|
||||
3. 确认数据库服务是否启动
|
||||
|
||||
### 权限问题
|
||||
1. 检查用户是否有数据库访问权限
|
||||
2. 确认数据库是否存在
|
||||
|
||||
### 性能问题
|
||||
1. 检查连接池配置是否合理
|
||||
2. 监控数据库服务器负载
|
||||
|
||||
## 📝 开发建议
|
||||
|
||||
1. 开发环境使用测试数据库配置
|
||||
2. 生产环境使用生产数据库配置
|
||||
3. 定期备份重要数据
|
||||
4. 使用事务保证数据一致性
|
||||
|
||||
## 🔗 相关文档
|
||||
|
||||
- [MySQL官方文档](https://dev.mysql.com/doc/)
|
||||
- [mysql2 npm包文档](https://www.npmjs.com/package/mysql2)
|
||||
- [连接池最佳实践](https://github.com/mysqljs/mysql#pooling-connections)
|
||||
108
backend/app.js
Normal file
108
backend/app.js
Normal file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* 爱鉴花后端服务主入口文件
|
||||
* 基于Express.js的RESTful API服务
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const helmet = require('helmet');
|
||||
const compression = require('compression');
|
||||
const morgan = require('morgan');
|
||||
const path = require('path');
|
||||
require('dotenv').config();
|
||||
|
||||
// Swagger配置
|
||||
const { specs, swaggerUi } = require('./config/swagger');
|
||||
|
||||
// 导入路由模块
|
||||
const authRoutes = require('./routes/auth');
|
||||
const userRoutes = require('./routes/users');
|
||||
const productRoutes = require('./routes/products');
|
||||
const orderRoutes = require('./routes/orders');
|
||||
const identificationRoutes = require('./routes/identifications');
|
||||
|
||||
// 导入中间件
|
||||
const { errorHandler } = require('./middlewares/errorHandler');
|
||||
const { authMiddleware } = require('./middlewares/auth');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// 安全中间件
|
||||
app.use(helmet());
|
||||
app.use(compression());
|
||||
|
||||
// CORS配置
|
||||
app.use(cors({
|
||||
origin: process.env.NODE_ENV === 'production'
|
||||
? ['https://your-domain.com']
|
||||
: ['http://localhost:9000', 'http://127.0.0.1:9000'],
|
||||
credentials: true
|
||||
}));
|
||||
|
||||
// 日志中间件
|
||||
app.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev'));
|
||||
|
||||
// 解析请求体
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
||||
|
||||
// 静态文件服务
|
||||
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
|
||||
|
||||
// 健康检查接口
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'OK',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: process.uptime(),
|
||||
environment: process.env.NODE_ENV || 'development'
|
||||
});
|
||||
});
|
||||
|
||||
// API文档
|
||||
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs, {
|
||||
explorer: true,
|
||||
customCss: '.swagger-ui .topbar { display: none }',
|
||||
customSiteTitle: '爱鉴花API文档'
|
||||
}));
|
||||
|
||||
// API路由配置
|
||||
app.use('/api/v1/auth', authRoutes);
|
||||
app.use('/api/v1/users', authMiddleware, userRoutes);
|
||||
app.use('/api/v1/products', productRoutes);
|
||||
app.use('/api/v1/orders', authMiddleware, orderRoutes);
|
||||
app.use('/api/v1/identifications', authMiddleware, identificationRoutes);
|
||||
|
||||
// 404处理
|
||||
app.use('*', (req, res) => {
|
||||
res.status(404).json({
|
||||
code: 404,
|
||||
message: '接口不存在',
|
||||
data: null,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
});
|
||||
|
||||
// 全局错误处理
|
||||
app.use(errorHandler);
|
||||
|
||||
// 启动服务
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 爱鉴花后端服务已启动`);
|
||||
console.log(`📍 环境: ${process.env.NODE_ENV || 'development'}`);
|
||||
console.log(`🌐 地址: http://localhost:${PORT}`);
|
||||
console.log(`📚 API文档: http://localhost:${PORT}/api-docs`);
|
||||
console.log(`❤️ 健康检查: http://localhost:${PORT}/health`);
|
||||
console.log('─'.repeat(50));
|
||||
});
|
||||
|
||||
// 优雅关闭
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n🛑 正在关闭服务...');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
47
backend/config/database.js
Normal file
47
backend/config/database.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 数据库配置文件
|
||||
* 包含测试环境和生产环境的MySQL连接配置
|
||||
*/
|
||||
|
||||
const databaseConfig = {
|
||||
// 测试环境配置
|
||||
development: {
|
||||
host: '192.168.0.240',
|
||||
port: 3306,
|
||||
username: 'root',
|
||||
password: 'aiot$Aiot123',
|
||||
database: 'ajhdata',
|
||||
dialect: 'mysql',
|
||||
logging: console.log,
|
||||
pool: {
|
||||
max: 20,
|
||||
min: 5,
|
||||
acquire: 30000,
|
||||
idle: 10000
|
||||
}
|
||||
},
|
||||
|
||||
// 生产环境配置
|
||||
production: {
|
||||
host: '129.211.213.226',
|
||||
port: 9527,
|
||||
username: 'root',
|
||||
password: 'aiotAiot123!',
|
||||
database: 'ajhdata',
|
||||
dialect: 'mysql',
|
||||
logging: false, // 生产环境关闭SQL日志
|
||||
pool: {
|
||||
max: 200,
|
||||
min: 20,
|
||||
acquire: 30000,
|
||||
idle: 30000
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 根据环境变量选择配置
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
module.exports = databaseConfig[env];
|
||||
|
||||
// 导出完整配置对象(用于其他需要访问所有配置的场景)
|
||||
module.exports.allConfigs = databaseConfig;
|
||||
153
backend/config/swagger.js
Normal file
153
backend/config/swagger.js
Normal file
@@ -0,0 +1,153 @@
|
||||
const swaggerJsdoc = require('swagger-jsdoc');
|
||||
const swaggerUi = require('swagger-ui-express');
|
||||
|
||||
const options = {
|
||||
definition: {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: '爱鉴花小程序后端API',
|
||||
version: '1.0.0',
|
||||
description: '爱鉴花小程序后端RESTful API文档',
|
||||
contact: {
|
||||
name: 'API支持',
|
||||
email: 'support@aijianhua.com'
|
||||
},
|
||||
license: {
|
||||
name: 'MIT',
|
||||
url: 'https://opensource.org/licenses/MIT'
|
||||
}
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: process.env.NODE_ENV === 'production'
|
||||
? 'https://api.aijianhua.com'
|
||||
: `http://localhost:${process.env.PORT || 3000}`,
|
||||
description: process.env.NODE_ENV === 'production' ? '生产环境' : '开发环境'
|
||||
}
|
||||
],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
BearerAuth: {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
bearerFormat: 'JWT'
|
||||
}
|
||||
},
|
||||
schemas: {
|
||||
User: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer', example: 1 },
|
||||
username: { type: 'string', example: 'testuser' },
|
||||
phone: { type: 'string', example: '13800138000' },
|
||||
email: { type: 'string', example: 'user@example.com' },
|
||||
user_type: { type: 'string', enum: ['farmer', 'buyer', 'admin'], example: 'farmer' },
|
||||
avatar_url: { type: 'string', example: '/uploads/avatars/avatar.jpg' },
|
||||
created_at: { type: 'string', format: 'date-time', example: '2023-01-01T00:00:00Z' },
|
||||
last_login: { type: 'string', format: 'date-time', example: '2023-01-01T00:00:00Z' }
|
||||
}
|
||||
},
|
||||
Product: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer', example: 1 },
|
||||
name: { type: 'string', example: '玫瑰花' },
|
||||
category_id: { type: 'integer', example: 1 },
|
||||
price: { type: 'number', format: 'float', example: 29.9 },
|
||||
stock: { type: 'integer', example: 100 },
|
||||
image: { type: 'string', example: '/uploads/products/rose.jpg' },
|
||||
description: { type: 'string', example: '新鲜玫瑰花,香气浓郁' },
|
||||
status: { type: 'integer', enum: [0, 1], example: 1 },
|
||||
created_at: { type: 'string', format: 'date-time', example: '2023-01-01T00:00:00Z' },
|
||||
updated_at: { type: 'string', format: 'date-time', example: '2023-01-01T00:00:00Z' }
|
||||
}
|
||||
},
|
||||
Order: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer', example: 1 },
|
||||
order_number: { type: 'string', example: 'O123456789' },
|
||||
user_id: { type: 'integer', example: 1 },
|
||||
total_amount: { type: 'number', format: 'float', example: 99.9 },
|
||||
payment_status: { type: 'string', enum: ['pending', 'paid', 'cancelled'], example: 'pending' },
|
||||
shipping_status: { type: 'string', enum: ['pending', 'shipped', 'delivered'], example: 'pending' },
|
||||
shipping_address: { type: 'string', example: '北京市朝阳区xxx街道' },
|
||||
created_at: { type: 'string', format: 'date-time', example: '2023-01-01T00:00:00Z' },
|
||||
updated_at: { type: 'string', format: 'date-time', example: '2023-01-01T00:00:00Z' }
|
||||
}
|
||||
},
|
||||
Identification: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer', example: 1 },
|
||||
user_id: { type: 'integer', example: 1 },
|
||||
image_url: { type: 'string', example: '/uploads/identifications/identify-123.jpg' },
|
||||
result: { type: 'string', description: 'JSON格式的识别结果' },
|
||||
confidence: { type: 'number', format: 'float', example: 0.95 },
|
||||
created_at: { type: 'string', format: 'date-time', example: '2023-01-01T00:00:00Z' }
|
||||
}
|
||||
},
|
||||
Error: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
code: { type: 'integer', example: 400 },
|
||||
message: { type: 'string', example: '参数验证失败' },
|
||||
data: { type: 'object', nullable: true, example: null }
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
UnauthorizedError: {
|
||||
description: '未授权访问',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/Error' },
|
||||
example: {
|
||||
code: 401,
|
||||
message: '未提供有效的认证token',
|
||||
data: null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
NotFoundError: {
|
||||
description: '资源不存在',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/Error' },
|
||||
example: {
|
||||
code: 404,
|
||||
message: '资源不存在',
|
||||
data: null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ValidationError: {
|
||||
description: '参数验证失败',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/Error' },
|
||||
example: {
|
||||
code: 400,
|
||||
message: '参数验证失败',
|
||||
data: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
security: [
|
||||
{ BearerAuth: [] }
|
||||
]
|
||||
},
|
||||
apis: [
|
||||
'./routes/*.js',
|
||||
'./middlewares/*.js'
|
||||
]
|
||||
};
|
||||
|
||||
const specs = swaggerJsdoc(options);
|
||||
|
||||
module.exports = { specs, swaggerUi };
|
||||
165
backend/middlewares/auth.js
Normal file
165
backend/middlewares/auth.js
Normal file
@@ -0,0 +1,165 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const dbConnector = require('../utils/dbConnector');
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
||||
|
||||
/**
|
||||
* JWT认证中间件
|
||||
* 验证token并附加用户信息到req对象
|
||||
*/
|
||||
const authMiddleware = async (req, res, next) => {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '未提供有效的认证token',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7); // 移除'Bearer '前缀
|
||||
|
||||
try {
|
||||
// 验证token
|
||||
const decoded = jwt.verify(token, JWT_SECRET);
|
||||
|
||||
// 查询用户信息
|
||||
const users = await dbConnector.query(
|
||||
'SELECT id, username, phone, email, user_type, avatar_url, status FROM users WHERE id = ?',
|
||||
[decoded.userId]
|
||||
);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '用户不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
const user = users[0];
|
||||
|
||||
// 检查用户状态
|
||||
if (user.status !== 1) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '用户已被禁用',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 附加用户信息到请求对象
|
||||
req.user = {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
phone: user.phone,
|
||||
email: user.email,
|
||||
user_type: user.user_type,
|
||||
avatar_url: user.avatar_url
|
||||
};
|
||||
|
||||
next();
|
||||
|
||||
} catch (jwtError) {
|
||||
if (jwtError.name === 'TokenExpiredError') {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: 'token已过期',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (jwtError.name === 'JsonWebTokenError') {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '无效的token',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
throw jwtError;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('认证中间件错误:', error);
|
||||
return res.status(500).json({
|
||||
code: 500,
|
||||
message: '服务器内部错误',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 可选认证中间件
|
||||
* 如果提供了token就验证,不提供也不报错
|
||||
*/
|
||||
const optionalAuth = async (req, res, next) => {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (authHeader && authHeader.startsWith('Bearer ')) {
|
||||
const token = authHeader.substring(7);
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, JWT_SECRET);
|
||||
|
||||
const users = await dbConnector.query(
|
||||
'SELECT id, username, phone, email, user_type, avatar_url, status FROM users WHERE id = ? AND status = 1',
|
||||
[decoded.userId]
|
||||
);
|
||||
|
||||
if (users.length > 0) {
|
||||
const user = users[0];
|
||||
req.user = {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
phone: user.phone,
|
||||
email: user.email,
|
||||
user_type: user.user_type,
|
||||
avatar_url: user.avatar_url
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
// token无效,但不阻止请求继续
|
||||
console.warn('可选认证失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('可选认证中间件错误:', error);
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 管理员权限检查中间件
|
||||
*/
|
||||
const adminRequired = (req, res, next) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '需要登录',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (req.user.user_type !== 'admin') {
|
||||
return res.status(403).json({
|
||||
code: 403,
|
||||
message: '需要管理员权限',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
authMiddleware,
|
||||
optionalAuth,
|
||||
adminRequired
|
||||
};
|
||||
124
backend/middlewares/errorHandler.js
Normal file
124
backend/middlewares/errorHandler.js
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 全局错误处理中间件
|
||||
* 统一处理应用中的各种错误
|
||||
*/
|
||||
|
||||
const errorHandler = (err, req, res, next) => {
|
||||
console.error('错误详情:', {
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
url: req.url,
|
||||
method: req.method,
|
||||
body: req.body,
|
||||
params: req.params,
|
||||
query: req.query,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// 数据库错误
|
||||
if (err.code === 'ER_DUP_ENTRY') {
|
||||
return res.status(409).json({
|
||||
code: 409,
|
||||
message: '数据已存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (err.code === 'ER_NO_REFERENCED_ROW_2') {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '关联数据不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (err.code === 'ER_DATA_TOO_LONG') {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '数据长度超过限制',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// JWT错误
|
||||
if (err.name === 'JsonWebTokenError') {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '无效的token',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (err.name === 'TokenExpiredError') {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: 'token已过期',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 验证错误
|
||||
if (err.name === 'ValidationError') {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: err.message || '参数验证失败',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 文件上传错误
|
||||
if (err.code === 'LIMIT_FILE_SIZE') {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '文件大小超过限制',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (err.code === 'LIMIT_UNEXPECTED_FILE') {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '不支持的文件类型',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 默认错误处理
|
||||
const statusCode = err.statusCode || err.status || 500;
|
||||
const message = process.env.NODE_ENV === 'production'
|
||||
? '服务器内部错误'
|
||||
: err.message || '服务器内部错误';
|
||||
|
||||
res.status(statusCode).json({
|
||||
code: statusCode,
|
||||
message,
|
||||
data: null,
|
||||
...(process.env.NODE_ENV !== 'production' && { stack: err.stack })
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 异步错误处理包装器
|
||||
* 用于包装异步路由处理函数,自动捕获错误
|
||||
*/
|
||||
const asyncHandler = (fn) => (req, res, next) => {
|
||||
Promise.resolve(fn(req, res, next)).catch(next);
|
||||
};
|
||||
|
||||
/**
|
||||
* 404错误处理中间件
|
||||
*/
|
||||
const notFoundHandler = (req, res) => {
|
||||
res.status(404).json({
|
||||
code: 404,
|
||||
message: '接口不存在',
|
||||
data: null,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
errorHandler,
|
||||
asyncHandler,
|
||||
notFoundHandler
|
||||
};
|
||||
6642
backend/package-lock.json
generated
Normal file
6642
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
53
backend/package.json
Normal file
53
backend/package.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "aijianhua-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "爱鉴花小程序后端API服务",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"dev": "nodemon app.js",
|
||||
"test": "jest",
|
||||
"lint": "eslint .",
|
||||
"db:init": "node scripts/initDatabase.js",
|
||||
"db:check": "node scripts/initDatabase.js --check",
|
||||
"db:migrate": "node scripts/migrate.js",
|
||||
"db:seed": "node scripts/seedData.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"mysql2": "^3.6.0",
|
||||
"cors": "^2.8.5",
|
||||
"helmet": "^7.0.0",
|
||||
"compression": "^1.7.4",
|
||||
"morgan": "^1.10.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"validator": "^13.11.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"redis": "^4.6.8",
|
||||
"socket.io": "^4.7.2",
|
||||
"swagger-ui-express": "^4.6.3",
|
||||
"yamljs": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1",
|
||||
"jest": "^29.6.2",
|
||||
"eslint": "^8.47.0",
|
||||
"prettier": "^3.0.2",
|
||||
"@types/node": "^20.5.0",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"keywords": [
|
||||
"wechat",
|
||||
"mini-program",
|
||||
"plant-identification",
|
||||
"ecommerce",
|
||||
"api"
|
||||
],
|
||||
"author": "爱鉴花开发团队",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
}
|
||||
218
backend/routes/auth.js
Normal file
218
backend/routes/auth.js
Normal file
@@ -0,0 +1,218 @@
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const validator = require('validator');
|
||||
const dbConnector = require('../utils/dbConnector');
|
||||
|
||||
const router = express.Router();
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
router.post('/register', async (req, res, next) => {
|
||||
try {
|
||||
const { username, password, phone, email, user_type = 'farmer' } = req.body;
|
||||
|
||||
// 参数验证
|
||||
if (!username || !password || !phone) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '用户名、密码和手机号为必填项',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '密码长度不能少于6位',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (!validator.isMobilePhone(phone, 'zh-CN')) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '手机号格式不正确',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (email && !validator.isEmail(email)) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '邮箱格式不正确',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 检查用户是否已存在
|
||||
const existingUser = await dbConnector.query(
|
||||
'SELECT id FROM users WHERE username = ? OR phone = ? OR email = ?',
|
||||
[username, phone, email]
|
||||
);
|
||||
|
||||
if (existingUser.length > 0) {
|
||||
return res.status(409).json({
|
||||
code: 409,
|
||||
message: '用户名、手机号或邮箱已存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 加密密码
|
||||
const hashedPassword = await bcrypt.hash(password, 12);
|
||||
|
||||
// 创建用户
|
||||
const result = await dbConnector.query(
|
||||
'INSERT INTO users (username, password_hash, phone, email, user_type) VALUES (?, ?, ?, ?, ?)',
|
||||
[username, hashedPassword, phone, email, user_type]
|
||||
);
|
||||
|
||||
// 生成JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: result.insertId, username, user_type },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
code: 201,
|
||||
message: '注册成功',
|
||||
data: {
|
||||
user_id: result.insertId,
|
||||
username,
|
||||
phone,
|
||||
email,
|
||||
user_type,
|
||||
token
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
router.post('/login', async (req, res, next) => {
|
||||
try {
|
||||
const { login, password } = req.body;
|
||||
|
||||
if (!login || !password) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '登录账号和密码为必填项',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 查询用户(支持用户名、手机号、邮箱登录)
|
||||
const user = await dbConnector.query(
|
||||
'SELECT * FROM users WHERE (username = ? OR phone = ? OR email = ?) AND status = 1',
|
||||
[login, login, login]
|
||||
);
|
||||
|
||||
if (user.length === 0) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '用户不存在或已被禁用',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
const userData = user[0];
|
||||
|
||||
// 验证密码
|
||||
const isValidPassword = await bcrypt.compare(password, userData.password_hash);
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '密码不正确',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 更新最后登录时间
|
||||
await dbConnector.query(
|
||||
'UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[userData.id]
|
||||
);
|
||||
|
||||
// 生成JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: userData.id, username: userData.username, user_type: userData.user_type },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '登录成功',
|
||||
data: {
|
||||
user_id: userData.id,
|
||||
username: userData.username,
|
||||
phone: userData.phone,
|
||||
email: userData.email,
|
||||
user_type: userData.user_type,
|
||||
avatar_url: userData.avatar_url,
|
||||
token
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
router.get('/me', async (req, res, next) => {
|
||||
try {
|
||||
// 从token中获取用户ID
|
||||
const token = req.headers.authorization?.replace('Bearer ', '');
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '未提供认证token',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
const decoded = jwt.verify(token, JWT_SECRET);
|
||||
const user = await dbConnector.query(
|
||||
'SELECT id, username, phone, email, user_type, avatar_url, created_at, last_login FROM users WHERE id = ? AND status = 1',
|
||||
[decoded.userId]
|
||||
);
|
||||
|
||||
if (user.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '用户不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: user[0]
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
if (error.name === 'JsonWebTokenError') {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '无效的token',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
355
backend/routes/identifications.js
Normal file
355
backend/routes/identifications.js
Normal file
@@ -0,0 +1,355 @@
|
||||
const express = require('express');
|
||||
const multer = require('multer');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const dbConnector = require('../utils/dbConnector');
|
||||
const { asyncHandler } = require('../middlewares/errorHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 配置multer用于文件上传
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
const uploadDir = path.join(__dirname, '../uploads/identifications');
|
||||
|
||||
// 确保上传目录存在
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true });
|
||||
}
|
||||
|
||||
cb(null, uploadDir);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
cb(null, 'identification-' + uniqueSuffix + path.extname(file.originalname));
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: {
|
||||
fileSize: 10 * 1024 * 1024, // 10MB限制
|
||||
},
|
||||
fileFilter: (req, file, cb) => {
|
||||
// 只允许图片文件
|
||||
if (file.mimetype.startsWith('image/')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('只支持图片文件'), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取识别历史记录
|
||||
*/
|
||||
router.get('/', asyncHandler(async (req, res) => {
|
||||
const { page = 1, limit = 10 } = req.query;
|
||||
const userId = req.user.id;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// 查询识别记录
|
||||
const identifications = await dbConnector.query(
|
||||
`SELECT
|
||||
id, user_id, image_url, result, confidence, created_at
|
||||
FROM identifications
|
||||
WHERE user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[userId, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
// 查询总数
|
||||
const totalResult = await dbConnector.query(
|
||||
'SELECT COUNT(*) as total FROM identifications WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
identifications,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: totalResult[0].total,
|
||||
pages: Math.ceil(totalResult[0].total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取单条识别记录详情
|
||||
*/
|
||||
router.get('/:id', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
const identifications = await dbConnector.query(
|
||||
`SELECT
|
||||
id, user_id, image_url, result, confidence, created_at
|
||||
FROM identifications
|
||||
WHERE id = ? AND user_id = ?`,
|
||||
[id, userId]
|
||||
);
|
||||
|
||||
if (identifications.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '识别记录不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: identifications[0]
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 花卉识别接口
|
||||
*/
|
||||
router.post('/identify', upload.single('image'), asyncHandler(async (req, res) => {
|
||||
const userId = req.user.id;
|
||||
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '请上传图片文件',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 这里应该调用AI识别服务
|
||||
// 由于AI识别服务需要额外配置,这里先模拟识别结果
|
||||
|
||||
const imageUrl = `/uploads/identifications/${req.file.filename}`;
|
||||
|
||||
// 模拟AI识别结果(实际项目中应该调用真实的AI服务)
|
||||
const mockResults = [
|
||||
{ name: '玫瑰', confidence: 0.95, scientificName: 'Rosa rugosa', description: '玫瑰是蔷薇科蔷薇属的植物,具有浓郁的芳香和美丽的花朵。' },
|
||||
{ name: '百合', confidence: 0.87, scientificName: 'Lilium brownii', description: '百合是百合科百合属的植物,象征纯洁和高雅。' },
|
||||
{ name: '菊花', confidence: 0.82, scientificName: 'Chrysanthemum morifolium', description: '菊花是菊科菊属的植物,具有很高的观赏和药用价值。' }
|
||||
];
|
||||
|
||||
// 选择置信度最高的结果
|
||||
const bestResult = mockResults[0];
|
||||
|
||||
// 保存识别记录到数据库
|
||||
const result = await dbConnector.query(
|
||||
'INSERT INTO identifications (user_id, image_url, result, confidence) VALUES (?, ?, ?, ?)',
|
||||
[userId, imageUrl, JSON.stringify(mockResults), bestResult.confidence]
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '识别成功',
|
||||
data: {
|
||||
identification_id: result.insertId,
|
||||
image_url: imageUrl,
|
||||
results: mockResults,
|
||||
best_result: bestResult,
|
||||
created_at: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 批量识别历史记录
|
||||
*/
|
||||
router.get('/batch/history', asyncHandler(async (req, res) => {
|
||||
const { start_date, end_date, min_confidence = 0.7 } = req.query;
|
||||
const userId = req.user.id;
|
||||
|
||||
let whereClause = 'WHERE user_id = ? AND confidence >= ?';
|
||||
let queryParams = [userId, parseFloat(min_confidence)];
|
||||
|
||||
if (start_date) {
|
||||
whereClause += ' AND created_at >= ?';
|
||||
queryParams.push(start_date);
|
||||
}
|
||||
|
||||
if (end_date) {
|
||||
whereClause += ' AND created_at <= ?';
|
||||
queryParams.push(end_date + ' 23:59:59');
|
||||
}
|
||||
|
||||
const identifications = await dbConnector.query(
|
||||
`SELECT
|
||||
id, image_url, result, confidence, created_at,
|
||||
DATE(created_at) as identify_date
|
||||
FROM identifications
|
||||
${whereClause}
|
||||
ORDER BY created_at DESC`,
|
||||
queryParams
|
||||
);
|
||||
|
||||
// 按日期分组
|
||||
const groupedByDate = {};
|
||||
identifications.forEach(record => {
|
||||
const date = record.identify_date;
|
||||
if (!groupedByDate[date]) {
|
||||
groupedByDate[date] = [];
|
||||
}
|
||||
groupedByDate[date].push(record);
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
total: identifications.length,
|
||||
by_date: groupedByDate,
|
||||
statistics: {
|
||||
total_count: identifications.length,
|
||||
avg_confidence: identifications.reduce((sum, item) => sum + item.confidence, 0) / identifications.length,
|
||||
date_range: {
|
||||
start: identifications.length > 0 ? identifications[identifications.length - 1].identify_date : null,
|
||||
end: identifications.length > 0 ? identifications[0].identify_date : null
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 识别统计信息
|
||||
*/
|
||||
router.get('/stats/summary', asyncHandler(async (req, res) => {
|
||||
const userId = req.user.id;
|
||||
|
||||
// 总识别次数
|
||||
const totalCountResult = await dbConnector.query(
|
||||
'SELECT COUNT(*) as total FROM identifications WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
// 平均置信度
|
||||
const avgConfidenceResult = await dbConnector.query(
|
||||
'SELECT AVG(confidence) as avg_confidence FROM identifications WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
// 最近识别时间
|
||||
const lastIdentificationResult = await dbConnector.query(
|
||||
'SELECT created_at FROM identifications WHERE user_id = ? ORDER BY created_at DESC LIMIT 1',
|
||||
[userId]
|
||||
);
|
||||
|
||||
// 识别最多的花卉类型(需要解析result字段)
|
||||
const allIdentifications = await dbConnector.query(
|
||||
'SELECT result FROM identifications WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
const flowerCounts = {};
|
||||
allIdentifications.forEach(item => {
|
||||
try {
|
||||
const results = JSON.parse(item.result);
|
||||
if (results && results.length > 0) {
|
||||
const bestResult = results[0];
|
||||
flowerCounts[bestResult.name] = (flowerCounts[bestResult.name] || 0) + 1;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析识别结果失败:', e);
|
||||
}
|
||||
});
|
||||
|
||||
// 找出识别最多的花卉
|
||||
let mostIdentifiedFlower = null;
|
||||
let maxCount = 0;
|
||||
for (const [flower, count] of Object.entries(flowerCounts)) {
|
||||
if (count > maxCount) {
|
||||
mostIdentifiedFlower = flower;
|
||||
maxCount = count;
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
total_count: totalCountResult[0].total,
|
||||
avg_confidence: avgConfidenceResult[0].avg_confidence || 0,
|
||||
last_identification: lastIdentificationResult[0] ? lastIdentificationResult[0].created_at : null,
|
||||
most_identified_flower: mostIdentifiedFlower,
|
||||
flower_counts: flowerCounts,
|
||||
weekly_trend: await getWeeklyTrend(userId)
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取周趋势数据
|
||||
*/
|
||||
async function getWeeklyTrend(userId) {
|
||||
const weeklyData = await dbConnector.query(
|
||||
`SELECT
|
||||
DATE(created_at) as date,
|
||||
COUNT(*) as count,
|
||||
AVG(confidence) as avg_confidence
|
||||
FROM identifications
|
||||
WHERE user_id = ? AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date DESC`,
|
||||
[userId]
|
||||
);
|
||||
|
||||
return weeklyData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除识别记录
|
||||
*/
|
||||
router.delete('/:id', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
// 先获取记录信息以删除图片文件
|
||||
const identification = await dbConnector.query(
|
||||
'SELECT image_url FROM identifications WHERE id = ? AND user_id = ?',
|
||||
[id, userId]
|
||||
);
|
||||
|
||||
if (identification.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '识别记录不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 删除图片文件
|
||||
if (identification[0].image_url) {
|
||||
const imagePath = path.join(__dirname, '../', identification[0].image_url);
|
||||
if (fs.existsSync(imagePath)) {
|
||||
fs.unlinkSync(imagePath);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除数据库记录
|
||||
const result = await dbConnector.query(
|
||||
'DELETE FROM identifications WHERE id = ? AND user_id = ?',
|
||||
[id, userId]
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '识别记录不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '删除成功',
|
||||
data: null
|
||||
});
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
379
backend/routes/orders.js
Normal file
379
backend/routes/orders.js
Normal file
@@ -0,0 +1,379 @@
|
||||
const express = require('express');
|
||||
const dbConnector = require('../utils/dbConnector');
|
||||
const { asyncHandler } = require('../middlewares/errorHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* 获取订单列表
|
||||
*/
|
||||
router.get('/', asyncHandler(async (req, res) => {
|
||||
const { page = 1, limit = 10, status, start_date, end_date } = req.query;
|
||||
const userId = req.user.id;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
let whereClause = 'WHERE o.user_id = ?';
|
||||
let queryParams = [userId];
|
||||
|
||||
if (status) {
|
||||
whereClause += ' AND o.payment_status = ?';
|
||||
queryParams.push(status);
|
||||
}
|
||||
|
||||
if (start_date) {
|
||||
whereClause += ' AND o.created_at >= ?';
|
||||
queryParams.push(start_date);
|
||||
}
|
||||
|
||||
if (end_date) {
|
||||
whereClause += ' AND o.created_at <= ?';
|
||||
queryParams.push(end_date + ' 23:59:59');
|
||||
}
|
||||
|
||||
// 查询订单列表
|
||||
const orders = await dbConnector.query(
|
||||
`SELECT
|
||||
o.id, o.order_number, o.user_id, o.total_amount, o.payment_status,
|
||||
o.shipping_status, o.shipping_address, o.created_at, o.updated_at,
|
||||
u.username, u.phone
|
||||
FROM orders o
|
||||
LEFT JOIN users u ON o.user_id = u.id
|
||||
${whereClause}
|
||||
ORDER BY o.created_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...queryParams, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
// 查询总数
|
||||
const totalResult = await dbConnector.query(
|
||||
`SELECT COUNT(*) as total
|
||||
FROM orders o
|
||||
${whereClause}`,
|
||||
queryParams
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
orders,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: totalResult[0].total,
|
||||
pages: Math.ceil(totalResult[0].total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取订单详情
|
||||
*/
|
||||
router.get('/:id', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
// 查询订单基本信息
|
||||
const orders = await dbConnector.query(
|
||||
`SELECT
|
||||
o.id, o.order_number, o.user_id, o.total_amount, o.payment_status,
|
||||
o.shipping_status, o.shipping_address, o.created_at, o.updated_at,
|
||||
u.username, u.phone, u.email
|
||||
FROM orders o
|
||||
LEFT JOIN users u ON o.user_id = u.id
|
||||
WHERE o.id = ? AND o.user_id = ?`,
|
||||
[id, userId]
|
||||
);
|
||||
|
||||
if (orders.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '订单不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
const order = orders[0];
|
||||
|
||||
// 查询订单商品明细
|
||||
const orderItems = await dbConnector.query(
|
||||
`SELECT
|
||||
oi.id, oi.order_id, oi.product_id, oi.quantity, oi.unit_price,
|
||||
p.name as product_name, p.image as product_image
|
||||
FROM order_items oi
|
||||
LEFT JOIN products p ON oi.product_id = p.id
|
||||
WHERE oi.order_id = ?`,
|
||||
[id]
|
||||
);
|
||||
|
||||
order.items = orderItems;
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: order
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
*/
|
||||
router.post('/', asyncHandler(async (req, res) => {
|
||||
const { items, shipping_address } = req.body;
|
||||
const userId = req.user.id;
|
||||
|
||||
// 参数验证
|
||||
if (!items || !Array.isArray(items) || items.length === 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '订单商品不能为空',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (!shipping_address) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '收货地址不能为空',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
let connection;
|
||||
try {
|
||||
connection = await dbConnector.getConnection();
|
||||
await connection.beginTransaction();
|
||||
|
||||
// 验证商品库存和价格
|
||||
let totalAmount = 0;
|
||||
const productUpdates = [];
|
||||
|
||||
for (const item of items) {
|
||||
const { product_id, quantity } = item;
|
||||
|
||||
if (!product_id || !quantity || quantity <= 0) {
|
||||
throw { code: 400, message: '商品ID和数量必须为正数' };
|
||||
}
|
||||
|
||||
// 查询商品信息
|
||||
const products = await connection.query(
|
||||
'SELECT id, name, price, stock FROM products WHERE id = ? AND status = 1',
|
||||
[product_id]
|
||||
);
|
||||
|
||||
if (products.length === 0) {
|
||||
throw { code: 404, message: `商品ID ${product_id} 不存在` };
|
||||
}
|
||||
|
||||
const product = products[0];
|
||||
|
||||
// 检查库存
|
||||
if (product.stock < quantity) {
|
||||
throw { code: 400, message: `商品 ${product.name} 库存不足` };
|
||||
}
|
||||
|
||||
const itemTotal = product.price * quantity;
|
||||
totalAmount += itemTotal;
|
||||
|
||||
// 记录商品更新信息
|
||||
productUpdates.push({
|
||||
product_id,
|
||||
quantity,
|
||||
unit_price: product.price,
|
||||
new_stock: product.stock - quantity
|
||||
});
|
||||
}
|
||||
|
||||
// 生成订单号
|
||||
const orderNumber = 'O' + Date.now() + Math.random().toString(36).substr(2, 6);
|
||||
|
||||
// 创建订单
|
||||
const orderResult = await connection.query(
|
||||
'INSERT INTO orders (order_number, user_id, total_amount, shipping_address) VALUES (?, ?, ?, ?)',
|
||||
[orderNumber, userId, totalAmount, shipping_address]
|
||||
);
|
||||
|
||||
const orderId = orderResult.insertId;
|
||||
|
||||
// 创建订单商品明细
|
||||
for (const item of items) {
|
||||
const { product_id, quantity } = item;
|
||||
const productInfo = productUpdates.find(p => p.product_id === product_id);
|
||||
|
||||
await connection.query(
|
||||
'INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES (?, ?, ?, ?)',
|
||||
[orderId, product_id, quantity, productInfo.unit_price]
|
||||
);
|
||||
|
||||
// 更新商品库存
|
||||
await connection.query(
|
||||
'UPDATE products SET stock = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[productInfo.new_stock, product_id]
|
||||
);
|
||||
}
|
||||
|
||||
await connection.commit();
|
||||
|
||||
// 获取完整的订单信息
|
||||
const newOrder = await dbConnector.query(
|
||||
'SELECT * FROM orders WHERE id = ?',
|
||||
[orderId]
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
code: 201,
|
||||
message: '订单创建成功',
|
||||
data: newOrder[0]
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
if (connection) {
|
||||
await connection.rollback();
|
||||
}
|
||||
|
||||
if (error.code && error.message) {
|
||||
return res.status(error.code).json({
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
/**
|
||||
* 取消订单
|
||||
*/
|
||||
router.post('/:id/cancel', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
let connection;
|
||||
try {
|
||||
connection = await dbConnector.getConnection();
|
||||
await connection.beginTransaction();
|
||||
|
||||
// 查询订单信息
|
||||
const orders = await connection.query(
|
||||
'SELECT id, payment_status, shipping_status FROM orders WHERE id = ? AND user_id = ?',
|
||||
[id, userId]
|
||||
);
|
||||
|
||||
if (orders.length === 0) {
|
||||
throw { code: 404, message: '订单不存在' };
|
||||
}
|
||||
|
||||
const order = orders[0];
|
||||
|
||||
// 检查订单状态是否可以取消
|
||||
if (order.payment_status === 'paid' || order.shipping_status === 'shipped') {
|
||||
throw { code: 400, message: '订单已支付或已发货,无法取消' };
|
||||
}
|
||||
|
||||
// 恢复商品库存
|
||||
const orderItems = await connection.query(
|
||||
'SELECT product_id, quantity FROM order_items WHERE order_id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
for (const item of orderItems) {
|
||||
await connection.query(
|
||||
'UPDATE products SET stock = stock + ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[item.quantity, item.product_id]
|
||||
);
|
||||
}
|
||||
|
||||
// 更新订单状态为已取消
|
||||
await connection.query(
|
||||
'UPDATE orders SET payment_status = "cancelled", updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
await connection.commit();
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '订单取消成功',
|
||||
data: null
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
if (connection) {
|
||||
await connection.rollback();
|
||||
}
|
||||
|
||||
if (error.code && error.message) {
|
||||
return res.status(error.code).json({
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
/**
|
||||
* 更新订单状态(支付成功回调)
|
||||
*/
|
||||
router.put('/:id/status', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { payment_status, shipping_status } = req.body;
|
||||
|
||||
if (!payment_status && !shipping_status) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '需要提供支付状态或发货状态',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
const updateFields = [];
|
||||
const updateValues = [];
|
||||
|
||||
if (payment_status) {
|
||||
updateFields.push('payment_status = ?');
|
||||
updateValues.push(payment_status);
|
||||
}
|
||||
|
||||
if (shipping_status) {
|
||||
updateFields.push('shipping_status = ?');
|
||||
updateValues.push(shipping_status);
|
||||
}
|
||||
|
||||
updateFields.push('updated_at = CURRENT_TIMESTAMP');
|
||||
updateValues.push(id);
|
||||
|
||||
const result = await dbConnector.query(
|
||||
`UPDATE orders SET ${updateFields.join(', ')} WHERE id = ?`,
|
||||
updateValues
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '订单不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '订单状态更新成功',
|
||||
data: null
|
||||
});
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
336
backend/routes/products.js
Normal file
336
backend/routes/products.js
Normal file
@@ -0,0 +1,336 @@
|
||||
const express = require('express');
|
||||
const dbConnector = require('../utils/dbConnector');
|
||||
const { optionalAuth } = require('../middlewares/auth');
|
||||
const { asyncHandler } = require('../middlewares/errorHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* 获取商品列表
|
||||
*/
|
||||
router.get('/', optionalAuth, asyncHandler(async (req, res) => {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 12,
|
||||
category_id,
|
||||
keyword,
|
||||
min_price,
|
||||
max_price,
|
||||
sort_by = 'created_at',
|
||||
sort_order = 'desc'
|
||||
} = req.query;
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
let whereClause = 'WHERE p.status = 1';
|
||||
let queryParams = [];
|
||||
|
||||
// 构建查询条件
|
||||
if (category_id) {
|
||||
whereClause += ' AND p.category_id = ?';
|
||||
queryParams.push(category_id);
|
||||
}
|
||||
|
||||
if (keyword) {
|
||||
whereClause += ' AND (p.name LIKE ? OR p.description LIKE ?)';
|
||||
const likeKeyword = `%${keyword}%`;
|
||||
queryParams.push(likeKeyword, likeKeyword);
|
||||
}
|
||||
|
||||
if (min_price) {
|
||||
whereClause += ' AND p.price >= ?';
|
||||
queryParams.push(parseFloat(min_price));
|
||||
}
|
||||
|
||||
if (max_price) {
|
||||
whereClause += ' AND p.price <= ?';
|
||||
queryParams.push(parseFloat(max_price));
|
||||
}
|
||||
|
||||
// 验证排序参数
|
||||
const validSortFields = ['name', 'price', 'created_at', 'stock'];
|
||||
const validSortOrders = ['asc', 'desc'];
|
||||
|
||||
const sortField = validSortFields.includes(sort_by) ? sort_by : 'created_at';
|
||||
const sortOrder = validSortOrders.includes(sort_order) ? sort_order : 'desc';
|
||||
|
||||
// 查询商品列表
|
||||
const products = await dbConnector.query(
|
||||
`SELECT
|
||||
p.id, p.name, p.category_id, p.price, p.stock, p.image,
|
||||
p.description, p.status, p.created_at, p.updated_at,
|
||||
c.name as category_name
|
||||
FROM products p
|
||||
LEFT JOIN categories c ON p.category_id = c.id
|
||||
${whereClause}
|
||||
ORDER BY p.${sortField} ${sortOrder}
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...queryParams, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
// 查询总数
|
||||
const totalResult = await dbConnector.query(
|
||||
`SELECT COUNT(*) as total
|
||||
FROM products p
|
||||
${whereClause}`,
|
||||
queryParams
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
products,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: totalResult[0].total,
|
||||
pages: Math.ceil(totalResult[0].total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取商品详情
|
||||
*/
|
||||
router.get('/:id', optionalAuth, asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
const products = await dbConnector.query(
|
||||
`SELECT
|
||||
p.id, p.name, p.category_id, p.price, p.stock, p.image,
|
||||
p.description, p.status, p.created_at, p.updated_at,
|
||||
c.name as category_name
|
||||
FROM products p
|
||||
LEFT JOIN categories c ON p.category_id = c.id
|
||||
WHERE p.id = ? AND p.status = 1`,
|
||||
[id]
|
||||
);
|
||||
|
||||
if (products.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '商品不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: products[0]
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 创建商品(需要管理员权限)
|
||||
*/
|
||||
router.post('/', asyncHandler(async (req, res) => {
|
||||
const { name, category_id, price, stock, image, description } = req.body;
|
||||
|
||||
// 参数验证
|
||||
if (!name || !category_id || price === undefined || stock === undefined) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '商品名称、分类、价格和库存为必填项',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (price < 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '价格不能为负数',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (stock < 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '库存不能为负数',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 检查分类是否存在
|
||||
const category = await dbConnector.query(
|
||||
'SELECT id FROM categories WHERE id = ? AND status = 1',
|
||||
[category_id]
|
||||
);
|
||||
|
||||
if (category.length === 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '分类不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 创建商品
|
||||
const result = await dbConnector.query(
|
||||
'INSERT INTO products (name, category_id, price, stock, image, description) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[name, category_id, price, stock, image, description]
|
||||
);
|
||||
|
||||
// 获取创建的商品信息
|
||||
const newProduct = await dbConnector.query(
|
||||
'SELECT * FROM products WHERE id = ?',
|
||||
[result.insertId]
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
code: 201,
|
||||
message: '商品创建成功',
|
||||
data: newProduct[0]
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 更新商品信息
|
||||
*/
|
||||
router.put('/:id', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { name, category_id, price, stock, image, description, status } = req.body;
|
||||
|
||||
// 检查商品是否存在
|
||||
const existingProduct = await dbConnector.query(
|
||||
'SELECT id FROM products WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (existingProduct.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '商品不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 参数验证
|
||||
if (price !== undefined && price < 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '价格不能为负数',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (stock !== undefined && stock < 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '库存不能为负数',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (category_id !== undefined) {
|
||||
const category = await dbConnector.query(
|
||||
'SELECT id FROM categories WHERE id = ? AND status = 1',
|
||||
[category_id]
|
||||
);
|
||||
|
||||
if (category.length === 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '分类不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 构建更新字段
|
||||
const updateFields = [];
|
||||
const updateValues = [];
|
||||
|
||||
if (name !== undefined) {
|
||||
updateFields.push('name = ?');
|
||||
updateValues.push(name);
|
||||
}
|
||||
|
||||
if (category_id !== undefined) {
|
||||
updateFields.push('category_id = ?');
|
||||
updateValues.push(category_id);
|
||||
}
|
||||
|
||||
if (price !== undefined) {
|
||||
updateFields.push('price = ?');
|
||||
updateValues.push(price);
|
||||
}
|
||||
|
||||
if (stock !== undefined) {
|
||||
updateFields.push('stock = ?');
|
||||
updateValues.push(stock);
|
||||
}
|
||||
|
||||
if (image !== undefined) {
|
||||
updateFields.push('image = ?');
|
||||
updateValues.push(image);
|
||||
}
|
||||
|
||||
if (description !== undefined) {
|
||||
updateFields.push('description = ?');
|
||||
updateValues.push(description);
|
||||
}
|
||||
|
||||
if (status !== undefined) {
|
||||
updateFields.push('status = ?');
|
||||
updateValues.push(status);
|
||||
}
|
||||
|
||||
if (updateFields.length === 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '没有提供需要更新的字段',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
updateFields.push('updated_at = CURRENT_TIMESTAMP');
|
||||
updateValues.push(id);
|
||||
|
||||
await dbConnector.query(
|
||||
`UPDATE products SET ${updateFields.join(', ')} WHERE id = ?`,
|
||||
updateValues
|
||||
);
|
||||
|
||||
// 获取更新后的商品信息
|
||||
const updatedProduct = await dbConnector.query(
|
||||
'SELECT * FROM products WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '商品更新成功',
|
||||
data: updatedProduct[0]
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 删除商品(软删除)
|
||||
*/
|
||||
router.delete('/:id', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await dbConnector.query(
|
||||
'UPDATE products SET status = 0, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '商品不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '商品删除成功',
|
||||
data: null
|
||||
});
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
269
backend/routes/users.js
Normal file
269
backend/routes/users.js
Normal file
@@ -0,0 +1,269 @@
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const validator = require('validator');
|
||||
const dbConnector = require('../utils/dbConnector');
|
||||
const { adminRequired } = require('../middlewares/auth');
|
||||
const { asyncHandler } = require('../middlewares/errorHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* 获取用户列表(管理员权限)
|
||||
*/
|
||||
router.get('/', adminRequired, asyncHandler(async (req, res) => {
|
||||
const { page = 1, limit = 10, keyword, user_type } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
let whereClause = 'WHERE status = 1';
|
||||
let queryParams = [];
|
||||
|
||||
if (keyword) {
|
||||
whereClause += ' AND (username LIKE ? OR phone LIKE ? OR email LIKE ?)';
|
||||
const likeKeyword = `%${keyword}%`;
|
||||
queryParams.push(likeKeyword, likeKeyword, likeKeyword);
|
||||
}
|
||||
|
||||
if (user_type) {
|
||||
whereClause += ' AND user_type = ?';
|
||||
queryParams.push(user_type);
|
||||
}
|
||||
|
||||
// 获取用户列表
|
||||
const users = await dbConnector.query(
|
||||
`SELECT id, username, phone, email, user_type, avatar_url, created_at, last_login
|
||||
FROM users ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
|
||||
[...queryParams, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
// 获取总数
|
||||
const totalResult = await dbConnector.query(
|
||||
`SELECT COUNT(*) as total FROM users ${whereClause}`,
|
||||
queryParams
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
users,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: totalResult[0].total,
|
||||
pages: Math.ceil(totalResult[0].total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取用户详情
|
||||
*/
|
||||
router.get('/:id', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
const users = await dbConnector.query(
|
||||
`SELECT id, username, phone, email, user_type, avatar_url,
|
||||
real_name, created_at, last_login
|
||||
FROM users WHERE id = ? AND status = 1`,
|
||||
[id]
|
||||
);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '用户不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: users[0]
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
router.put('/:id', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { email, real_name, avatar_url } = req.body;
|
||||
|
||||
// 检查用户是否存在
|
||||
const existingUser = await dbConnector.query(
|
||||
'SELECT id FROM users WHERE id = ? AND status = 1',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (existingUser.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '用户不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 验证邮箱格式
|
||||
if (email && !validator.isEmail(email)) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '邮箱格式不正确',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 检查邮箱是否已被其他用户使用
|
||||
if (email) {
|
||||
const emailUser = await dbConnector.query(
|
||||
'SELECT id FROM users WHERE email = ? AND id != ?',
|
||||
[email, id]
|
||||
);
|
||||
|
||||
if (emailUser.length > 0) {
|
||||
return res.status(409).json({
|
||||
code: 409,
|
||||
message: '邮箱已被其他用户使用',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 构建更新字段
|
||||
const updateFields = [];
|
||||
const updateValues = [];
|
||||
|
||||
if (email !== undefined) {
|
||||
updateFields.push('email = ?');
|
||||
updateValues.push(email);
|
||||
}
|
||||
|
||||
if (real_name !== undefined) {
|
||||
updateFields.push('real_name = ?');
|
||||
updateValues.push(real_name);
|
||||
}
|
||||
|
||||
if (avatar_url !== undefined) {
|
||||
updateFields.push('avatar_url = ?');
|
||||
updateValues.push(avatar_url);
|
||||
}
|
||||
|
||||
if (updateFields.length === 0) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '没有提供需要更新的字段',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
updateFields.push('updated_at = CURRENT_TIMESTAMP');
|
||||
updateValues.push(id);
|
||||
|
||||
await dbConnector.query(
|
||||
`UPDATE users SET ${updateFields.join(', ')} WHERE id = ?`,
|
||||
updateValues
|
||||
);
|
||||
|
||||
// 获取更新后的用户信息
|
||||
const updatedUser = await dbConnector.query(
|
||||
'SELECT id, username, phone, email, user_type, avatar_url, real_name FROM users WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '更新成功',
|
||||
data: updatedUser[0]
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
router.put('/:id/password', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { old_password, new_password } = req.body;
|
||||
|
||||
if (!old_password || !new_password) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '原密码和新密码为必填项',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
if (new_password.length < 6) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '新密码长度不能少于6位',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 获取用户当前密码
|
||||
const users = await dbConnector.query(
|
||||
'SELECT password_hash FROM users WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '用户不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 验证原密码
|
||||
const isValidPassword = await bcrypt.compare(old_password, users[0].password_hash);
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({
|
||||
code: 401,
|
||||
message: '原密码不正确',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 加密新密码
|
||||
const hashedPassword = await bcrypt.hash(new_password, 12);
|
||||
|
||||
await dbConnector.query(
|
||||
'UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[hashedPassword, id]
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '密码修改成功',
|
||||
data: null
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 删除用户(软删除)
|
||||
*/
|
||||
router.delete('/:id', adminRequired, asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await dbConnector.query(
|
||||
'UPDATE users SET status = 0, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '用户不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '用户删除成功',
|
||||
data: null
|
||||
});
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
165
backend/scripts/initDatabase.js
Normal file
165
backend/scripts/initDatabase.js
Normal file
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* 数据库初始化脚本
|
||||
* 用于验证数据库连接和创建基础表结构
|
||||
*/
|
||||
|
||||
const mysql = require('mysql2/promise');
|
||||
const databaseConfig = require('../config/database');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
class DatabaseInitializer {
|
||||
constructor() {
|
||||
this.connection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建数据库连接
|
||||
*/
|
||||
async createConnection() {
|
||||
try {
|
||||
this.connection = await mysql.createConnection({
|
||||
host: databaseConfig.host,
|
||||
port: databaseConfig.port,
|
||||
user: databaseConfig.username,
|
||||
password: databaseConfig.password,
|
||||
database: databaseConfig.database,
|
||||
charset: 'utf8mb4',
|
||||
timezone: '+08:00'
|
||||
});
|
||||
|
||||
console.log('✅ 数据库连接成功');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 数据库连接失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证数据库连接
|
||||
*/
|
||||
async validateConnection() {
|
||||
try {
|
||||
const [rows] = await this.connection.execute('SELECT NOW() as current_time, VERSION() as mysql_version');
|
||||
console.log('📊 数据库信息:');
|
||||
console.log(` 当前时间: ${rows[0].current_time}`);
|
||||
console.log(` MySQL版本: ${rows[0].mysql_version}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 数据库验证失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查表是否存在
|
||||
* @param {string} tableName - 表名
|
||||
*/
|
||||
async checkTableExists(tableName) {
|
||||
try {
|
||||
const [rows] = await this.connection.execute(
|
||||
`SELECT COUNT(*) as count FROM information_schema.tables
|
||||
WHERE table_schema = ? AND table_name = ?`,
|
||||
[databaseConfig.database, tableName]
|
||||
);
|
||||
return rows[0].count > 0;
|
||||
} catch (error) {
|
||||
console.error(`❌ 检查表 ${tableName} 存在失败:`, error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行SQL文件
|
||||
* @param {string} filePath - SQL文件路径
|
||||
*/
|
||||
async executeSqlFile(filePath) {
|
||||
try {
|
||||
const sqlContent = fs.readFileSync(filePath, 'utf8');
|
||||
const statements = sqlContent.split(';').filter(stmt => stmt.trim());
|
||||
|
||||
for (const statement of statements) {
|
||||
if (statement.trim()) {
|
||||
await this.connection.execute(statement);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ 成功执行SQL文件: ${path.basename(filePath)}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`❌ 执行SQL文件失败:`, error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭数据库连接
|
||||
*/
|
||||
async closeConnection() {
|
||||
if (this.connection) {
|
||||
await this.connection.end();
|
||||
console.log('🔌 数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主初始化方法
|
||||
*/
|
||||
async initialize() {
|
||||
console.log('🚀 开始数据库初始化...');
|
||||
console.log(`📋 环境: ${process.env.NODE_ENV || 'development'}`);
|
||||
console.log(`🗄️ 数据库: ${databaseConfig.database}`);
|
||||
console.log(`🌐 主机: ${databaseConfig.host}:${databaseConfig.port}`);
|
||||
console.log('─'.repeat(50));
|
||||
|
||||
// 创建连接
|
||||
const connected = await this.createConnection();
|
||||
if (!connected) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 验证连接
|
||||
const validated = await this.validateConnection();
|
||||
if (!validated) {
|
||||
await this.closeConnection();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('✅ 数据库连接验证通过');
|
||||
console.log('─'.repeat(50));
|
||||
|
||||
// 这里可以添加具体的表创建逻辑
|
||||
console.log('📋 数据库初始化完成');
|
||||
console.log('✅ 所有检查通过,数据库连接正常');
|
||||
|
||||
await this.closeConnection();
|
||||
}
|
||||
}
|
||||
|
||||
// 执行初始化
|
||||
const initializer = new DatabaseInitializer();
|
||||
|
||||
// 处理命令行参数
|
||||
const args = process.argv.slice(2);
|
||||
if (args.includes('--help') || args.includes('-h')) {
|
||||
console.log(`
|
||||
使用方法: node scripts/initDatabase.js [选项]
|
||||
|
||||
选项:
|
||||
--help, -h 显示帮助信息
|
||||
--check 只检查连接,不执行初始化
|
||||
|
||||
示例:
|
||||
node scripts/initDatabase.js # 完整初始化
|
||||
node scripts/initDatabase.js --check # 只检查连接
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
initializer.initialize().catch(error => {
|
||||
console.error('❌ 初始化过程中发生错误:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
152
backend/utils/dbConnector.js
Normal file
152
backend/utils/dbConnector.js
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 数据库连接工具类
|
||||
* 封装MySQL数据库连接和基本操作
|
||||
*/
|
||||
|
||||
const mysql = require('mysql2/promise');
|
||||
const databaseConfig = require('../config/database');
|
||||
|
||||
class DBConnector {
|
||||
constructor() {
|
||||
this.pool = null;
|
||||
this.connection = null;
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据库连接池
|
||||
*/
|
||||
init() {
|
||||
try {
|
||||
this.pool = mysql.createPool({
|
||||
host: databaseConfig.host,
|
||||
port: databaseConfig.port,
|
||||
user: databaseConfig.username,
|
||||
password: databaseConfig.password,
|
||||
database: databaseConfig.database,
|
||||
connectionLimit: databaseConfig.pool.max,
|
||||
acquireTimeout: databaseConfig.pool.acquire,
|
||||
timeout: databaseConfig.pool.idle,
|
||||
charset: 'utf8mb4',
|
||||
timezone: '+08:00',
|
||||
decimalNumbers: true,
|
||||
supportBigNumbers: true,
|
||||
bigNumberStrings: false
|
||||
});
|
||||
|
||||
console.log('✅ 数据库连接池初始化成功');
|
||||
} catch (error) {
|
||||
console.error('❌ 数据库连接池初始化失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库连接
|
||||
* @returns {Promise} 数据库连接对象
|
||||
*/
|
||||
async getConnection() {
|
||||
try {
|
||||
this.connection = await this.pool.getConnection();
|
||||
return this.connection;
|
||||
} catch (error) {
|
||||
console.error('❌ 获取数据库连接失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行SQL查询
|
||||
* @param {string} sql - SQL语句
|
||||
* @param {Array} params - 参数数组
|
||||
* @returns {Promise} 查询结果
|
||||
*/
|
||||
async query(sql, params = []) {
|
||||
let connection;
|
||||
try {
|
||||
connection = await this.getConnection();
|
||||
const [rows] = await connection.execute(sql, params);
|
||||
return rows;
|
||||
} catch (error) {
|
||||
console.error('❌ SQL执行失败:', error.message);
|
||||
console.error('SQL:', sql);
|
||||
console.error('参数:', params);
|
||||
throw error;
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启事务
|
||||
* @returns {Promise} 事务连接对象
|
||||
*/
|
||||
async beginTransaction() {
|
||||
try {
|
||||
const connection = await this.getConnection();
|
||||
await connection.beginTransaction();
|
||||
return connection;
|
||||
} catch (error) {
|
||||
console.error('❌ 开启事务失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交事务
|
||||
* @param {Object} connection - 事务连接对象
|
||||
*/
|
||||
async commitTransaction(connection) {
|
||||
try {
|
||||
await connection.commit();
|
||||
connection.release();
|
||||
} catch (error) {
|
||||
console.error('❌ 提交事务失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚事务
|
||||
* @param {Object} connection - 事务连接对象
|
||||
*/
|
||||
async rollbackTransaction(connection) {
|
||||
try {
|
||||
await connection.rollback();
|
||||
connection.release();
|
||||
} catch (error) {
|
||||
console.error('❌ 回滚事务失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭连接池
|
||||
*/
|
||||
async close() {
|
||||
if (this.pool) {
|
||||
await this.pool.end();
|
||||
console.log('✅ 数据库连接池已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 健康检查
|
||||
* @returns {Promise<boolean>} 数据库连接状态
|
||||
*/
|
||||
async healthCheck() {
|
||||
try {
|
||||
const result = await this.query('SELECT 1 as status');
|
||||
return result[0].status === 1;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建全局数据库连接实例
|
||||
const dbConnector = new DBConnector();
|
||||
|
||||
module.exports = dbConnector;
|
||||
326
official_website/about.html
Normal file
326
official_website/about.html
Normal file
@@ -0,0 +1,326 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="了解爱鉴花的故事、使命和团队 - 专业的AI花卉识别平台,由资深人工智能专家和植物学家组成的创新团队。">
|
||||
<meta name="keywords" content="爱鉴花团队,AI花卉识别,植物识别技术,公司介绍,团队介绍,发展历程">
|
||||
<meta name="author" content="爱鉴花团队">
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:title" content="关于我们 - 爱鉴花">
|
||||
<meta property="og:description" content="了解爱鉴花的故事、使命和专业团队,我们致力于通过AI技术让植物识别变得简单有趣。">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://aijianhua.com/about.html">
|
||||
<meta property="og:image" content="https://aijianhua.com/images/about-og.png">
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="关于我们 - 爱鉴花">
|
||||
<meta name="twitter:description" content="了解爱鉴花的故事、使命和专业团队。">
|
||||
|
||||
<title>关于我们 - 爱鉴花</title>
|
||||
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.7.2/css/all.css" rel="stylesheet">
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- 导航栏 -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white fixed-top shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="index.html">
|
||||
<i class="fas fa-flower text-primary me-2"></i>
|
||||
<span class="fw-bold text-primary">爱鉴花</span>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.html">首页</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="about.html">关于我们</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="products.html">产品介绍</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="news.html">新闻中心</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="contact.html">联系我们</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 页面标题 -->
|
||||
<section class="page-header bg-gradient-primary text-white py-5">
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center">
|
||||
<h1 class="display-4 fw-bold mb-3">关于我们</h1>
|
||||
<p class="lead">了解爱鉴花的故事、使命和团队</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 公司介绍 -->
|
||||
<section class="py-5">
|
||||
<div class="container py-5">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-6">
|
||||
<h2 class="display-5 fw-bold text-dark mb-4">我们的故事</h2>
|
||||
<p class="lead text-muted mb-4">
|
||||
爱鉴花成立于2023年,是一家专注于人工智能花卉识别技术的创新企业。
|
||||
我们致力于通过先进的AI技术,让每个人都能轻松识别和了解身边的植物。
|
||||
</p>
|
||||
<p class="text-muted">
|
||||
我们的团队由资深的人工智能专家、植物学家和产品设计师组成,
|
||||
共同打造了这款智能花卉识别平台。通过深度学习和大数据分析,
|
||||
我们能够准确识别数千种花卉植物,并提供详细的养护知识。
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<img src="images/about-1.jpg" alt="公司环境" class="img-fluid rounded shadow lazy-load" loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 使命愿景 -->
|
||||
<section class="bg-light py-5">
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="card border-0 text-center p-4">
|
||||
<div class="feature-icon bg-primary text-white rounded-circle mx-auto mb-3">
|
||||
<i class="fas fa-bullseye fa-2x"></i>
|
||||
</div>
|
||||
<h4 class="mb-3">我们的使命</h4>
|
||||
<p class="text-muted">
|
||||
通过人工智能技术,让植物识别变得简单有趣,
|
||||
帮助人们更好地了解和保护自然环境。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="card border-0 text-center p-4">
|
||||
<div class="feature-icon bg-success text-white rounded-circle mx-auto mb-3">
|
||||
<i class="fas fa-eye fa-2x"></i>
|
||||
</div>
|
||||
<h4 class="mb-3">我们的愿景</h4>
|
||||
<p class="text-muted">
|
||||
成为全球领先的植物识别平台,
|
||||
构建连接人与自然的知识桥梁。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="card border-0 text-center p-4">
|
||||
<div class="feature-icon bg-info text-white rounded-circle mx-auto mb-3">
|
||||
<i class="fas fa-handshake fa-2x"></i>
|
||||
</div>
|
||||
<h4 class="mb-3">我们的价值观</h4>
|
||||
<p class="text-muted">
|
||||
创新、专业、用户至上、环境保护,
|
||||
这些是我们始终坚持的核心价值观。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 发展历程 -->
|
||||
<section class="py-5">
|
||||
<div class="container py-5">
|
||||
<h2 class="display-5 fw-bold text-dark text-center mb-5">发展历程</h2>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="timeline">
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-content">
|
||||
<h5>2023年1月</h5>
|
||||
<p>公司成立,开始AI花卉识别技术研发</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-content">
|
||||
<h5>2023年6月</h5>
|
||||
<p>完成首个花卉识别模型,准确率达到85%</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-content">
|
||||
<h5>2023年9月</h5>
|
||||
<p>微信小程序上线,获得首批用户</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-content">
|
||||
<h5>2024年1月</h5>
|
||||
<p>识别准确率提升至95%,用户突破10万</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 团队介绍 -->
|
||||
<section class="bg-light py-5">
|
||||
<div class="container py-5">
|
||||
<h2 class="display-5 fw-bold text-dark text-center mb-5">核心团队</h2>
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-md-6 mb-4">
|
||||
<div class="card team-card border-0 text-center">
|
||||
<img src="images/team-1.jpg" alt="CEO" class="card-img-top rounded-circle mx-auto mt-4 lazy-load" style="width: 120px; height: 120px; object-fit: cover;" loading="lazy">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">张明</h5>
|
||||
<p class="text-muted">CEO/创始人</p>
|
||||
<p class="card-text text-muted small">
|
||||
前Google AI工程师,10年人工智能领域经验
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 mb-4">
|
||||
<div class="card team-card border-0 text-center">
|
||||
<img src="images/team-2.jpg" alt="CTO" class="card-img-top rounded-circle mx-auto mt-4 lazy-load" style="width: 120px; height: 120px; object-fit: cover;" loading="lazy">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">李华</h5>
|
||||
<p class="text-muted">CTO</p>
|
||||
<p class="card-text text-muted small">
|
||||
深度学习专家,专注于计算机视觉技术
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 mb-4">
|
||||
<div class="card team-card border-0 text-center">
|
||||
<img src="images/team-3.jpg" alt="植物学家" class="card-img-top rounded-circle mx-auto mt-4 lazy-load" style="width: 120px; height: 120px; object-fit: cover;" loading="lazy">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">王教授</h5>
|
||||
<p class="text-muted">首席植物学家</p>
|
||||
<p class="card-text text-muted small">
|
||||
中科院植物研究所博士,30年植物学研究经验
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6 mb-4">
|
||||
<div class="card team-card border-0 text-center">
|
||||
<img src="images/team-4.jpg" alt="产品经理" class="card-img-top rounded-circle mx-auto mt-4" style="width: 120px; height: 120px; object-fit: cover;">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">陈小姐</h5>
|
||||
<p class="text-muted">产品总监</p>
|
||||
<p class="card-text text-muted small">
|
||||
前腾讯产品经理,擅长用户体验设计
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="bg-dark text-white py-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-4 mb-4">
|
||||
<h5 class="mb-3">爱鉴花</h5>
|
||||
<p class="text-muted">智能花卉识别平台,让您更好地了解和欣赏自然之美。</p>
|
||||
<div class="social-links">
|
||||
<a href="#" class="text-white me-3"><i class="fab fa-weixin"></i></a>
|
||||
<a href="#" class="text-white me-3"><i class="fab fa-weibo"></i></a>
|
||||
<a href="#" class="text-white"><i class="fab fa-github"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 mb-4">
|
||||
<h6 class="mb-3">产品</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="#" class="text-muted text-decoration-none">微信小程序</a></li>
|
||||
<li><a href="#" class="text-muted text-decoration-none">API服务</a></li>
|
||||
<li><a href="#" class="text-muted text-decoration-none">企业版</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-2 mb-4">
|
||||
<h6 class="mb-3">支持</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="#" class="text-muted text-decoration-none">帮助中心</a></li>
|
||||
<li><a href="#" class="text-muted text-decoration-none">常见问题</a></li>
|
||||
<li><a href="#" class="text-muted text-decoration-none">联系我们</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-4 mb-4">
|
||||
<h6 class="mb-3">联系我们</h6>
|
||||
<p class="text-muted mb-1"><i class="fas fa-map-marker-alt me-2"></i>北京市海淀区中关村大街</p>
|
||||
<p class="text-muted mb-1"><i class="fas fa-phone me-2"></i>400-123-4567</p>
|
||||
<p class="text-muted"><i class="fas fa-envelope me-2"></i>contact@aijianhua.com</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-4">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6 text-center text-md-start">
|
||||
<p class="text-muted mb-0">© 2024 爱鉴花. 保留所有权利.</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-center text-md-end">
|
||||
<p class="text-muted mb-0">
|
||||
<a href="#" class="text-muted text-decoration-none me-3">隐私政策</a>
|
||||
<a href="#" class="text-muted text-decoration-none me-3">服务条款</a>
|
||||
<a href="#" class="text-muted text-decoration-none">京ICP备12345678号</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-3">
|
||||
<p class="text-muted small">
|
||||
友情链接:
|
||||
<a href="#" class="text-muted text-decoration-none me-2">中国花卉协会</a>
|
||||
<a href="#" class="text-muted text-decoration-none me-2">植物智</a>
|
||||
<a href="#" class="text-muted text-decoration-none">园林在线</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
|
||||
<!-- Schema.org结构化数据 -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "AboutPage",
|
||||
"name": "关于爱鉴花",
|
||||
"description": "了解爱鉴花的故事、使命和专业团队,我们致力于通过AI技术让植物识别变得简单有趣。",
|
||||
"url": "https://aijianhua.com/about.html",
|
||||
"mainEntity": {
|
||||
"@type": "Organization",
|
||||
"name": "爱鉴花",
|
||||
"description": "专业的AI花卉识别平台",
|
||||
"url": "https://aijianhua.com",
|
||||
"foundingDate": "2023-01",
|
||||
"founder": [
|
||||
{
|
||||
"@type": "Person",
|
||||
"name": "张明"
|
||||
}
|
||||
],
|
||||
"numberOfEmployees": "15",
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
"addressLocality": "北京",
|
||||
"addressCountry": "CN"
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
481
official_website/contact.html
Normal file
481
official_website/contact.html
Normal file
@@ -0,0 +1,481 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>联系我们 - 爱鉴花</title>
|
||||
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.7.2/css/all.css" rel="stylesheet">
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- 导航栏 -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white fixed-top shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="index.html">
|
||||
<i class="fas fa-flower text-primary me-2"></i>
|
||||
<span class="fw-bold text-primary">爱鉴花</span>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.html">首页</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="about.html">关于我们</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="products.html">产品介绍</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="news.html">新闻中心</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="contact.html">联系我们</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 页面标题 -->
|
||||
<section class="page-header bg-gradient-primary text-white py-5">
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center">
|
||||
<h1 class="display-4 fw-bold mb-3">联系我们</h1>
|
||||
<p class="lead">我们很乐意听取您的意见和建议</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 联系信息 -->
|
||||
<section class="py-5">
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<!-- 联系表单 -->
|
||||
<div class="card border-0 shadow-sm mb-5">
|
||||
<div class="card-body p-5">
|
||||
<h2 class="fw-bold mb-4 text-dark">发送消息</h2>
|
||||
<form id="contactForm">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="name" class="form-label">姓名 *</label>
|
||||
<input type="text" class="form-control" id="name" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="email" class="form-label">邮箱 *</label>
|
||||
<input type="email" class="form-control" id="email" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="phone" class="form-label">电话</label>
|
||||
<input type="tel" class="form-control" id="phone">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="subject" class="form-label">主题 *</label>
|
||||
<select class="form-select" id="subject" required>
|
||||
<option value="">请选择主题</option>
|
||||
<option value="general">一般咨询</option>
|
||||
<option value="technical">技术支持</option>
|
||||
<option value="business">商务合作</option>
|
||||
<option value="feedback">产品反馈</option>
|
||||
<option value="other">其他</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="message" class="form-label">消息内容 *</label>
|
||||
<textarea class="form-control" id="message" rows="5" required></textarea>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="agree" required>
|
||||
<label class="form-check-label" for="agree">
|
||||
我同意<a href="#" class="text-primary">隐私政策</a>
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-lg w-100">发送消息</button>
|
||||
<div class="mt-3 text-center">
|
||||
<small class="text-muted">我们将在24小时内回复您的消息</small>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- 表单提交成功提示 -->
|
||||
<div class="alert alert-success mt-4 d-none" id="successAlert">
|
||||
<i class="fas fa-check-circle me-2"></i>
|
||||
消息发送成功!我们会尽快与您联系。
|
||||
</div>
|
||||
|
||||
<!-- 表单提交失败提示 -->
|
||||
<div class="alert alert-danger mt-4 d-none" id="errorAlert">
|
||||
<i class="fas fa-exclamation-circle me-2"></i>
|
||||
发送失败,请检查表单内容后重试。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<!-- 联系信息卡片 -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body text-center">
|
||||
<div class="feature-icon bg-primary text-white rounded-circle mx-auto mb-3">
|
||||
<i class="fas fa-map-marker-alt fa-2x"></i>
|
||||
</div>
|
||||
<h5 class="fw-bold">公司地址</h5>
|
||||
<p class="text-muted">
|
||||
北京市海淀区中关村大街<br>
|
||||
科技园区A座1001室
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body text-center">
|
||||
<div class="feature-icon bg-success text-white rounded-circle mx-auto mb-3">
|
||||
<i class="fas fa-phone fa-2x"></i>
|
||||
</div>
|
||||
<h5 class="fw-bold">联系电话</h5>
|
||||
<p class="text-muted">
|
||||
400-123-4567<br>
|
||||
+86 10-8888-7777
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body text-center">
|
||||
<div class="feature-icon bg-info text-white rounded-circle mx-auto mb-3">
|
||||
<i class="fas fa-envelope fa-2x"></i>
|
||||
</div>
|
||||
<h5 class="fw-bold">电子邮箱</h5>
|
||||
<p class="text-muted">
|
||||
contact@aijianhua.com<br>
|
||||
support@aijianhua.com
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<div class="feature-icon bg-warning text-white rounded-circle mx-auto mb-3">
|
||||
<i class="fas fa-clock fa-2x"></i>
|
||||
</div>
|
||||
<h5 class="fw-bold">工作时间</h5>
|
||||
<p class="text-muted">
|
||||
周一至周五: 9:00-18:00<br>
|
||||
周末: 10:00-16:00
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 地图 -->
|
||||
<section class="bg-light py-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center mb-5">
|
||||
<h2 class="display-5 fw-bold text-dark">我们的位置</h2>
|
||||
<p class="lead text-muted">欢迎来访我们的办公室</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<!-- 地图占位图 -->
|
||||
<div class="map-placeholder bg-secondary text-white text-center py-5">
|
||||
<i class="fas fa-map-marked-alt fa-4x mb-3"></i>
|
||||
<h4>地图位置</h4>
|
||||
<p>北京市海淀区中关村大街科技园区A座</p>
|
||||
<small class="text-white-50">(实际部署时可集成百度地图或高德地图)</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 常见问题 -->
|
||||
<section class="py-5">
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center mb-5">
|
||||
<h2 class="display-5 fw-bold text-dark">常见问题</h2>
|
||||
<p class="lead text-muted">快速找到您需要的答案</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="accordion" id="faqAccordion">
|
||||
<div class="accordion-item border-0 shadow-sm mb-3">
|
||||
<h3 class="accordion-header">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#faq1">
|
||||
如何开始使用爱鉴花小程序?
|
||||
</button>
|
||||
</h3>
|
||||
<div id="faq1" class="accordion-collapse collapse show" data-bs-parent="#faqAccordion">
|
||||
<div class="accordion-body">
|
||||
<p class="text-muted">
|
||||
在微信中搜索"爱鉴花"即可找到我们的小程序,
|
||||
点击进入后即可开始使用花卉识别功能。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item border-0 shadow-sm mb-3">
|
||||
<h3 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faq2">
|
||||
识别准确率如何?
|
||||
</button>
|
||||
</h3>
|
||||
<div id="faq2" class="accordion-collapse collapse" data-bs-parent="#faqAccordion">
|
||||
<div class="accordion-body">
|
||||
<p class="text-muted">
|
||||
我们的AI识别准确率目前达到95%,
|
||||
在良好光线和清晰图片条件下准确率更高。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="accordion" id="faqAccordion2">
|
||||
<div class="accordion-item border-0 shadow-sm mb-3">
|
||||
<h3 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faq3">
|
||||
是否支持企业定制?
|
||||
</button>
|
||||
</h3>
|
||||
<div id="faq3" class="accordion-collapse collapse" data-bs-parent="#faqAccordion2">
|
||||
<div class="accordion-body">
|
||||
<p class="text-muted">
|
||||
是的,我们提供企业定制服务,包括私有化部署、
|
||||
定制模型训练等,请联系商务合作部门。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item border-0 shadow-sm mb-3">
|
||||
<h3 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faq4">
|
||||
技术支持响应时间?
|
||||
</button>
|
||||
</h3>
|
||||
<div id="faq4" class="accordion-collapse collapse" data-bs-parent="#faqAccordion2">
|
||||
<div class="accordion-body">
|
||||
<p class="text-muted">
|
||||
普通咨询:24小时内回复<br>
|
||||
紧急问题:4小时内响应<br>
|
||||
企业客户:专属技术支持
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-4">
|
||||
<a href="#" class="btn btn-outline-primary">查看更多问题</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="bg-dark text-white py-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-4 mb-4">
|
||||
<h5 class="fw-bold mb-3">爱鉴花</h5>
|
||||
<p class="text-white-50">
|
||||
用AI技术连接人与自然,让植物识别变得简单有趣。
|
||||
</p>
|
||||
<div class="social-links">
|
||||
<a href="#" class="text-white-50 me-3"><i class="fab fa-weixin"></i></a>
|
||||
<a href="#" class="text-white-50 me-3"><i class="fab fa-weibo"></i></a>
|
||||
<a href="#" class="text-white-50"><i class="fab fa-github"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-4 mb-4">
|
||||
<h6 class="fw-bold mb-3">产品</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="products.html" class="text-white-50 text-decoration-none">微信小程序</a></li>
|
||||
<li><a href="products.html" class="text-white-50 text-decoration-none">API服务</a></li>
|
||||
<li><a href="products.html" class="text-white-50 text-decoration-none">企业版</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-4 mb-4">
|
||||
<h6 class="fw-bold mb-3">关于</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="about.html" class="text-white-50 text-decoration-none">关于我们</a></li>
|
||||
<li><a href="about.html" class="text-white-50 text-decoration-none">团队介绍</a></li>
|
||||
<li><a href="news.html" class="text-white-50 text-decoration-none">新闻动态</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-4 mb-4">
|
||||
<h6 class="fw-bold mb-3">支持</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="#" class="text-white-50 text-decoration-none">帮助中心</a></li>
|
||||
<li><a href="#" class="text-white-50 text-decoration-none">API文档</a></li>
|
||||
<li><a href="contact.html" class="text-white-50 text-decoration-none">联系我们</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-4 mb-4">
|
||||
<h6 class="fw-bold mb-3">法律</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="#" class="text-white-50 text-decoration-none">隐私政策</a></li>
|
||||
<li><a href="#" class="text-white-50 text-decoration-none">服务条款</a></li>
|
||||
<li><a href="#" class="text-white-50 text-decoration-none">版权声明</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-4">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6 text-center text-md-start">
|
||||
<p class="text-white-50 mb-0">© 2024 爱鉴花. 保留所有权利</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-center text-md-end">
|
||||
<p class="text-white-50 mb-0">
|
||||
<a href="#" class="text-white-50 text-decoration-none me-3">隐私政策</a>
|
||||
<a href="#" class="text-white-50 text-decoration-none me-3">服务条款</a>
|
||||
<a href="#" class="text-white-50 text-decoration-none">京ICP备12345678号</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-3">
|
||||
<p class="text-white-50 small">
|
||||
友情链接:
|
||||
<a href="#" class="text-white-50 text-decoration-none me-2">中国花卉协会</a>
|
||||
<a href="#" class="text-white-50 text-decoration-none me-2">植物智</a>
|
||||
<a href="#" class="text-white-50 text-decoration-none">园林在线</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
|
||||
<!-- 表单验证脚本 -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const contactForm = document.getElementById('contactForm');
|
||||
const successAlert = document.getElementById('successAlert');
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
|
||||
// 实时表单验证
|
||||
const inputs = contactForm.querySelectorAll('input, textarea, select');
|
||||
inputs.forEach(input => {
|
||||
input.addEventListener('blur', function() {
|
||||
validateField(this);
|
||||
});
|
||||
|
||||
input.addEventListener('input', function() {
|
||||
clearValidation(this);
|
||||
});
|
||||
});
|
||||
|
||||
// 表单提交处理
|
||||
contactForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
let isValid = true;
|
||||
inputs.forEach(input => {
|
||||
if (!validateField(input)) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (isValid) {
|
||||
// 模拟表单提交成功
|
||||
simulateFormSubmission();
|
||||
}
|
||||
});
|
||||
|
||||
function validateField(field) {
|
||||
let isValid = true;
|
||||
let errorMessage = '';
|
||||
|
||||
if (field.required && !field.value.trim()) {
|
||||
isValid = false;
|
||||
errorMessage = '此字段为必填项';
|
||||
} else if (field.type === 'email' && field.value) {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(field.value)) {
|
||||
isValid = false;
|
||||
errorMessage = '请输入有效的邮箱地址';
|
||||
}
|
||||
} else if (field.type === 'tel' && field.value) {
|
||||
const phoneRegex = /^1[3-9]\d{9}$/;
|
||||
if (!phoneRegex.test(field.value)) {
|
||||
isValid = false;
|
||||
errorMessage = '请输入有效的手机号码';
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
showError(field, errorMessage);
|
||||
} else {
|
||||
clearValidation(field);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
function showError(field, message) {
|
||||
clearValidation(field);
|
||||
field.classList.add('is-invalid');
|
||||
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'invalid-feedback';
|
||||
errorDiv.textContent = message;
|
||||
field.parentNode.appendChild(errorDiv);
|
||||
}
|
||||
|
||||
function clearValidation(field) {
|
||||
field.classList.remove('is-invalid');
|
||||
const errorDiv = field.parentNode.querySelector('.invalid-feedback');
|
||||
if (errorDiv) {
|
||||
errorDiv.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function simulateFormSubmission() {
|
||||
// 显示加载状态
|
||||
const submitBtn = contactForm.querySelector('button[type="submit"]');
|
||||
const originalText = submitBtn.textContent;
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 发送中...';
|
||||
|
||||
// 模拟网络请求延迟
|
||||
setTimeout(() => {
|
||||
// 恢复按钮状态
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = originalText;
|
||||
|
||||
// 显示成功消息
|
||||
successAlert.classList.remove('d-none');
|
||||
errorAlert.classList.add('d-none');
|
||||
|
||||
// 重置表单
|
||||
contactForm.reset();
|
||||
|
||||
// 3秒后隐藏成功消息
|
||||
setTimeout(() => {
|
||||
successAlert.classList.add('d-none');
|
||||
}, 3000);
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
472
official_website/css/style.css
Normal file
472
official_website/css/style.css
Normal file
@@ -0,0 +1,472 @@
|
||||
/* 全局样式 */
|
||||
:root {
|
||||
--primary-color: #4CAF50; /* 植物绿色 */
|
||||
--secondary-color: #9C27B0; /* 花卉紫色 */
|
||||
--success-color: #66BB6A; /* 嫩绿色 */
|
||||
--info-color: #42A5F5; /* 天空蓝色 */
|
||||
--warning-color: #FFA726; /* 橙黄色 */
|
||||
--danger-color: #EF5350; /* 红色 */
|
||||
--light-color: #F1F8E9; /* 淡绿色背景 */
|
||||
--dark-color: #2E7D32; /* 深绿色 */
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 导航栏样式 */
|
||||
.navbar {
|
||||
padding: 1rem 0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-link {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
margin: 0 0.5rem;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-link:hover,
|
||||
.navbar-nav .nav-link.active {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 英雄区域样式 */
|
||||
.hero-section {
|
||||
background: linear-gradient(135deg, #4CAF50 0%, #66BB6A 100%); /* 绿色渐变 */
|
||||
margin-top: 76px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.min-vh-75 {
|
||||
min-height: 75vh;
|
||||
}
|
||||
|
||||
.hero-section h1 {
|
||||
font-size: 3.5rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.hero-section p {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn {
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #3a56c4;
|
||||
border-color: #3a56c4;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
border: 2px solid var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-outline-primary:hover {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 特性图标样式 */
|
||||
.feature-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.card:hover .feature-icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* 产品卡片样式 */
|
||||
.product-card {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.product-card:hover {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
|
||||
.product-card img {
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
border-top-left-radius: 1rem;
|
||||
border-top-right-radius: 1rem;
|
||||
}
|
||||
|
||||
/* 新闻卡片样式 */
|
||||
.news-card {
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.news-card:hover {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
/* 页脚样式 */
|
||||
/* 修复body样式 */
|
||||
body {
|
||||
position: relative;
|
||||
min-height: 100vh; /* 确保body有足够高度 */
|
||||
/* 移除了overflow-x: hidden */
|
||||
}
|
||||
|
||||
/* 增强页脚样式 */
|
||||
footer {
|
||||
position: relative;
|
||||
z-index: 10; /* 提高层级避免被遮挡 */
|
||||
background: linear-gradient(135deg, #2E7D32 0%, #388E3C 100%); /* 深绿色渐变 */
|
||||
}
|
||||
|
||||
footer h5,
|
||||
footer h6 {
|
||||
color: white;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: white !important;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.social-links a {
|
||||
font-size: 1.5rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.social-links a:hover {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
/* 表单验证样式 */
|
||||
.invalid-feedback {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
.is-invalid {
|
||||
border-color: var(--danger-color) !important;
|
||||
}
|
||||
|
||||
.is-invalid:focus {
|
||||
box-shadow: 0 0 0 0.2rem rgba(231, 74, 59, 0.25);
|
||||
}
|
||||
|
||||
/* 客户评价样式 */
|
||||
.avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background: var(--light-color);
|
||||
|
||||
/* 性能优化样式 */
|
||||
.lazy-load {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.lazy-load.loaded {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.rating {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.team-card img {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.team-card:hover img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.spinner-border-sm {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.hero-section h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.hero-section p {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.display-5 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.min-vh-75 {
|
||||
min-height: 60vh;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.avatar i {
|
||||
font-size: 2rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.hero-section h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in-up {
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
/* 工具类 */
|
||||
.text-gradient {
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.bg-gradient-primary {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
|
||||
}
|
||||
|
||||
.shadow-sm {
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
/* 交互效果增强样式 */
|
||||
/* 返回顶部按钮 */
|
||||
.back-to-top {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: all 0.3s ease;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.back-to-top.show {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.back-to-top:hover {
|
||||
background: var(--secondary-color);
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* 移动端菜单动画 */
|
||||
.navbar-toggler {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.navbar-toggler.active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.navbar-collapse {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 页面过渡效果 */
|
||||
.page-entering {
|
||||
animation: pageEnter 0.3s ease-out;
|
||||
}
|
||||
|
||||
.page-leaving {
|
||||
animation: pageLeave 0.3s ease-in;
|
||||
}
|
||||
|
||||
@keyframes pageEnter {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pageLeave {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 加载状态样式 */
|
||||
.btn:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.spinner-border-sm {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
/* 增强悬停效果 */
|
||||
.card, .product-card, .news-card {
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn, .nav-link {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* 表单聚焦效果增强 */
|
||||
.form-control:focus {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 0.3rem rgba(76, 175, 80, 0.25);
|
||||
}
|
||||
|
||||
/* 滚动条美化 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--primary-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--secondary-color);
|
||||
}
|
||||
|
||||
/* 焦点可见性增强 */
|
||||
*:focus {
|
||||
outline: 2px solid var(--primary-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* 减少运动偏好 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
|
||||
.back-to-top {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
316
official_website/index.html
Normal file
316
official_website/index.html
Normal file
@@ -0,0 +1,316 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="爱鉴花 - 专业的AI花卉识别平台,提供精准的花卉识别服务,包含数万种花卉知识库和智能识别技术,准确率高达95%。">
|
||||
<meta name="keywords" content="花卉识别,AI识别,植物识别,花卉知识,智能识别,爱鉴花">
|
||||
<meta name="author" content="爱鉴花团队">
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:title" content="爱鉴花 - 智能花卉识别平台">
|
||||
<meta property="og:description" content="专业的AI花卉识别平台,提供精准的花卉识别服务,准确率高达95%。">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://aijianhua.com">
|
||||
<meta property="og:image" content="https://aijianhua.com/images/og-image.png">
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="爱鉴花 - 智能花卉识别平台">
|
||||
<meta name="twitter:description" content="专业的AI花卉识别平台,提供精准的花卉识别服务。">
|
||||
|
||||
<title>爱鉴花 - 智能花卉识别平台</title>
|
||||
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.7.2/css/all.css" rel="stylesheet">
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- 导航栏 -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white fixed-top shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="index.html">
|
||||
<i class="fas fa-flower text-primary me-2"></i>
|
||||
<span class="fw-bold text-primary">爱鉴花</span>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="index.html">首页</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="about.html">关于我们</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="products.html">产品介绍</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="news.html">新闻中心</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="contact.html">联系我们</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 英雄区域 -->
|
||||
<section class="hero-section bg-gradient-primary text-white py-5">
|
||||
<div class="container py-5">
|
||||
<div class="row align-items-center min-vh-75">
|
||||
<div class="col-lg-6">
|
||||
<h1 class="display-4 fw-bold mb-4">智能花卉识别,发现自然之美</h1>
|
||||
<p class="lead mb-4">爱鉴花为您提供精准的花卉识别服务,通过AI技术快速识别各种花卉植物,让您更好地了解和欣赏自然。</p>
|
||||
<div class="d-flex gap-3">
|
||||
<a href="#features" class="btn btn-light btn-lg px-4">了解更多</a>
|
||||
<a href="contact.html" class="btn btn-outline-light btn-lg px-4">立即体验</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<img src="images/hero-image.png" alt="花卉识别" class="img-fluid rounded shadow lazy-load" loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 特性介绍 -->
|
||||
<section id="features" class="py-5">
|
||||
<div class="container py-5">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="display-5 fw-bold text-dark mb-3">核心功能</h2>
|
||||
<p class="lead text-muted">爱鉴花为您提供全方位的花卉识别解决方案</p>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center p-4">
|
||||
<div class="feature-icon bg-primary text-white rounded-circle mx-auto mb-3">
|
||||
<i class="fas fa-camera fa-2x"></i>
|
||||
</div>
|
||||
<h5 class="card-title">智能识别</h5>
|
||||
<p class="card-text text-muted">通过拍照或上传图片,快速识别花卉种类,准确率高达95%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center p-4">
|
||||
<div class="feature-icon bg-success text-white rounded-circle mx-auto mb-3">
|
||||
<i class="fas fa-book fa-2x"></i>
|
||||
</div>
|
||||
<h5 class="card-title">知识库</h5>
|
||||
<p class="card-text text-muted">包含数万种花卉的详细信息,包括生长习性、养护方法等</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center p-4">
|
||||
<div class="feature-icon bg-info text-white rounded-circle mx-auto mb-3">
|
||||
<i class="fas fa-users fa-2x"></i>
|
||||
</div>
|
||||
<h5 class="card-title">社区交流</h5>
|
||||
<p class="card-text text-muted">与花卉爱好者分享经验,交流养护心得,共同成长</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 产品展示 -->
|
||||
<section class="bg-light py-5">
|
||||
<div class="container py-5">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="display-5 fw-bold text-dark mb-3">热门产品</h2>
|
||||
<p class="lead text-muted">探索我们的核心产品和服务</p>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card product-card border-0 shadow-sm h-100">
|
||||
<img src="images/product-1.jpg" class="card-img-top lazy-load" alt="微信小程序" loading="lazy">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title fw-bold">微信小程序</h5>
|
||||
<p class="card-text text-muted">随时随地识别花卉,操作简单便捷</p>
|
||||
<div class="mt-3">
|
||||
<span class="badge bg-success me-2">免费使用</span>
|
||||
<span class="badge bg-info">95%准确率</span>
|
||||
</div>
|
||||
<a href="products.html" class="btn btn-primary mt-3">立即体验</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card product-card border-0 shadow-sm h-100">
|
||||
<img src="images/product-2.jpg" class="card-img-top lazy-load" alt="API服务" loading="lazy">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title fw-bold">API服务</h5>
|
||||
<p class="card-text text-muted">为开发者提供稳定可靠的识别接口</p>
|
||||
<div class="mt-3">
|
||||
<span class="badge bg-primary me-2">¥99/月</span>
|
||||
<span class="badge bg-warning">1000次调用</span>
|
||||
</div>
|
||||
<a href="products.html" class="btn btn-primary mt-3">查看文档</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card product-card border-0 shadow-sm h-100">
|
||||
<img src="images/product-3.jpg" class="card-img-top lazy-load" alt="企业版" loading="lazy">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title fw-bold">企业版</h5>
|
||||
<p class="card-text text-muted">定制化解决方案,满足专业需求</p>
|
||||
<div class="mt-3">
|
||||
<span class="badge bg-dark me-2">私有部署</span>
|
||||
<span class="badge bg-success">专属支持</span>
|
||||
</div>
|
||||
<a href="contact.html" class="btn btn-primary mt-3">咨询方案</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 新闻动态 -->
|
||||
<section class="py-5">
|
||||
<div class="container py-5">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="display-5 fw-bold text-dark mb-3">最新动态</h2>
|
||||
<p class="lead text-muted">关注我们的最新进展和行业资讯</p>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card news-card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<small class="text-muted">2024-01-15</small>
|
||||
<h6 class="card-title mt-2">爱鉴花小程序正式上线</h6>
|
||||
<p class="card-text text-muted">经过数月开发,我们的微信小程序正式上线,欢迎体验。</p>
|
||||
<a href="news.html" class="text-primary text-decoration-none">阅读更多 →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card news-card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<small class="text-muted">2024-01-10</small>
|
||||
<h6 class="card-title mt-2">AI识别准确率再创新高</h6>
|
||||
<p class="card-text text-muted">通过算法优化,花卉识别准确率提升至95%。</p>
|
||||
<a href="news.html" class="text-primary text-decoration-none">阅读更多 →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card news-card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<small class="text-muted">2024-01-05</small>
|
||||
<h6 class="card-title mt-2">新增1000种花卉数据</h6>
|
||||
<p class="card-text text-muted">知识库新增1000种花卉信息,覆盖更全面。</p>
|
||||
<a href="news.html" class="text-primary text-decoration-none">阅读更多 →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-4">
|
||||
<a href="news.html" class="btn btn-primary">查看更多新闻</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="bg-dark text-white py-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-4 mb-4">
|
||||
<h5 class="mb-3">爱鉴花</h5>
|
||||
<p class="text-muted">智能花卉识别平台,让您更好地了解和欣赏自然之美。</p>
|
||||
<div class="social-links">
|
||||
<a href="#" class="text-white me-3"><i class="fab fa-weixin"></i></a>
|
||||
<a href="#" class="text-white me-3"><i class="fab fa-weibo"></i></a>
|
||||
<a href="#" class="text-white"><i class="fab fa-github"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 mb-4">
|
||||
<h6 class="mb-3">产品</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="#" class="text-muted text-decoration-none">微信小程序</a></li>
|
||||
<li><a href="#" class="text-muted text-decoration-none">API服务</a></li>
|
||||
<li><a href="#" class="text-muted text-decoration-none">企业版</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-2 mb-4">
|
||||
<h6 class="mb-3">支持</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="#" class="text-muted text-decoration-none">帮助中心</a></li>
|
||||
<li><a href="#" class="text-muted text-decoration-none">常见问题</a></li>
|
||||
<li><a href="#" class="text-muted text-decoration-none">联系我们</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-4 mb-4">
|
||||
<h6 class="mb-3">联系我们</h6>
|
||||
<p class="text-muted mb-1"><i class="fas fa-map-marker-alt me-2"></i>北京市海淀区中关村大街</p>
|
||||
<p class="text-muted mb-1"><i class="fas fa-phone me-2"></i>400-123-4567</p>
|
||||
<p class="text-muted"><i class="fas fa-envelope me-2"></i>contact@aijianhua.com</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-4">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6 text-center text-md-start">
|
||||
<p class="text-muted mb-0">© 2024 爱鉴花. 保留所有权利.</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-center text-md-end">
|
||||
<p class="text-muted mb-0">
|
||||
<a href="#" class="text-muted text-decoration-none me-3">隐私政策</a>
|
||||
<a href="#" class="text-muted text-decoration-none me-3">服务条款</a>
|
||||
<a href="#" class="text-muted text-decoration-none">京ICP备12345678号</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-3">
|
||||
<p class="text-muted small">
|
||||
友情链接:
|
||||
<a href="#" class="text-muted text-decoration-none me-2">中国花卉协会</a>
|
||||
<a href="#" class="text-muted text-decoration-none me-2">植物智</a>
|
||||
<a href="#" class="text-muted text-decoration-none">园林在线</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
|
||||
<!-- Schema.org结构化数据 -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
"name": "爱鉴花",
|
||||
"url": "https://aijianhua.com",
|
||||
"logo": "https://aijianhua.com/images/logo.png",
|
||||
"description": "专业的AI花卉识别平台,提供精准的花卉识别服务",
|
||||
"sameAs": [
|
||||
"https://weixin.qq.com/",
|
||||
"https://github.com/aijianhua"
|
||||
]
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "爱鉴花",
|
||||
"url": "https://aijianhua.com",
|
||||
"potentialAction": {
|
||||
"@type": "SearchAction",
|
||||
"target": "https://aijianhua.com/search?q={search_term_string}",
|
||||
"query-input": "required name=search_term_string"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
435
official_website/js/main.js
Normal file
435
official_website/js/main.js
Normal file
@@ -0,0 +1,435 @@
|
||||
// 主JavaScript文件 - 爱鉴花官方网站
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 初始化函数
|
||||
initWebsite();
|
||||
});
|
||||
|
||||
function initWebsite() {
|
||||
// 初始化导航栏滚动效果
|
||||
initNavbarScroll();
|
||||
|
||||
// 初始化滚动动画
|
||||
initScrollAnimation();
|
||||
|
||||
// 初始化表单验证
|
||||
initFormValidation();
|
||||
|
||||
// 初始化计数器动画
|
||||
initCounterAnimation();
|
||||
|
||||
// 初始化图片懒加载
|
||||
initLazyLoading();
|
||||
|
||||
// 初始化移动端菜单
|
||||
initMobileMenu();
|
||||
|
||||
// 初始化平滑滚动
|
||||
initSmoothScroll();
|
||||
|
||||
// 初始化悬停动画
|
||||
initHoverEffects();
|
||||
|
||||
// 初始化加载状态
|
||||
initLoadingStates();
|
||||
|
||||
// 初始化页面过渡效果
|
||||
initPageTransitions();
|
||||
}
|
||||
|
||||
// 导航栏滚动效果
|
||||
function initNavbarScroll() {
|
||||
const navbar = document.querySelector('.navbar');
|
||||
|
||||
window.addEventListener('scroll', function() {
|
||||
if (window.scrollY > 100) {
|
||||
navbar.classList.add('navbar-scrolled');
|
||||
} else {
|
||||
navbar.classList.remove('navbar-scrolled');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 滚动动画效果
|
||||
function initScrollAnimation() {
|
||||
const animatedElements = document.querySelectorAll('.card, .feature-icon');
|
||||
|
||||
const observerOptions = {
|
||||
threshold: 0.1,
|
||||
rootMargin: '0px 0px -50px 0px'
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(function(entries) {
|
||||
entries.forEach(function(entry) {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('fade-in-up');
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
animatedElements.forEach(function(element) {
|
||||
observer.observe(element);
|
||||
});
|
||||
}
|
||||
|
||||
// 表单验证
|
||||
function initFormValidation() {
|
||||
const forms = document.querySelectorAll('form');
|
||||
|
||||
forms.forEach(function(form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
if (!validateForm(form)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function validateForm(form) {
|
||||
let isValid = true;
|
||||
const inputs = form.querySelectorAll('input[required], textarea[required]');
|
||||
|
||||
inputs.forEach(function(input) {
|
||||
if (!input.value.trim()) {
|
||||
showError(input, '此字段为必填项');
|
||||
isValid = false;
|
||||
} else if (input.type === 'email' && !isValidEmail(input.value)) {
|
||||
showError(input, '请输入有效的邮箱地址');
|
||||
isValid = false;
|
||||
} else {
|
||||
clearError(input);
|
||||
}
|
||||
});
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
function isValidEmail(email) {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
function showError(input, message) {
|
||||
clearError(input);
|
||||
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'invalid-feedback';
|
||||
errorDiv.textContent = message;
|
||||
|
||||
input.classList.add('is-invalid');
|
||||
input.parentNode.appendChild(errorDiv);
|
||||
}
|
||||
|
||||
function clearError(input) {
|
||||
input.classList.remove('is-invalid');
|
||||
const errorDiv = input.parentNode.querySelector('.invalid-feedback');
|
||||
if (errorDiv) {
|
||||
errorDiv.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 计数器动画
|
||||
function initCounterAnimation() {
|
||||
const counters = document.querySelectorAll('.counter');
|
||||
|
||||
if (counters.length > 0) {
|
||||
const observer = new IntersectionObserver(function(entries) {
|
||||
entries.forEach(function(entry) {
|
||||
if (entry.isIntersecting) {
|
||||
animateCounters();
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(counters[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function animateCounters() {
|
||||
const counters = document.querySelectorAll('.counter');
|
||||
|
||||
counters.forEach(function(counter) {
|
||||
const target = parseInt(counter.getAttribute('data-target'));
|
||||
const duration = 2000; // 2 seconds
|
||||
const frameDuration = 1000 / 60; // 60fps
|
||||
const totalFrames = Math.round(duration / frameDuration);
|
||||
let currentFrame = 0;
|
||||
|
||||
const updateCounter = function() {
|
||||
currentFrame++;
|
||||
const progress = currentFrame / totalFrames;
|
||||
const currentValue = Math.round(target * progress);
|
||||
|
||||
counter.textContent = currentValue.toLocaleString();
|
||||
|
||||
if (currentFrame < totalFrames) {
|
||||
requestAnimationFrame(updateCounter);
|
||||
} else {
|
||||
counter.textContent = target.toLocaleString();
|
||||
}
|
||||
};
|
||||
|
||||
updateCounter();
|
||||
});
|
||||
}
|
||||
|
||||
// 图片懒加载
|
||||
function initLazyLoading() {
|
||||
if ('IntersectionObserver' in window) {
|
||||
const lazyImages = document.querySelectorAll('img.lazy-load');
|
||||
|
||||
const imageObserver = new IntersectionObserver(function(entries) {
|
||||
entries.forEach(function(entry) {
|
||||
if (entry.isIntersecting) {
|
||||
const img = entry.target;
|
||||
// 确保图片完全加载后显示
|
||||
img.onload = function() {
|
||||
img.classList.add('loaded');
|
||||
};
|
||||
// 处理data-src属性或直接使用src
|
||||
if (img.hasAttribute('data-src')) {
|
||||
img.src = img.getAttribute('data-src');
|
||||
img.removeAttribute('data-src');
|
||||
}
|
||||
imageObserver.unobserve(img);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
threshold: 0.1,
|
||||
rootMargin: '50px 0px'
|
||||
});
|
||||
|
||||
lazyImages.forEach(function(img) {
|
||||
imageObserver.observe(img);
|
||||
});
|
||||
} else {
|
||||
// 浏览器不支持IntersectionObserver时的降级方案
|
||||
const lazyImages = document.querySelectorAll('img.lazy-load');
|
||||
lazyImages.forEach(function(img) {
|
||||
img.classList.add('loaded');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 移动端菜单处理
|
||||
function initMobileMenu() {
|
||||
const navbarToggler = document.querySelector('.navbar-toggler');
|
||||
const navbarCollapse = document.querySelector('.navbar-collapse');
|
||||
|
||||
if (navbarToggler && navbarCollapse) {
|
||||
navbarToggler.addEventListener('click', function() {
|
||||
navbarCollapse.classList.toggle('show');
|
||||
// 添加菜单动画效果
|
||||
navbarToggler.classList.toggle('active');
|
||||
});
|
||||
|
||||
// 点击菜单项后自动关闭菜单(移动端)
|
||||
const navLinks = document.querySelectorAll('.navbar-nav .nav-link');
|
||||
navLinks.forEach(function(link) {
|
||||
link.addEventListener('click', function() {
|
||||
if (window.innerWidth < 992) {
|
||||
navbarCollapse.classList.remove('show');
|
||||
navbarToggler.classList.remove('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 平滑滚动效果
|
||||
function initSmoothScroll() {
|
||||
// 内部链接平滑滚动
|
||||
const internalLinks = document.querySelectorAll('a[href^="#"]');
|
||||
|
||||
internalLinks.forEach(function(link) {
|
||||
link.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const targetId = this.getAttribute('href').substring(1);
|
||||
const targetElement = document.getElementById(targetId);
|
||||
|
||||
if (targetElement) {
|
||||
const offsetTop = targetElement.offsetTop - 80; // 考虑导航栏高度
|
||||
|
||||
window.scrollTo({
|
||||
top: offsetTop,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 返回顶部按钮
|
||||
const backToTopBtn = document.createElement('button');
|
||||
backToTopBtn.className = 'back-to-top';
|
||||
backToTopBtn.innerHTML = '↑';
|
||||
backToTopBtn.setAttribute('aria-label', '返回顶部');
|
||||
|
||||
backToTopBtn.addEventListener('click', function() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
});
|
||||
|
||||
// 滚动时显示/隐藏返回顶部按钮
|
||||
window.addEventListener('scroll', debounce(function() {
|
||||
if (window.scrollY > 500) {
|
||||
backToTopBtn.classList.add('show');
|
||||
} else {
|
||||
backToTopBtn.classList.remove('show');
|
||||
}
|
||||
}, 100));
|
||||
|
||||
document.body.appendChild(backToTopBtn);
|
||||
}
|
||||
|
||||
// 悬停动画效果
|
||||
function initHoverEffects() {
|
||||
// 卡片悬停效果
|
||||
const cards = document.querySelectorAll('.card, .product-card, .news-card');
|
||||
|
||||
cards.forEach(function(card) {
|
||||
card.addEventListener('mouseenter', function() {
|
||||
this.style.transform = 'translateY(-5px) scale(1.02)';
|
||||
this.style.boxShadow = '0 10px 25px rgba(0, 0, 0, 0.15)';
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', function() {
|
||||
this.style.transform = 'translateY(0) scale(1)';
|
||||
this.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)';
|
||||
});
|
||||
});
|
||||
|
||||
// 按钮悬停效果
|
||||
const buttons = document.querySelectorAll('.btn, .nav-link');
|
||||
|
||||
buttons.forEach(function(button) {
|
||||
button.addEventListener('mouseenter', function() {
|
||||
this.style.transform = 'translateY(-2px)';
|
||||
});
|
||||
|
||||
button.addEventListener('mouseleave', function() {
|
||||
this.style.transform = 'translateY(0)';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 加载状态指示器
|
||||
function initLoadingStates() {
|
||||
const forms = document.querySelectorAll('form');
|
||||
|
||||
forms.forEach(function(form) {
|
||||
form.addEventListener('submit', function() {
|
||||
const submitBtn = this.querySelector('button[type="submit"]');
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 处理中...';
|
||||
|
||||
// 3秒后恢复按钮状态(防止无限加载)
|
||||
setTimeout(function() {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = submitBtn.getAttribute('data-original-text') || '提交';
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 保存按钮原始文本
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const submitButtons = document.querySelectorAll('button[type="submit"]');
|
||||
submitButtons.forEach(function(btn) {
|
||||
btn.setAttribute('data-original-text', btn.textContent);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 页面过渡效果
|
||||
function initPageTransitions() {
|
||||
// 链接点击时的过渡效果
|
||||
const links = document.querySelectorAll('a:not([href^="#"]):not([href^="javascript"])');
|
||||
|
||||
links.forEach(function(link) {
|
||||
link.addEventListener('click', function(e) {
|
||||
if (this.href && !this.target && !this.hasAttribute('download')) {
|
||||
e.preventDefault();
|
||||
|
||||
// 添加页面离开动画
|
||||
document.body.classList.add('page-leaving');
|
||||
|
||||
setTimeout(function() {
|
||||
window.location.href = link.href;
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 页面进入动画
|
||||
document.body.classList.add('page-entering');
|
||||
|
||||
setTimeout(function() {
|
||||
document.body.classList.remove('page-entering');
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// 工具函数:防抖
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function() {
|
||||
const context = this;
|
||||
const args = arguments;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(function() {
|
||||
func.apply(context, args);
|
||||
}, wait);
|
||||
};
|
||||
}
|
||||
|
||||
// 工具函数:节流
|
||||
function throttle(func, limit) {
|
||||
let inThrottle;
|
||||
return function() {
|
||||
const args = arguments;
|
||||
const context = this;
|
||||
if (!inThrottle) {
|
||||
func.apply(context, args);
|
||||
inThrottle = true;
|
||||
setTimeout(function() {
|
||||
inThrottle = false;
|
||||
}, limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 页面性能监控
|
||||
function monitorPerformance() {
|
||||
// 页面加载时间
|
||||
window.addEventListener('load', function() {
|
||||
const loadTime = performance.timing.loadEventEnd - performance.timing.navigationStart;
|
||||
console.log('页面加载时间:', loadTime + 'ms');
|
||||
});
|
||||
|
||||
// 最大内容绘制(LCP)
|
||||
new PerformanceObserver(function(entryList) {
|
||||
const entries = entryList.getEntries();
|
||||
const lastEntry = entries[entries.length - 1];
|
||||
console.log('LCP:', lastEntry.startTime);
|
||||
}).observe({type: 'largest-contentful-paint', buffered: true});
|
||||
}
|
||||
|
||||
// 错误监控
|
||||
function monitorErrors() {
|
||||
window.addEventListener('error', function(e) {
|
||||
console.error('JavaScript错误:', e.error);
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', function(e) {
|
||||
console.error('未处理的Promise拒绝:', e.reason);
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化性能和错误监控
|
||||
monitorPerformance();
|
||||
monitorErrors();
|
||||
386
official_website/news.html
Normal file
386
official_website/news.html
Normal file
@@ -0,0 +1,386 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>新闻中心 - 爱鉴花</title>
|
||||
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.7.2/css/all.css" rel="stylesheet">
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- 导航栏 -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white fixed-top shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="index.html">
|
||||
<i class="fas fa-flower text-primary me-2"></i>
|
||||
<span class="fw-bold text-primary">爱鉴花</span>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.html">首页</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="about.html">关于我们</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="products.html">产品介绍</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="news.html">新闻中心</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="contact.html">联系我们</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 页面标题 -->
|
||||
<section class="page-header bg-gradient-primary text-white py-5">
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center">
|
||||
<h1 class="display-4 fw-bold mb-3">新闻中心</h1>
|
||||
<p class="lead">关注爱鉴花的最新动态和行业资讯</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 新闻内容 -->
|
||||
<section class="py-5">
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<!-- 主内容区 -->
|
||||
<div class="col-lg-8">
|
||||
<!-- 公司动态 -->
|
||||
<div class="mb-5">
|
||||
<h2 class="fw-bold mb-4 text-dark">公司动态</h2>
|
||||
|
||||
<!-- 新闻文章1 -->
|
||||
<article class="card news-article border-0 shadow-sm mb-4">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-4">
|
||||
<img src="images/news-1.jpg" class="img-fluid h-100 object-fit-cover rounded-start" alt="小程序上线">
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="badge bg-primary me-2">公司新闻</span>
|
||||
<small class="text-muted">2024-01-15</small>
|
||||
</div>
|
||||
<h3 class="h4 card-title">爱鉴花微信小程序正式上线</h3>
|
||||
<p class="card-text text-muted">
|
||||
经过数月的精心开发和测试,爱鉴花微信小程序今日正式上线。
|
||||
用户可以通过微信搜索"爱鉴花"或扫描二维码即可体验智能花卉识别服务。
|
||||
</p>
|
||||
<a href="news-detail.html" class="btn btn-outline-primary btn-sm">阅读全文</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- 新闻文章2 -->
|
||||
<article class="card news-article border-0 shadow-sm mb-4">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-4">
|
||||
<img src="images/news-2.jpg" class="img-fluid h-100 object-fit-cover rounded-start" alt="AI识别">
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="badge bg-primary me-2">技术突破</span>
|
||||
<small class="text-muted">2024-01-10</small>
|
||||
</div>
|
||||
<h3 class="h4 card-title">AI识别准确率突破95%大关</h3>
|
||||
<p class="card-text text-muted">
|
||||
通过最新的深度学习算法优化,爱鉴花的AI识别准确率成功突破95%,
|
||||
在花卉识别领域达到行业领先水平。
|
||||
</p>
|
||||
<a href="news-detail.html" class="btn btn-outline-primary btn-sm">阅读全文</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- 新闻文章3 -->
|
||||
<article class="card news-article border-0 shadow-sm mb-4">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-4">
|
||||
<img src="images/news-3.jpg" class="img-fluid h-100 object-fit-cover rounded-start" alt="数据更新">
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="badge bg-primary me-2">产品更新</span>
|
||||
<small class="text-muted">2024-01-05</small>
|
||||
</div>
|
||||
<h3 class="h4 card-title">知识库新增1000种花卉数据</h3>
|
||||
<p class="card-text text-muted">
|
||||
本次更新新增1000种花卉的详细信息,包括生长习性、养护方法、药用价值等,
|
||||
为用户提供更全面的植物知识服务。
|
||||
</p>
|
||||
<a href="news-detail.html" class="btn btn-outline-primary btn-sm">阅读全文</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- 行业资讯 -->
|
||||
<div class="mb-5">
|
||||
<h2 class="fw-bold mb-4 text-dark">行业资讯</h2>
|
||||
|
||||
<!-- 行业新闻1 -->
|
||||
<article class="card news-article border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="badge bg-info me-2">行业动态</span>
|
||||
<small class="text-muted">2024-01-08</small>
|
||||
</div>
|
||||
<h3 class="h5 card-title">人工智能在植物识别领域的应用前景</h3>
|
||||
<p class="card-text text-muted">
|
||||
随着深度学习技术的发展,AI在植物识别领域的应用越来越广泛。
|
||||
专家预测,未来几年该领域将迎来爆发式增长。
|
||||
</p>
|
||||
<a href="news-detail.html" class="btn btn-outline-info btn-sm">阅读全文</a>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- 行业新闻2 -->
|
||||
<article class="card news-article border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="badge bg-info me-2">市场分析</span>
|
||||
<small class="text-muted">2024-01-03</small>
|
||||
</div>
|
||||
<h3 class="h5 card-title">2024年植物识别APP市场趋势分析</h3>
|
||||
<p class="card-text text-muted">
|
||||
最新市场研究报告显示,植物识别类APP用户规模持续增长,
|
||||
预计2024年全球市场规模将达到50亿美元。
|
||||
</p>
|
||||
<a href="news-detail.html" class="btn btn-outline-info btn-sm">阅读全文</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- 媒体报道 -->
|
||||
<div>
|
||||
<h2 class="fw-bold mb-4 text-dark">媒体报道</h2>
|
||||
|
||||
<!-- 媒体报道1 -->
|
||||
<article class="card news-article border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="badge bg-warning me-2">媒体报道</span>
|
||||
<small class="text-muted">2024-01-12</small>
|
||||
</div>
|
||||
<h3 class="h5 card-title">科技日报:爱鉴花用AI技术连接人与自然</h3>
|
||||
<p class="card-text text-muted">
|
||||
"爱鉴花团队通过先进的AI技术,让普通用户也能轻松识别植物,
|
||||
这种技术创新对普及植物知识具有重要意义。"
|
||||
</p>
|
||||
<a href="news-detail.html" class="btn btn-outline-warning btn-sm">阅读全文</a>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- 媒体报道2 -->
|
||||
<article class="card news-article border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="badge bg-warning me-2">专访</span>
|
||||
<small class="text-muted">2024-01-07</small>
|
||||
</div>
|
||||
<h3 class="h5 card-title">创业邦专访:爱鉴花创始人的AI情怀</h3>
|
||||
<p class="card-text text-muted">
|
||||
"我们希望通过技术让更多人关注和热爱自然,
|
||||
这是爱鉴花团队的初心和使命。"——创始人张明
|
||||
</p>
|
||||
<a href="news-detail.html" class="btn btn-outline-warning btn-sm">阅读全文</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination justify-content-center">
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" href="#" tabindex="-1">上一页</a>
|
||||
</li>
|
||||
<li class="page-item active">
|
||||
<a class="page-link" href="#">1</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="#">2</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="#">3</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="#">下一页</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- 侧边栏 -->
|
||||
<div class="col-lg-4">
|
||||
<!-- 搜索框 -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">搜索新闻</h5>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="输入关键词...">
|
||||
<button class="btn btn-primary" type="button">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新闻分类 -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">新闻分类</h5>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<a href="#" class="text-decoration-none text-dark">公司新闻</a>
|
||||
<span class="badge bg-primary rounded-pill">12</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<a href="#" class="text-decoration-none text-dark">技术突破</a>
|
||||
<span class="badge bg-primary rounded-pill">8</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<a href="#" class="text-decoration-none text-dark">产品更新</a>
|
||||
<span class="badge bg-primary rounded-pill">15</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<a href="#" class="text-decoration-none text-dark">行业动态</a>
|
||||
<span class="badge bg-primary rounded-pill">6</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<a href="#" class="text-decoration-none text-dark">媒体报道</a>
|
||||
<span class="badge bg-primary rounded-pill">9</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 热门新闻 -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">热门新闻</h5>
|
||||
<div class="list-group list-group-flush">
|
||||
<a href="#" class="list-group-item list-group-item-action">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h6 class="mb-1">小程序上线首日用户破万</h6>
|
||||
<small class="text-muted">3天前</small>
|
||||
</div>
|
||||
<small class="text-muted">阅读量: 2,345</small>
|
||||
</a>
|
||||
<a href="#" class="list-group-item list-group-item-action">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h6 class="mb-1">AI识别准确率行业领先</h6>
|
||||
<small class="text-muted">1周前</small>
|
||||
</div>
|
||||
<small class="text-muted">阅读量: 1,876</small>
|
||||
</a>
|
||||
<a href="#" class="list-group-item list-group-item-action">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h6 class="mb-1">新增千种花卉数据</h6>
|
||||
<small class="text-muted">2周前</small>
|
||||
</div>
|
||||
<small class="text-muted">阅读量: 1,532</small>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 订阅更新 -->
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title">订阅更新</h5>
|
||||
<p class="text-muted small">第一时间获取最新新闻动态</p>
|
||||
<div class="input-group mb-3">
|
||||
<input type="email" class="form-control" placeholder="您的邮箱">
|
||||
<button class="btn btn-primary" type="button">订阅</button>
|
||||
</div>
|
||||
<small class="text-muted">我们尊重您的隐私,绝不会分享您的信息</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="bg-dark text-white py-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-4 mb-4">
|
||||
<h5 class="mb-3">爱鉴花</h5>
|
||||
<p class="text-muted">智能花卉识别平台,让您更好地了解和欣赏自然之美。</p>
|
||||
<div class="social-links">
|
||||
<a href="#" class="text-white me-3"><i class="fab fa-weixin"></i></a>
|
||||
<a href="#" class="text-white me-3"><i class="fab fa-weibo"></i></a>
|
||||
<a href="#" class="text-white"><i class="fab fa-github"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 mb-4">
|
||||
<h6 class="mb-3">产品</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="#" class="text-muted text-decoration-none">微信小程序</a></li>
|
||||
<li><a href="#" class="text-muted text-decoration-none">API服务</a></li>
|
||||
<li><a href="#" class="text-muted text-decoration-none">企业版</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-2 mb-4">
|
||||
<h6 class="mb-3">支持</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="#" class="text-muted text-decoration-none">帮助中心</a></li>
|
||||
<li><a href="#" class="text-muted text-decoration-none">常见问题</a></li>
|
||||
<li><a href="#" class="text-muted text-decoration-none">联系我们</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-4 mb-4">
|
||||
<h6 class="mb-3">联系我们</h6>
|
||||
<p class="text-muted mb-1"><i class="fas fa-map-marker-alt me-2"></i>北京市海淀区中关村大街</p>
|
||||
<p class="text-muted mb-1"><i class="fas fa-phone me-2"></i>400-123-4567</p>
|
||||
<p class="text-muted"><i class="fas fa-envelope me-2"></i>contact@aijianhua.com</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-4">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6 text-center text-md-start">
|
||||
<p class="text-muted mb-0">© 2024 爱鉴花. 保留所有权利.</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-center text-md-end">
|
||||
<p class="text-muted mb-0">
|
||||
<a href="#" class="text-muted text-decoration-none me-3">隐私政策</a>
|
||||
<a href="#" class="text-muted text-decoration-none me-3">服务条款</a>
|
||||
<a href="#" class="text-muted text-decoration-none">京ICP备12345678号</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-3">
|
||||
<p class="text-muted small">
|
||||
友情链接:
|
||||
<a href="#" class="text-muted text-decoration-none me-2">中国花卉协会</a>
|
||||
<a href="#" class="text-muted text-decoration-none me-2">植物智</a>
|
||||
<a href="#" class="text-muted text-decoration-none">园林在线</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
537
official_website/products.html
Normal file
537
official_website/products.html
Normal file
@@ -0,0 +1,537 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="爱鉴花产品介绍 - 微信小程序、API服务和企业版解决方案,提供95%准确率的AI花卉识别服务,满足不同用户需求。">
|
||||
<meta name="keywords" content="花卉识别API,微信小程序,企业解决方案,AI识别服务,植物识别技术">
|
||||
<meta name="author" content="爱鉴花团队">
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:title" content="产品介绍 - 爱鉴花">
|
||||
<meta property="og:description" content="探索爱鉴花的智能花卉识别解决方案,包括微信小程序、API服务和企业版定制方案。">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://aijianhua.com/products.html">
|
||||
<meta property="og:image" content="https://aijianhua.com/images/products-og.png">
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="产品介绍 - 爱鉴花">
|
||||
<meta name="twitter:description" content="探索爱鉴花的智能花卉识别解决方案。">
|
||||
|
||||
<title>产品介绍 - 爱鉴花</title>
|
||||
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.7.2/css/all.css" rel="stylesheet">
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- 导航栏 -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white fixed-top shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="index.html">
|
||||
<i class="fas fa-flower text-primary me-2"></i>
|
||||
<span class="fw-bold text-primary">爱鉴花</span>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.html">首页</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="about.html">关于我们</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="products.html">产品介绍</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="news.html">新闻中心</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="contact.html">联系我们</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 页面标题 -->
|
||||
<section class="page-header bg-gradient-primary text-white py-5">
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center">
|
||||
<h1 class="display-4 fw-bold mb-3">产品介绍</h1>
|
||||
<p class="lead">探索爱鉴花的智能花卉识别解决方案</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 产品概览 -->
|
||||
<section class="py-5">
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center mb-5">
|
||||
<h2 class="display-5 fw-bold text-dark">我们的产品体系</h2>
|
||||
<p class="lead text-muted">为不同用户群体提供专业的花卉识别服务</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 微信小程序 -->
|
||||
<div class="row align-items-center mb-5">
|
||||
<div class="col-lg-6">
|
||||
<img src="images/product-mini-program.jpg" alt="微信小程序" class="img-fluid rounded shadow lazy-load" loading="lazy">
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<h3 class="fw-bold mb-3">微信小程序</h3>
|
||||
<p class="text-muted mb-4">
|
||||
随时随地识别花卉,操作简单,使用便捷。无需下载安装,
|
||||
打开微信即可使用,满足日常花卉识别需求。
|
||||
</p>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>拍照即时识别</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>详细的植物信息</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>养护知识指导</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>识别历史记录</li>
|
||||
</ul>
|
||||
<div class="mt-4">
|
||||
<a href="#" class="btn btn-primary me-3">立即体验</a>
|
||||
<a href="#" class="btn btn-outline-primary">了解更多</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API服务 -->
|
||||
<div class="row align-items-center mb-5">
|
||||
<div class="col-lg-6 order-lg-2">
|
||||
<img src="images/product-api.jpg" alt="API服务" class="img-fluid rounded shadow lazy-load" loading="lazy">
|
||||
</div>
|
||||
<div class="col-lg-6 order-lg-1">
|
||||
<h3 class="fw-bold mb-3">API服务</h3>
|
||||
<p class="text-muted mb-4">
|
||||
为开发者和企业提供稳定可靠的花卉识别API接口,
|
||||
轻松集成到您的应用中,扩展植物识别功能。
|
||||
</p>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>RESTful API接口</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>高并发支持</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>99.9%可用性</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>详细技术文档</li>
|
||||
</ul>
|
||||
<div class="mt-4">
|
||||
<a href="#" class="btn btn-primary me-3">查看文档</a>
|
||||
<a href="#" class="btn btn-outline-primary">申请试用</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 企业版 -->
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-6">
|
||||
<img src="images/product-enterprise.jpg" alt="企业版" class="img-fluid rounded shadow lazy-load" loading="lazy">
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<h3 class="fw-bold mb-3">企业版解决方案</h3>
|
||||
<p class="text-muted mb-4">
|
||||
为园林公司、教育机构、科研单位等提供定制化的
|
||||
花卉识别解决方案,满足专业级应用需求。
|
||||
</p>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>私有化部署</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>定制化模型训练</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>专业技术支持</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>批量处理功能</li>
|
||||
</ul>
|
||||
<div class="mt-4">
|
||||
<a href="contact.html" class="btn btn-primary me-3">联系我们</a>
|
||||
<a href="#" class="btn btn-outline-primary">方案详情</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 功能对比 -->
|
||||
<section class="bg-light py-5">
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center mb-5">
|
||||
<h2 class="display-5 fw-bold text-dark">功能对比</h2>
|
||||
<p class="lead text-muted">选择最适合您的产品方案</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="card pricing-card border-0 shadow-sm text-center h-100">
|
||||
<div class="card-header bg-white py-4">
|
||||
<h4 class="fw-bold">微信小程序</h4>
|
||||
<div class="price">
|
||||
<span class="h2 text-primary">免费</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<ul class="list-unstyled mb-4">
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>基础花卉识别</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>植物信息查询</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>识别历史记录</li>
|
||||
<li class="mb-2"><i class="fas fa-times text-muted me-2"></i>批量识别</li>
|
||||
<li class="mb-2"><i class="fas fa-times text-muted me-2"></i>API访问</li>
|
||||
</ul>
|
||||
<a href="#" class="btn btn-outline-primary w-100">开始使用</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="card pricing-card border-0 shadow-sm text-center h-100">
|
||||
<div class="card-header bg-primary text-white py-4">
|
||||
<h4 class="fw-bold">API服务</h4>
|
||||
<div class="price">
|
||||
<span class="h2">¥99</span>
|
||||
<span class="text-white-50">/月</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<ul class="list-unstyled mb-4">
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>1000次API调用</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>高精度识别</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>技术支持</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>批量识别</li>
|
||||
<li class="mb-2"><i class="fas fa-times text-muted me-2"></i>私有化部署</li>
|
||||
</ul>
|
||||
<a href="#" class="btn btn-primary w-100">立即订阅</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="card pricing-card border-0 shadow-sm text-center h-100">
|
||||
<div class="card-header bg-dark text-white py-4">
|
||||
<h4 class="fw-bold">企业版</h4>
|
||||
<div class="price">
|
||||
<span class="h2">定制</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<ul class="list-unstyled mb-4">
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>无限次调用</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>私有化部署</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>定制模型训练</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>专属技术支持</li>
|
||||
<li class="mb-2"><i class="fas fa-check text-success me-2"></i>批量处理</li>
|
||||
</ul>
|
||||
<a href="contact.html" class="btn btn-dark w-100">咨询方案</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 技术优势 -->
|
||||
<section class="py-5">
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center mb-5">
|
||||
<h2 class="display-5 fw-bold text-dark">技术优势</h2>
|
||||
<p class="lead text-muted">领先的AI技术,卓越的用户体验</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="text-center">
|
||||
<div class="feature-icon bg-primary text-white rounded-circle mx-auto mb-3">
|
||||
<i class="fas fa-brain fa-2x"></i>
|
||||
</div>
|
||||
<h5 class="fw-bold">深度学习算法</h5>
|
||||
<p class="text-muted">
|
||||
基于深度卷积神经网络,
|
||||
实现高精度花卉识别
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="text-center">
|
||||
<div class="feature-icon bg-success text-white rounded-circle mx-auto mb-3">
|
||||
<i class="fas fa-database fa-2x"></i>
|
||||
</div>
|
||||
<h5 class="fw-bold">海量数据训练</h5>
|
||||
<p class="text-muted">
|
||||
超过100万张花卉图像训练,
|
||||
覆盖5000+种植物
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="text-center">
|
||||
<div class="feature-icon bg-info text-white rounded-circle mx-auto mb-3">
|
||||
<i class="fas fa-bolt fa-2x"></i>
|
||||
</div>
|
||||
<h5 class="fw-bold">快速响应</h5>
|
||||
<p class="text-muted">
|
||||
平均识别时间小于2秒,
|
||||
提供流畅的用户体验
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="text-center">
|
||||
<div class="feature-icon bg-warning text-white rounded-circle mx-auto mb-3">
|
||||
<i class="fas fa-mobile-alt fa-2x"></i>
|
||||
</div>
|
||||
<h5 class="fw-bold">多平台支持</h5>
|
||||
<p class="text-muted">
|
||||
支持iOS、Android、Web等多平台,
|
||||
随时随地使用
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="text-center">
|
||||
<div class="feature-icon bg-danger text-white rounded-circle mx-auto mb-3">
|
||||
<i class="fas fa-shield-alt fa-2x"></i>
|
||||
</div>
|
||||
<h5 class="fw-bold">数据安全</h5>
|
||||
<p class="text-muted">
|
||||
HTTPS加密传输,
|
||||
确保用户数据安全
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="text-center">
|
||||
<div class="feature-icon bg-secondary text-white rounded-circle mx-auto mb-3">
|
||||
<i class="fas fa-sync fa-2x"></i>
|
||||
</div>
|
||||
<h5 class="fw-bold">持续优化</h5>
|
||||
<p class="text-muted">
|
||||
算法持续迭代优化,
|
||||
不断提升识别准确率
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 客户评价 -->
|
||||
<section class="py-5">
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center mb-5">
|
||||
<h2 class="display-5 fw-bold text-dark">客户评价</h2>
|
||||
<p class="lead text-muted">听听我们的用户怎么说</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body text-center p-4">
|
||||
<div class="avatar mx-auto mb-3">
|
||||
<i class="fas fa-user-circle fa-3x text-primary"></i>
|
||||
</div>
|
||||
<h5 class="fw-bold mb-2">张老师</h5>
|
||||
<p class="text-muted small mb-3">生物教师</p>
|
||||
<p class="text-muted">
|
||||
"爱鉴花小程序让我的生物课变得生动有趣,学生们现在对植物识别充满热情!"
|
||||
</p>
|
||||
<div class="rating text-warning">
|
||||
<i class="fas fa-star"></i>
|
||||
<i class="fas fa-star"></i>
|
||||
<i class="fas fa-star"></i>
|
||||
<i class="fas fa-star"></i>
|
||||
<i class="fas fa-star"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body text-center p-4">
|
||||
<div class="avatar mx-auto mb-3">
|
||||
<i class="fas fa-user-circle fa-3x text-primary"></i>
|
||||
</div>
|
||||
<h5 class="fw-bold mb-2">李经理</h5>
|
||||
<p class="text-muted small mb-3">园林公司</p>
|
||||
<p class="text-muted">
|
||||
"API接口非常稳定,集成到我们的园林管理系统中,大大提高了工作效率。"
|
||||
</p>
|
||||
<div class="rating text-warning">
|
||||
<i class="fas fa-star"></i>
|
||||
<i class="fas fa-star"></i>
|
||||
<i class="fas fa-star"></i>
|
||||
<i class="fas fa-star"></i>
|
||||
<i class="fas fa-star"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-body text-center p-4">
|
||||
<div class="avatar mx-auto mb-3">
|
||||
<i class="fas fa-user-circle fa-3x text-primary"></i>
|
||||
</div>
|
||||
<h5 class="fw-bold mb-2">王女士</h5>
|
||||
<p class="text-muted small mb-3">花卉爱好者</p>
|
||||
<p class="text-muted">
|
||||
"识别准确率很高,养护知识也很实用,现在养花再也不用担心认错品种了!"
|
||||
</p>
|
||||
<div class="rating text-warning">
|
||||
<i class="fas fa-star"></i>
|
||||
<i class="fas fa-star"></i>
|
||||
<i class="fas fa-star"></i>
|
||||
<i class="fas fa-star"></i>
|
||||
<i class="fas fa-star"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="bg-dark text-white py-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-4 mb-4">
|
||||
<h5 class="mb-3">爱鉴花</h5>
|
||||
<p class="text-muted">智能花卉识别平台,让您更好地了解和欣赏自然之美。</p>
|
||||
<div class="social-links">
|
||||
<a href="#" class="text-white me-3"><i class="fab fa-weixin"></i></a>
|
||||
<a href="#" class="text-white me-3"><i class="fab fa-weibo"></i></a>
|
||||
<a href="#" class="text-white"><i class="fab fa-github"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 mb-4">
|
||||
<h6 class="mb-3">产品</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="#" class="text-muted text-decoration-none">微信小程序</a></li>
|
||||
<li><a href="#" class="text-muted text-decoration-none">API服务</a></li>
|
||||
<li><a href="#" class="text-muted text-decoration-none">企业版</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-2 mb-4">
|
||||
<h6 class="mb-3">支持</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="#" class="text-muted text-decoration-none">帮助中心</a></li>
|
||||
<li><a href="#" class="text-muted text-decoration-none">常见问题</a></li>
|
||||
<li><a href="#" class="text-muted text-decoration-none">联系我们</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-4 mb-4">
|
||||
<h6 class="mb-3">联系我们</h6>
|
||||
<p class="text-muted mb-1"><i class="fas fa-map-marker-alt me-2"></i>北京市海淀区中关村大街</p>
|
||||
<p class="text-muted mb-1"><i class="fas fa-phone me-2"></i>400-123-4567</p>
|
||||
<p class="text-muted"><i class="fas fa-envelope me-2"></i>contact@aijianhua.com</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-4">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6 text-center text-md-start">
|
||||
<p class="text-muted mb-0">© 2024 爱鉴花. 保留所有权利.</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-center text-md-end">
|
||||
<p class="text-muted mb-0">
|
||||
<a href="#" class="text-muted text-decoration-none me-3">隐私政策</a>
|
||||
<a href="#" class="text-muted text-decoration-none me-3">服务条款</a>
|
||||
<a href="#" class="text-muted text-decoration-none">京ICP备12345678号</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-3">
|
||||
<p class="text-muted small">
|
||||
友情链接:
|
||||
<a href="#" class="text-muted text-decoration-none me-2">中国花卉协会</a>
|
||||
<a href="#" class="text-muted text-decoration-none me-2">植物智</a>
|
||||
<a href="#" class="text-muted text-decoration-none">园林在线</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
|
||||
<!-- Schema.org结构化数据 -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "ItemList",
|
||||
"name": "爱鉴花产品服务",
|
||||
"description": "爱鉴花提供的智能花卉识别产品和服务",
|
||||
"url": "https://aijianhua.com/products.html",
|
||||
"numberOfItems": 3,
|
||||
"itemListElement": [
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 1,
|
||||
"item": {
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "微信小程序",
|
||||
"description": "免费的AI花卉识别微信小程序,随时随地识别植物",
|
||||
"applicationCategory": "UtilitiesApplication",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "CNY"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 2,
|
||||
"item": {
|
||||
"@type": "Service",
|
||||
"name": "API服务",
|
||||
"description": "花卉识别API接口服务,为开发者提供技术支持",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "99",
|
||||
"priceCurrency": "CNY",
|
||||
"priceSpecification": {
|
||||
"@type": "UnitPriceSpecification",
|
||||
"price": "99",
|
||||
"priceCurrency": "CNY",
|
||||
"referenceQuantity": {
|
||||
"@type": "QuantitativeValue",
|
||||
"value": "1000",
|
||||
"unitCode": "C62"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 3,
|
||||
"item": {
|
||||
"@type": "Service",
|
||||
"name": "企业版解决方案",
|
||||
"description": "定制化的企业级花卉识别解决方案",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"priceSpecification": {
|
||||
"@type": "PriceSpecification",
|
||||
"price": "0",
|
||||
"priceCurrency": "CNY",
|
||||
"valueAddedTaxIncluded": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
18
official_website/robots.txt
Normal file
18
official_website/robots.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# Sitemap
|
||||
Sitemap: https://aijianhua.com/sitemap.xml
|
||||
|
||||
# Disallow admin pages and sensitive directories
|
||||
Disallow: /admin/
|
||||
Disallow: /private/
|
||||
Disallow: /config/
|
||||
|
||||
# Allow search engines to crawl all content
|
||||
Allow: /*.html
|
||||
Allow: /*.css
|
||||
Allow: /*.js
|
||||
|
||||
# Crawl delay to prevent overloading server
|
||||
Crawl-delay: 1
|
||||
33
official_website/sitemap.xml
Normal file
33
official_website/sitemap.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://aijianhua.com/index.html</loc>
|
||||
<lastmod>2024-01-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://aijianhua.com/about.html</loc>
|
||||
<lastmod>2024-01-20</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://aijianhua.com/products.html</loc>
|
||||
<lastmod>2024-01-20</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://aijianhua.com/news.html</loc>
|
||||
<lastmod>2024-01-20</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://aijianhua.com/contact.html</loc>
|
||||
<lastmod>2024-01-20</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
137
数据库设计文档.md
Normal file
137
数据库设计文档.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# 爱鉴花项目数据库设计文档
|
||||
|
||||
## 1. 数据库概述
|
||||
本项目使用MySQL 8.0作为主数据库,存储用户、商品、订单、识别记录等核心业务数据。
|
||||
|
||||
## 2. 数据库表结构
|
||||
|
||||
### 2.1 用户表 (users)
|
||||
| 字段名 | 类型 | 说明 | 约束 |
|
||||
|--------|------|------|------|
|
||||
| id | bigint | 用户ID | 主键,自增 |
|
||||
| username | varchar(50) | 用户名 | 唯一索引 |
|
||||
| email | varchar(100) | 邮箱 | 唯一索引 |
|
||||
| phone | varchar(20) | 手机号 | 唯一索引 |
|
||||
| password_hash | varchar(255) | 密码哈希 | 非空 |
|
||||
| real_name | varchar(50) | 真实姓名 | 可空 |
|
||||
| avatar_url | varchar(255) | 头像URL | 可空 |
|
||||
| user_type | enum | 用户类型 | 非空 |
|
||||
| status | tinyint | 状态 | 默认1 |
|
||||
| last_login | timestamp | 最后登录时间 | 可空 |
|
||||
| created_at | timestamp | 创建时间 | 默认当前时间 |
|
||||
| updated_at | timestamp | 更新时间 | 自动更新 |
|
||||
|
||||
### 2.2 商品分类表 (categories)
|
||||
| 字段名 | 类型 | 说明 | 约束 |
|
||||
|--------|------|------|------|
|
||||
| id | int | 分类ID | 主键,自增 |
|
||||
| name | varchar(100) | 分类名称 | 非空 |
|
||||
| parent_id | int | 父分类ID | 默认0 |
|
||||
| sort_order | int | 排序 | 默认0 |
|
||||
| status | tinyint | 状态 | 默认1 |
|
||||
| created_at | timestamp | 创建时间 | 默认当前时间 |
|
||||
| updated_at | timestamp | 更新时间 | 自动更新 |
|
||||
|
||||
### 2.3 商品表 (products)
|
||||
| 字段名 | 类型 | 说明 | 约束 |
|
||||
|--------|------|------|------|
|
||||
| id | bigint | 商品ID | 主键,自增 |
|
||||
| name | varchar(200) | 商品名称 | 非空 |
|
||||
| category_id | int | 分类ID | 索引 |
|
||||
| price | decimal(10,2) | 价格 | 非空 |
|
||||
| stock | int | 库存 | 默认0 |
|
||||
| image | varchar(255) | 图片URL | 可空 |
|
||||
| description | text | 商品描述 | 可空 |
|
||||
| status | tinyint | 状态 | 默认1 |
|
||||
| created_at | timestamp | 创建时间 | 默认当前时间 |
|
||||
| updated_at | timestamp | 更新时间 | 自动更新 |
|
||||
|
||||
### 2.4 订单表 (orders)
|
||||
| 字段名 | 类型 | 说明 | 约束 |
|
||||
|--------|------|------|------|
|
||||
| id | bigint | 订单ID | 主键,自增 |
|
||||
| user_id | bigint | 用户ID | 非空,索引 |
|
||||
| total_amount | decimal(10,2) | 订单总额 | 非空 |
|
||||
| payment_status | tinyint | 支付状态 | 默认0 |
|
||||
| shipping_status | tinyint | 配送状态 | 默认0 |
|
||||
| shipping_address | text | 配送地址 | 可空 |
|
||||
| created_at | timestamp | 创建时间 | 默认当前时间 |
|
||||
| updated_at | timestamp | 更新时间 | 自动更新 |
|
||||
|
||||
### 2.5 订单商品表 (order_items)
|
||||
| 字段名 | 类型 | 说明 | 约束 |
|
||||
|--------|------|------|------|
|
||||
| id | bigint | 订单项ID | 主键,自增 |
|
||||
| order_id | bigint | 订单ID | 非空,索引 |
|
||||
| product_id | bigint | 商品ID | 非空,索引 |
|
||||
| quantity | int | 数量 | 非空 |
|
||||
| unit_price | decimal(10,2) | 单价 | 非空 |
|
||||
|
||||
### 2.6 识别记录表 (identifications)
|
||||
| 字段名 | 类型 | 说明 | 约束 |
|
||||
|--------|------|------|------|
|
||||
| id | bigint | 记录ID | 主键,自增 |
|
||||
| user_id | bigint | 用户ID | 非空,索引 |
|
||||
| image_url | varchar(255) | 图片URL | 非空 |
|
||||
| result | text | 识别结果 | 可空 |
|
||||
| confidence | decimal(5,4) | 置信度 | 可空 |
|
||||
| created_at | timestamp | 创建时间 | 默认当前时间 |
|
||||
|
||||
### 2.7 收货地址表 (addresses)
|
||||
| 字段名 | 类型 | 说明 | 约束 |
|
||||
|--------|------|------|------|
|
||||
| id | bigint | 地址ID | 主键,自增 |
|
||||
| user_id | bigint | 用户ID | 非空,索引 |
|
||||
| recipient | varchar(100) | 收货人 | 非空 |
|
||||
| phone | varchar(20) | 手机号 | 非空 |
|
||||
| address | text | 详细地址 | 非空 |
|
||||
| is_default | tinyint | 是否默认 | 默认0 |
|
||||
| created_at | timestamp | 创建时间 | 默认当前时间 |
|
||||
| updated_at | timestamp | 更新时间 | 自动更新 |
|
||||
|
||||
## 3. 索引设计
|
||||
|
||||
### 3.1 唯一索引
|
||||
- users.phone: 手机号唯一索引
|
||||
- users.email: 邮箱唯一索引
|
||||
- users.username: 用户名唯一索引
|
||||
|
||||
### 3.2 普通索引
|
||||
- products.category_id: 商品分类索引
|
||||
- orders.user_id: 订单用户索引
|
||||
- orders.created_at: 订单时间索引
|
||||
- identifications.user_id: 识别记录用户索引
|
||||
- identifications.created_at: 识别时间索引
|
||||
- addresses.user_id: 地址用户索引
|
||||
|
||||
## 4. 测试数据统计
|
||||
|
||||
已插入的测试数据:
|
||||
- 用户数据: 3条
|
||||
- 商品分类: 7条
|
||||
- 商品数据: 5条
|
||||
- 订单数据: 3条
|
||||
- 订单商品: 4条
|
||||
- 识别记录: 3条
|
||||
- 收货地址: 3条
|
||||
|
||||
## 5. 数据库连接信息
|
||||
|
||||
**生产环境**:
|
||||
- 主机: 129.211.213.226:9527
|
||||
- 数据库: ajhdata
|
||||
- 用户名: root
|
||||
- 密码: aiotAiot123!
|
||||
|
||||
**连接池配置**:
|
||||
- 最大连接数: 200
|
||||
- 最小连接数: 20
|
||||
- 超时时间: 30秒
|
||||
|
||||
## 6. 字符编码和时区
|
||||
- 字符编码: UTF8MB4(支持emoji表情)
|
||||
- 时区设置: 东八区(+08:00)
|
||||
|
||||
---
|
||||
*本文档最后更新: 2024年1月*
|
||||
*数据库版本: MySQL 8.0*
|
||||
367
部署指南.md
Normal file
367
部署指南.md
Normal file
@@ -0,0 +1,367 @@
|
||||
# 爱鉴花小程序 - 部署操作指南
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本文档提供「爱鉴花」小程序项目的完整部署指南,涵盖开发、测试、生产环境的部署流程和运维操作。
|
||||
|
||||
## 🚀 环境准备
|
||||
|
||||
### 1. 系统要求
|
||||
- **操作系统**: Ubuntu 20.04+ / CentOS 7+
|
||||
- **Node.js**: 16.0.0+
|
||||
- **MySQL**: 8.0+
|
||||
- **Redis**: 6.0+
|
||||
- **Nginx**: 1.18+
|
||||
|
||||
### 2. 依赖安装
|
||||
|
||||
#### 后端依赖
|
||||
```bash
|
||||
cd backend
|
||||
npm install
|
||||
```
|
||||
|
||||
#### 前端依赖
|
||||
```bash
|
||||
cd mini_program
|
||||
npm install
|
||||
```
|
||||
|
||||
## 🔧 环境配置
|
||||
|
||||
### 1. 数据库配置
|
||||
|
||||
#### 创建配置文件
|
||||
```bash
|
||||
# 复制环境变量模板
|
||||
cp backend/.env.example backend/.env.development
|
||||
|
||||
# 编辑配置文件
|
||||
vim backend/.env.development
|
||||
```
|
||||
|
||||
#### 环境变量配置示例
|
||||
```env
|
||||
# 应用配置
|
||||
NODE_ENV=development
|
||||
PORT=3000
|
||||
APP_NAME=爱鉴花小程序后端
|
||||
|
||||
# MySQL数据库配置
|
||||
DB_HOST=192.168.0.240
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=aiot$Aiot123
|
||||
DB_DATABASE=ajhdata
|
||||
|
||||
# Redis配置
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
|
||||
# 腾讯云配置
|
||||
TENCENT_CLOUD_SECRET_ID=your_secret_id
|
||||
TENCENT_CLOUD_SECRET_KEY=your_secret_key
|
||||
COS_BUCKET=your_bucket_name
|
||||
COS_REGION=ap-beijing
|
||||
|
||||
# 微信小程序配置
|
||||
WX_APPID=your_wechat_appid
|
||||
WX_APPSECRET=your_wechat_appsecret
|
||||
|
||||
# JWT配置
|
||||
JWT_SECRET=your_jwt_secret_key
|
||||
JWT_EXPIRE=7d
|
||||
```
|
||||
|
||||
### 2. 数据库初始化
|
||||
|
||||
```bash
|
||||
# 验证数据库连接
|
||||
cd backend
|
||||
npm run db:check
|
||||
|
||||
# 完整数据库初始化
|
||||
npm run db:init
|
||||
```
|
||||
|
||||
## 🏗️ 构建部署
|
||||
|
||||
### 1. 后端服务部署
|
||||
|
||||
#### 开发环境启动
|
||||
```bash
|
||||
cd backend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
#### 生产环境构建
|
||||
```bash
|
||||
# 安装生产依赖
|
||||
npm install --production
|
||||
|
||||
# 启动生产服务
|
||||
npm start
|
||||
```
|
||||
|
||||
#### PM2进程管理
|
||||
```bash
|
||||
# 安装PM2
|
||||
npm install -g pm2
|
||||
|
||||
# 启动服务
|
||||
pm2 start app.js --name aijianhua-backend
|
||||
|
||||
# 查看服务状态
|
||||
pm2 status
|
||||
|
||||
# 设置开机自启
|
||||
pm2 startup
|
||||
pm2 save
|
||||
```
|
||||
|
||||
### 2. 前端小程序部署
|
||||
|
||||
#### 开发环境
|
||||
```bash
|
||||
cd mini_program
|
||||
npm run dev:mp-weixin
|
||||
```
|
||||
|
||||
#### 生产构建
|
||||
```bash
|
||||
npm run build:mp-weixin
|
||||
```
|
||||
|
||||
#### 微信开发者工具
|
||||
1. 打开微信开发者工具
|
||||
2. 导入项目目录 `mini_program`
|
||||
3. 配置AppID和项目设置
|
||||
4. 点击"上传"发布体验版
|
||||
|
||||
### 3. Nginx配置
|
||||
|
||||
#### 创建Nginx配置文件
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name api.aijianhua.com;
|
||||
|
||||
# API反向代理
|
||||
location /api/ {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
# 静态资源服务
|
||||
location /static/ {
|
||||
alias /path/to/static/files/;
|
||||
expires 30d;
|
||||
}
|
||||
|
||||
# 健康检查
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "healthy\n";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 启用配置
|
||||
```bash
|
||||
# 检查配置语法
|
||||
nginx -t
|
||||
|
||||
# 重新加载配置
|
||||
nginx -s reload
|
||||
```
|
||||
|
||||
## 📊 监控运维
|
||||
|
||||
### 1. 日志管理
|
||||
|
||||
#### 查看应用日志
|
||||
```bash
|
||||
# PM2日志
|
||||
pm2 logs aijianhua-backend
|
||||
|
||||
# Nginx访问日志
|
||||
tail -f /var/log/nginx/access.log
|
||||
|
||||
# Nginx错误日志
|
||||
tail -f /var/log/nginx/error.log
|
||||
```
|
||||
|
||||
#### 日志轮转配置
|
||||
```bash
|
||||
# 安装logrotate
|
||||
sudo apt-get install logrotate
|
||||
|
||||
# 创建日志轮转配置
|
||||
sudo vim /etc/logrotate.d/aijianhua
|
||||
```
|
||||
|
||||
### 2. 性能监控
|
||||
|
||||
#### 系统监控
|
||||
```bash
|
||||
# 查看系统负载
|
||||
top
|
||||
htop
|
||||
|
||||
# 查看磁盘使用
|
||||
df -h
|
||||
|
||||
# 查看内存使用
|
||||
free -h
|
||||
```
|
||||
|
||||
#### 应用监控
|
||||
```bash
|
||||
# 查看Node.js进程
|
||||
pm2 monit
|
||||
|
||||
# 查看数据库连接
|
||||
mysql -u root -p -e "SHOW PROCESSLIST;"
|
||||
```
|
||||
|
||||
### 3. 备份恢复
|
||||
|
||||
#### 数据库备份
|
||||
```bash
|
||||
# 全量备份
|
||||
mysqldump -u root -p ajhdata > backup_$(date +%Y%m%d).sql
|
||||
|
||||
# 压缩备份
|
||||
gzip backup_$(date +%Y%m%d).sql
|
||||
```
|
||||
|
||||
#### 文件备份
|
||||
```bash
|
||||
# 备份重要文件
|
||||
tar -czf backup_$(date +%Y%m%d).tar.gz /path/to/important/files
|
||||
```
|
||||
|
||||
## 🔒 安全配置
|
||||
|
||||
### 1. 防火墙配置
|
||||
```bash
|
||||
# 启用防火墙
|
||||
sudo ufw enable
|
||||
|
||||
# 开放必要端口
|
||||
sudo ufw allow 22 # SSH
|
||||
sudo ufw allow 80 # HTTP
|
||||
sudo ufw allow 443 # HTTPS
|
||||
sudo ufw allow 3000 # Node.js
|
||||
|
||||
# 查看防火墙状态
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
### 2. SSL证书配置
|
||||
|
||||
#### 申请证书
|
||||
```bash
|
||||
# 使用Certbot申请SSL证书
|
||||
sudo apt-get install certbot python3-certbot-nginx
|
||||
sudo certbot --nginx -d api.aijianhua.com
|
||||
```
|
||||
|
||||
#### 自动续期
|
||||
```bash
|
||||
# 测试续期
|
||||
sudo certbot renew --dry-run
|
||||
|
||||
# 设置定时任务
|
||||
sudo crontab -e
|
||||
# 添加:0 12 * * * /usr/bin/certbot renew --quiet
|
||||
```
|
||||
|
||||
### 3. 安全加固
|
||||
|
||||
#### 数据库安全
|
||||
```sql
|
||||
-- 创建专用用户
|
||||
CREATE USER 'aijianhua'@'localhost' IDENTIFIED BY 'strong_password';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ajhdata.* TO 'aijianhua'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
#### 文件权限
|
||||
```bash
|
||||
# 设置正确的文件权限
|
||||
chmod 600 /path/to/config/file
|
||||
chmod 700 /path/to/executable
|
||||
```
|
||||
|
||||
## 🚨 故障处理
|
||||
|
||||
### 1. 常见问题
|
||||
|
||||
#### 端口冲突
|
||||
```bash
|
||||
# 查看端口占用
|
||||
netstat -tlnp | grep :3000
|
||||
lsof -i :3000
|
||||
|
||||
# 杀死进程
|
||||
kill -9 <PID>
|
||||
```
|
||||
|
||||
#### 服务无法启动
|
||||
```bash
|
||||
# 查看错误详情
|
||||
npm start 2>&1 | tee error.log
|
||||
|
||||
# 检查依赖
|
||||
npm ls --depth=0
|
||||
```
|
||||
|
||||
#### 数据库连接失败
|
||||
```bash
|
||||
# 测试数据库连接
|
||||
mysql -h 192.168.0.240 -P 3306 -u root -p
|
||||
|
||||
# 检查MySQL服务状态
|
||||
sudo systemctl status mysql
|
||||
```
|
||||
|
||||
### 2. 应急响应
|
||||
|
||||
#### 服务重启
|
||||
```bash
|
||||
# 重启Node.js服务
|
||||
pm2 restart aijianhua-backend
|
||||
|
||||
# 重启Nginx
|
||||
sudo systemctl restart nginx
|
||||
|
||||
# 重启MySQL
|
||||
sudo systemctl restart mysql
|
||||
```
|
||||
|
||||
#### 回滚部署
|
||||
```bash
|
||||
# 回滚到上一个版本
|
||||
pm2 restart aijianhua-backend --update-env
|
||||
```
|
||||
|
||||
## 📞 支持联系
|
||||
|
||||
### 运维团队
|
||||
- **值班电话**: 138-0013-8000
|
||||
- **技术支持**: tech@aijianhua.com
|
||||
- **紧急响应**: emergency@aijianhua.com
|
||||
|
||||
### 监控平台
|
||||
- **应用性能**: NewRelic / Datadog
|
||||
- **错误监控**: Sentry
|
||||
- **日志分析**: ELK Stack
|
||||
- **业务监控**: Grafana
|
||||
|
||||
---
|
||||
*最后更新: 2024-01-15*
|
||||
*文档版本: v1.0*
|
||||
Reference in New Issue
Block a user