Merge remote-tracking branch 'origin/main'
# Conflicts: # backend/api/server.js
This commit is contained in:
24113
.gitignore
vendored
24113
.gitignore
vendored
File diff suppressed because it is too large
Load Diff
111
PROJECT_PROGRESS.md
Normal file
111
PROJECT_PROGRESS.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# xlxumu项目进度跟踪
|
||||
|
||||
## 项目概述
|
||||
xlxumu是一个综合性的畜牧管理系统,旨在为畜牧业提供全面的数字化解决方案。该系统涵盖了从养殖管理、金融服务、政府监管到电商交易等各个环节。
|
||||
|
||||
## 总体进度:75%
|
||||
|
||||
## 各模块进度详情
|
||||
|
||||
### 1. 前端开发 (90%)
|
||||
|
||||
#### 1.1 管理后台系统 (95%)
|
||||
- [x] 仪表板子系统 (dashboard)
|
||||
- [x] 养殖管理子系统 (farming-management)
|
||||
- [x] 牛只交易子系统 (cattle-trading)
|
||||
- [x] 商城管理子系统 (mall-management)
|
||||
- [x] 银行监管子系统 (bank-supervision)
|
||||
- [x] 保险监管子系统 (insurance-supervision)
|
||||
- [x] 政府平台子系统 (government-platform)
|
||||
|
||||
#### 1.2 小程序端 (95%)
|
||||
- [x] 养殖管理小程序 (farming-manager)
|
||||
- [x] 牛只交易小程序 (cattle-trading)
|
||||
- [x] 牛肉商城小程序 (beef-mall)
|
||||
- [x] 银行监管小程序 (bank-supervision)
|
||||
- [x] 保险监管小程序 (insurance-supervision)
|
||||
|
||||
#### 1.3 官网 (100%)
|
||||
- [x] 首页
|
||||
- [x] 新闻页面
|
||||
- [x] 数据展示页面
|
||||
|
||||
### 2. 后端开发 (60%)
|
||||
|
||||
#### 2.1 Node.js版本 (70%)
|
||||
- [x] 基础API服务
|
||||
- [x] 数据库连接
|
||||
- [x] 用户认证模块
|
||||
- [ ] 牛只档案管理API
|
||||
- [ ] 金融服务监管API
|
||||
- [ ] 交易管理API
|
||||
- [ ] 商城管理API
|
||||
- [ ] 政府监管API
|
||||
|
||||
#### 2.2 Java版本 (50%)
|
||||
- [x] 微服务架构搭建
|
||||
- [x] farming-service服务实现并运行 (端口: 8081)
|
||||
- [x] user-center-service服务实现并运行 (端口: 8082)
|
||||
- [x] 数据库集成配置
|
||||
- [x] 服务间通信机制
|
||||
- [ ] finance-service开发
|
||||
- [ ] government-service开发
|
||||
- [ ] trade-service开发
|
||||
- [ ] mall-service开发
|
||||
- [ ] data-platform-service开发
|
||||
- [ ] ai-service开发
|
||||
- [ ] 网关服务 (gateway)
|
||||
- [ ] 配置服务器 (config-server)
|
||||
- [ ] 服务注册中心 (registry)
|
||||
|
||||
### 3. 文档完善 (70%)
|
||||
- [x] 项目结构说明文档
|
||||
- [x] 各子系统需求文档
|
||||
- [x] 各子系统设计文档
|
||||
- [x] 各子系统开发计划
|
||||
- [ ] API文档完善
|
||||
- [ ] 部署文档
|
||||
- [ ] 运维文档
|
||||
|
||||
## 最近完成的工作
|
||||
|
||||
### Java后端架构完善
|
||||
1. 成功搭建Java版本后端微服务架构
|
||||
2. 实现并部署farming-service服务 (端口: 8081)
|
||||
3. 实现并部署user-center-service服务 (端口: 8082)
|
||||
4. 完善各服务的数据库配置
|
||||
5. 更新项目文档以反映Java版本后端的添加
|
||||
|
||||
### 文档更新
|
||||
1. 更新backend-java/README.md,详细说明Java后端架构和使用方法
|
||||
2. 更新项目根目录README.md,添加Java后端技术栈说明
|
||||
3. 更新PROJECT_STATUS_REPORT.md,反映最新的项目进度和状态
|
||||
|
||||
## 下一步工作计划
|
||||
|
||||
### 短期目标 (1-2周)
|
||||
1. 完善Java版本其他微服务开发
|
||||
- finance-service
|
||||
- government-service
|
||||
- trade-service
|
||||
- mall-service
|
||||
2. 开发网关服务和配置中心
|
||||
3. 完善API文档
|
||||
4. 进行前后端联调测试
|
||||
|
||||
### 中期目标 (1个月)
|
||||
1. 完成所有Java微服务开发
|
||||
2. 实现服务间通信机制
|
||||
3. 进行系统集成测试
|
||||
4. 完善部署和运维文档
|
||||
|
||||
### 长期目标
|
||||
1. 开发数据平台服务和AI能力服务
|
||||
2. 进行性能优化
|
||||
3. 准备生产环境部署
|
||||
|
||||
## 当前问题与风险
|
||||
|
||||
1. 微服务间通信机制需要进一步完善
|
||||
2. 部分文档需要及时更新以匹配代码实现
|
||||
3. 需要制定详细的部署和运维方案
|
||||
95
PROJECT_STATUS_REPORT.md
Normal file
95
PROJECT_STATUS_REPORT.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# xlxumu项目状态报告
|
||||
|
||||
## 项目概述
|
||||
xlxumu是一个综合性的畜牧管理系统,旨在为畜牧业提供全面的数字化解决方案。该系统涵盖了从养殖管理、金融服务、政府监管到电商交易等各个环节。
|
||||
|
||||
## 当前进度
|
||||
|
||||
### 已完成模块
|
||||
|
||||
#### 前端部分
|
||||
1. **管理后台系统**
|
||||
- 仪表板子系统 (dashboard)
|
||||
- 养殖管理子系统 (farming-management)
|
||||
- 牛只交易子系统 (cattle-trading)
|
||||
- 商城管理子系统 (mall-management)
|
||||
- 银行监管子系统 (bank-supervision)
|
||||
- 保险监管子系统 (insurance-supervision)
|
||||
- 政府平台子系统 (government-platform)
|
||||
|
||||
2. **小程序端**
|
||||
- 养殖管理小程序 (farming-manager)
|
||||
- 牛只交易小程序 (cattle-trading)
|
||||
- 牛肉商城小程序 (beef-mall)
|
||||
- 银行监管小程序 (bank-supervision)
|
||||
- 保险监管小程序 (insurance-supervision)
|
||||
|
||||
3. **官网**
|
||||
- 首页、新闻、数据展示等页面
|
||||
|
||||
#### 后端部分
|
||||
1. **Node.js版本**
|
||||
- 基础API服务
|
||||
- 数据库连接
|
||||
- 用户认证模块
|
||||
|
||||
2. **Java版本** (新增)
|
||||
- 微服务架构搭建
|
||||
- farming-service服务实现并运行 (端口: 8081)
|
||||
- user-center-service服务实现并运行 (端口: 8082)
|
||||
- 数据库集成配置
|
||||
- 服务间通信机制
|
||||
|
||||
### 开发中模块
|
||||
1. Java版本其他微服务开发 (finance-service, government-service等)
|
||||
2. 后端API文档完善
|
||||
3. 前后端联调测试
|
||||
|
||||
### 待开发模块
|
||||
1. 数据平台服务 (data-platform-service)
|
||||
2. AI能力服务 (ai-service)
|
||||
3. 网关服务 (gateway)
|
||||
4. 配置服务器 (config-server)
|
||||
5. 服务注册中心 (registry)
|
||||
6. 部分前端功能完善
|
||||
7. 系统集成测试
|
||||
|
||||
## 技术架构更新
|
||||
|
||||
### 新增Java后端技术栈
|
||||
- Java 8+ + Spring Boot 2.7.x
|
||||
- Spring Cloud 2021.x
|
||||
- Maven 3.8.x
|
||||
- MySQL 8.0
|
||||
|
||||
### 微服务端口分配
|
||||
| 服务名称 | 端口 | 状态 |
|
||||
|---------|------|------|
|
||||
| farming-service | 8081 | 运行中 |
|
||||
| user-center-service | 8082 | 运行中 |
|
||||
| finance-service | 8083 | 开发中 |
|
||||
| government-service | 8084 | 待开发 |
|
||||
| trade-service | 8085 | 待开发 |
|
||||
| mall-service | 8086 | 待开发 |
|
||||
| data-platform-service | 8087 | 待开发 |
|
||||
| ai-service | 8088 | 待开发 |
|
||||
| gateway | 8000 | 待开发 |
|
||||
| registry | 8761 | 待开发 |
|
||||
| config-server | 8888 | 待开发 |
|
||||
|
||||
## 下一步计划
|
||||
1. 完善Java版本其他微服务开发
|
||||
2. 实现服务间通信机制
|
||||
3. 开发网关服务和配置中心
|
||||
4. 进行前后端联调测试
|
||||
5. 完善API文档
|
||||
6. 进行系统集成测试
|
||||
|
||||
## 问题与风险
|
||||
1. 微服务间通信机制需要进一步完善
|
||||
2. 部分文档需要及时更新以匹配代码实现
|
||||
3. 需要制定详细的部署和运维方案
|
||||
4. 团队需要适应新的Java技术栈
|
||||
|
||||
## 总结
|
||||
项目整体进展顺利,已成功实现Java版本后端基础架构,并完成两个核心微服务的开发和部署。前端各子系统也已基本完成,为后续的系统集成测试奠定了良好基础。随着Java微服务架构的逐步完善,项目的技术基础更加稳固,为后续功能开发提供了更好的支撑。
|
||||
175
README.md
175
README.md
@@ -1,100 +1,117 @@
|
||||
# xlxumu - 锡林郭勒盟地区智慧养殖产业平台
|
||||
# xlxumu - 畜牧管理系统
|
||||
|
||||
## 项目概述
|
||||
|
||||
本项目是一个综合性的畜牧业数字化管理平台,主要面向锡林郭勒盟地区的养殖产业,包含官网展示、后台管理、移动端小程序、大屏可视化等多个子系统。平台集成了养殖管理、金融服务(银行监管、保险监管)、政府监管、牛只交易、牛肉商城、数据分析等功能模块,旨在通过数字化手段提升整个产业链的管理效率和透明度。
|
||||
xlxumu是一个综合性的畜牧管理系统,旨在为畜牧业提供全面的数字化解决方案。该系统涵盖了从养殖管理、金融服务、政府监管到电商交易等各个环节,通过现代化的技术架构和用户友好的界面设计,提升畜牧业的管理效率和经济效益。
|
||||
|
||||
## 技术架构
|
||||
|
||||
项目采用前后端分离的架构设计,包含多个技术栈:
|
||||
|
||||
### 前端技术栈
|
||||
|
||||
1. **管理后台系统** (admin-system)
|
||||
- React + Vite
|
||||
- Ant Design Pro
|
||||
- TypeScript
|
||||
|
||||
2. **小程序端** (mini_program)
|
||||
- 微信小程序原生开发
|
||||
- WXML + WXSS + JavaScript
|
||||
|
||||
3. **官网** (website)
|
||||
- HTML5 + CSS3 + JavaScript
|
||||
|
||||
### 后端技术栈
|
||||
|
||||
1. **Node.js版本** (backend)
|
||||
- Node.js + Express
|
||||
- MySQL
|
||||
|
||||
2. **Java版本** (backend-java)
|
||||
- Java 8+ + Spring Boot 2.7.x
|
||||
- Spring Cloud 2021.x
|
||||
- MySQL 8.0
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
.
|
||||
├── backend/ # 后端项目
|
||||
├── admin-system/ # 管理后台系统
|
||||
│ ├── farming-management/ # 养殖管理系统
|
||||
│ ├── bank-supervision/ # 银行监管系统
|
||||
│ ├── insurance-supervision/ # 保险监管系统
|
||||
│ ├── government-platform/ # 政府监管平台
|
||||
│ ├── cattle-trading/ # 活牛交易系统
|
||||
│ ├── mall-management/ # 商城管理系统
|
||||
│ └── dashboard/ # 大屏可视化系统
|
||||
├── website/ # 官网项目
|
||||
├── mini_program/ # 微信小程序矩阵
|
||||
│ ├── beef-mall/ # 牛肉商城小程序
|
||||
│ ├── farming-manager/ # 养殖管理小程序
|
||||
xlxumu/
|
||||
├── admin-system/ # 管理后台系统
|
||||
│ ├── bank-supervision/ # 银行监管子系统
|
||||
│ ├── cattle-trading/ # 牛只交易子系统
|
||||
│ ├── dashboard/ # 仪表板子系统
|
||||
│ ├── farming-management/ # 养殖管理子系统
|
||||
│ ├── government-platform/ # 政府平台子系统
|
||||
│ ├── insurance-supervision/ # 保险监管子系统
|
||||
│ └── mall-management/ # 商城管理子系统
|
||||
├── backend/ # Node.js后端服务
|
||||
├── backend-java/ # Java后端服务
|
||||
├── mini_program/ # 微信小程序
|
||||
│ ├── bank-supervision/ # 银行监管小程序
|
||||
│ ├── insurance-supervision/ # 保险监管小程序
|
||||
│ ├── cattle-trading/ # 活牛交易小程序
|
||||
│ ├── gov-supervision/ # 政府监管小程序
|
||||
│ ├── data-platform/ # 数据中台小程序
|
||||
│ └── ai-capabilities/ # AI能力小程序
|
||||
├── docs/ # 文档目录
|
||||
│ ├── api/ # API服务
|
||||
│ │ ├── farming/ # 养殖管理API
|
||||
│ │ ├── finance/ # 金融服务API
|
||||
│ │ ├── government/ # 政府监管API
|
||||
│ │ ├── trade/ # 交易管理API
|
||||
│ │ ├── mall/ # 商城管理API
|
||||
│ │ ├── data-platform/ # 数据中台API
|
||||
│ │ ├── ai/ # AI能力API
|
||||
│ │ └── user-center/ # 用户中心API
|
||||
│ ├── services/ # 微服务模块
|
||||
│ │ ├── farming-service/ # 养殖管理服务
|
||||
│ │ ├── finance-service/ # 金融服务
|
||||
│ │ ├── government-service/ # 政府监管服务
|
||||
│ │ ├── trade-service/ # 交易管理服务
|
||||
│ │ ├── mall-service/ # 商城管理服务
|
||||
│ │ ├── data-platform-service/ # 数据中台服务
|
||||
│ │ ├── ai-service/ # AI能力服务
|
||||
│ │ └── user-center-service/ # 用户中心服务
|
||||
│ ├── database/ # 数据库脚本
|
||||
│ └── utils/ # 工具函数
|
||||
├── docs/ # 文档目录
|
||||
│ ├── design/ # 设计文档
|
||||
│ ├── requirements/ # 需求文档
|
||||
│ └── development_plans/ # 开发计划文档
|
||||
├── test/ # 测试文件目录
|
||||
└── deployment/ # 部署相关配置
|
||||
│ ├── beef-mall/ # 牛肉商城小程序
|
||||
│ ├── cattle-trading/ # 牛只交易小程序
|
||||
│ ├── farming-manager/ # 养殖管理小程序
|
||||
│ └── insurance-supervision/ # 保险监管小程序
|
||||
├── website/ # 官网
|
||||
└── docs/ # 项目文档
|
||||
```
|
||||
|
||||
## 文档目录
|
||||
## 快速开始
|
||||
|
||||
详细文档请参见 [docs/README.md](docs/README.md) 文件,其中包含所有系统文档的完整列表和链接。
|
||||
### 前端项目启动
|
||||
|
||||
- `docs/requirements/` - 需求文档
|
||||
- `docs/design/` - 设计文档
|
||||
- `docs/development_plans/` - 开发计划
|
||||
- `docs/PROJECT_STRUCTURE.md` - 项目结构说明文档
|
||||
1. **管理后台系统**
|
||||
```bash
|
||||
cd admin-system/dashboard
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## 技术栈
|
||||
2. **微信小程序**
|
||||
使用微信开发者工具打开对应的小程序目录
|
||||
|
||||
### 技术栈
|
||||
- **官网项目 (website/)**: HTML5 + CSS3 + JavaScript
|
||||
- **管理后台系统 (admin-system/)**: Vue.js 3 + TypeScript + Ant Design Vue + Pinia
|
||||
- **微信小程序矩阵 (mini_program/)**: 微信小程序原生开发 + uni-app
|
||||
3. **官网**
|
||||
直接在浏览器中打开website/index.html文件
|
||||
|
||||
### 后端技术栈
|
||||
- **API服务**: Node.js + Express.js + TypeScript + RESTful API
|
||||
- **数据库**: MySQL
|
||||
- **缓存系统**: Redis
|
||||
- **消息队列**: RabbitMQ(用于异步处理)
|
||||
- **文件存储**: 腾讯云对象存储
|
||||
- **实时通信**: WebSocket(用于大屏数据推送和实时通知)
|
||||
### 后端服务启动
|
||||
|
||||
## 部署说明
|
||||
#### Node.js版本
|
||||
```bash
|
||||
cd backend
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
详细的部署说明请参考 [deployment/README.md](deployment/README.md) 文件。
|
||||
#### Java版本
|
||||
```bash
|
||||
cd backend-java
|
||||
mvn clean install
|
||||
```
|
||||
|
||||
## 开发说明
|
||||
然后分别启动各个微服务:
|
||||
```bash
|
||||
# 启动farming-service
|
||||
cd backend-java/services/farming-service
|
||||
mvn spring-boot:run
|
||||
|
||||
1. 管理后台系统 (admin-system/) 使用 Vite 构建
|
||||
2. 后端项目 (backend/) 基于 Node.js 和 Express.js
|
||||
3. 各个子系统可以独立开发和部署
|
||||
4. 微服务架构便于扩展和维护
|
||||
# 启动user-center-service
|
||||
cd backend-java/services/user-center-service
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
## 项目特点
|
||||
## 文档
|
||||
|
||||
1. **模块化设计**: 前后端分离,模块化设计便于团队协作
|
||||
2. **微服务架构**: 后端采用微服务架构,提高系统可扩展性
|
||||
3. **多端支持**: 支持PC端、移动端、微信小程序和大屏展示
|
||||
4. **数据驱动**: 集成数据分析和可视化展示
|
||||
5. **安全可靠**: 完善的权限管理和数据安全保障
|
||||
详细的项目文档位于`docs/`目录下,包括:
|
||||
- 需求文档
|
||||
- 设计文档
|
||||
- API文档
|
||||
- 开发计划
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎提交Issue和Pull Request来改进本项目。
|
||||
|
||||
## 许可证
|
||||
|
||||
[MIT License](LICENSE)
|
||||
4047
admin-system/dashboard/dist/assets/index-da04cff0.js
vendored
Normal file
4047
admin-system/dashboard/dist/assets/index-da04cff0.js
vendored
Normal file
File diff suppressed because one or more lines are too long
138
admin-system/dashboard/dist/assets/index-e21ede74.css
vendored
Normal file
138
admin-system/dashboard/dist/assets/index-e21ede74.css
vendored
Normal file
File diff suppressed because one or more lines are too long
15
admin-system/dashboard/dist/index.html
vendored
Normal file
15
admin-system/dashboard/dist/index.html
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>养殖管理系统 - 锡林郭勒盟智慧养殖数字化管理平台</title>
|
||||
<script type="module" crossorigin src="/assets/index-da04cff0.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-e21ede74.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,23 +1,62 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<nav class="main-nav">
|
||||
<router-link to="/" class="nav-item">首页</router-link>
|
||||
<router-link to="/monitor" class="nav-item">监控中心</router-link>
|
||||
<router-link to="/government" class="nav-item">政府平台</router-link>
|
||||
<router-link to="/finance" class="nav-item">金融服务</router-link>
|
||||
<router-link to="/transport" class="nav-item">运输跟踪</router-link>
|
||||
<router-link to="/risk" class="nav-item">风险预警</router-link>
|
||||
<router-link to="/eco" class="nav-item">生态指标</router-link>
|
||||
<router-link to="/gov" class="nav-item">政府监管</router-link>
|
||||
<router-link to="/trade" class="nav-item">交易统计</router-link>
|
||||
<!-- 只在登录后显示导航栏 -->
|
||||
<nav v-if="authStore.isAuthenticated && $route.path !== '/login'" class="main-nav">
|
||||
<div class="nav-left">
|
||||
<router-link to="/" class="nav-item">首页</router-link>
|
||||
<router-link to="/monitor" class="nav-item">监控中心</router-link>
|
||||
<router-link to="/government" class="nav-item">政府平台</router-link>
|
||||
<router-link to="/finance" class="nav-item">金融服务</router-link>
|
||||
<router-link to="/transport" class="nav-item">运输跟踪</router-link>
|
||||
<router-link to="/risk" class="nav-item">风险预警</router-link>
|
||||
<router-link to="/eco" class="nav-item">生态指标</router-link>
|
||||
<router-link to="/gov" class="nav-item">政府监管</router-link>
|
||||
<router-link to="/trade" class="nav-item">交易统计</router-link>
|
||||
<router-link to="/users" class="nav-item">用户管理</router-link>
|
||||
</div>
|
||||
|
||||
<div class="nav-right">
|
||||
<span class="user-info">
|
||||
欢迎,{{ authStore.realName || authStore.username }}
|
||||
</span>
|
||||
<a-button type="text" @click="handleLogout" class="logout-btn">
|
||||
退出登录
|
||||
</a-button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useAuthStore } from './store/auth.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
export default {
|
||||
name: 'App'
|
||||
name: 'App',
|
||||
setup() {
|
||||
const authStore = useAuthStore()
|
||||
const router = useRouter()
|
||||
|
||||
// 处理登出
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await authStore.logout()
|
||||
message.success('退出登录成功')
|
||||
router.push('/login')
|
||||
} catch (error) {
|
||||
console.error('登出失败:', error)
|
||||
message.error('登出失败')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
authStore,
|
||||
handleLogout
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -33,12 +72,41 @@ export default {
|
||||
padding: 15px 20px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.nav-left {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
color: rgba(255, 255, 255, 0.8) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
.logout-btn:hover {
|
||||
color: #fff !important;
|
||||
border-color: #ff4d4f !important;
|
||||
background: rgba(255, 77, 79, 0.2) !important;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
|
||||
116
admin-system/dashboard/src/components/ApiTest.vue
Normal file
116
admin-system/dashboard/src/components/ApiTest.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div class="api-test-container">
|
||||
<a-card title="API连接测试" style="margin-bottom: 20px;">
|
||||
<div class="test-buttons">
|
||||
<a-button @click="testHealth" :loading="healthLoading" type="primary">
|
||||
测试服务器健康状态
|
||||
</a-button>
|
||||
<a-button @click="testMapData" :loading="mapLoading">
|
||||
测试地图数据
|
||||
</a-button>
|
||||
<a-button @click="testLogin" :loading="loginLoading">
|
||||
测试登录功能
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div class="test-results">
|
||||
<h4>测试结果:</h4>
|
||||
<pre class="result-output">{{ testResults }}</pre>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { systemAPI, dashboardAPI, authAPI } from '../services/api.js';
|
||||
|
||||
const healthLoading = ref(false);
|
||||
const mapLoading = ref(false);
|
||||
const loginLoading = ref(false);
|
||||
const testResults = ref('等待测试...');
|
||||
|
||||
// 测试服务器健康状态
|
||||
const testHealth = async () => {
|
||||
healthLoading.value = true;
|
||||
try {
|
||||
const response = await systemAPI.getHealth();
|
||||
testResults.value = JSON.stringify(response, null, 2);
|
||||
message.success('健康检查成功');
|
||||
} catch (error) {
|
||||
testResults.value = `健康检查失败: ${error.message}`;
|
||||
message.error('健康检查失败');
|
||||
} finally {
|
||||
healthLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 测试地图数据
|
||||
const testMapData = async () => {
|
||||
mapLoading.value = true;
|
||||
try {
|
||||
const response = await dashboardAPI.getMapRegions();
|
||||
testResults.value = JSON.stringify(response, null, 2);
|
||||
message.success('地图数据获取成功');
|
||||
} catch (error) {
|
||||
testResults.value = `地图数据获取失败: ${error.message}`;
|
||||
message.error('地图数据获取失败');
|
||||
} finally {
|
||||
mapLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 测试登录功能
|
||||
const testLogin = async () => {
|
||||
loginLoading.value = true;
|
||||
try {
|
||||
const response = await authAPI.login({
|
||||
username: 'admin',
|
||||
password: '123456'
|
||||
});
|
||||
testResults.value = JSON.stringify(response, null, 2);
|
||||
message.success('登录测试成功');
|
||||
} catch (error) {
|
||||
testResults.value = `登录测试失败: ${error.message}`;
|
||||
message.error('登录测试失败');
|
||||
} finally {
|
||||
loginLoading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.api-test-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.test-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.test-results {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.test-results h4 {
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.result-output {
|
||||
background: #f5f5f5;
|
||||
border: 1px solid #ddd;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
134
admin-system/dashboard/src/components/StatsCard.vue
Normal file
134
admin-system/dashboard/src/components/StatsCard.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<div class="stats-card">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6" v-for="(stat, index) in stats" :key="index">
|
||||
<a-card :bordered="false" class="stat-item">
|
||||
<a-statistic
|
||||
:title="stat.title"
|
||||
:value="stat.value"
|
||||
:prefix="stat.prefix"
|
||||
:suffix="stat.suffix"
|
||||
:value-style="{ color: stat.color }"
|
||||
/>
|
||||
<div class="stat-extra">
|
||||
<span :class="['trend', stat.trend]">
|
||||
{{ stat.trend === 'up' ? '↗' : '↘' }} {{ stat.change }}%
|
||||
</span>
|
||||
<span class="compare">较昨日</span>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { cattleAPI, financeAPI, tradingAPI, mallAPI } from '../services/api.js';
|
||||
|
||||
const stats = ref([
|
||||
{ title: '总牛只数量', value: 0, suffix: '头', color: '#3f8600', trend: 'up', change: 0 },
|
||||
{ title: '总产值', value: 0, prefix: '¥', suffix: '万', color: '#cf1322', trend: 'up', change: 0 },
|
||||
{ title: '活跃交易', value: 0, suffix: '笔', color: '#1890ff', trend: 'up', change: 0 },
|
||||
{ title: '在线用户', value: 0, suffix: '人', color: '#722ed1', trend: 'up', change: 0 },
|
||||
]);
|
||||
|
||||
// 加载统计数据
|
||||
const loadStats = async () => {
|
||||
try {
|
||||
// 并发请求各模块数据
|
||||
const [cattleData, financeData, tradingData, mallData] = await Promise.allSettled([
|
||||
cattleAPI.getStatistics(),
|
||||
financeAPI.getStatistics(),
|
||||
tradingAPI.getStatistics(),
|
||||
mallAPI.getStatistics(),
|
||||
]);
|
||||
|
||||
// 更新统计数据
|
||||
if (cattleData.status === 'fulfilled' && cattleData.value.success) {
|
||||
const data = cattleData.value.data;
|
||||
stats.value[0].value = data.total_cattle || 0;
|
||||
stats.value[0].change = Math.random() * 10; // 模拟变化率
|
||||
}
|
||||
|
||||
if (financeData.status === 'fulfilled' && financeData.value.success) {
|
||||
const data = financeData.value.data;
|
||||
stats.value[1].value = Math.round((data.total_loan_amount || 0) / 10000);
|
||||
stats.value[1].change = Math.random() * 8;
|
||||
}
|
||||
|
||||
if (tradingData.status === 'fulfilled' && tradingData.value.success) {
|
||||
const data = tradingData.value.data;
|
||||
stats.value[2].value = data.total_transactions || 0;
|
||||
stats.value[2].change = Math.random() * 12;
|
||||
}
|
||||
|
||||
if (mallData.status === 'fulfilled' && mallData.value.success) {
|
||||
const data = mallData.value.data;
|
||||
stats.value[3].value = data.active_users || 0;
|
||||
stats.value[3].change = Math.random() * 5;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载统计数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadStats();
|
||||
// 每30秒更新一次数据
|
||||
setInterval(loadStats, 30000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stats-card {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stat-item :deep(.ant-card-body) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.stat-item :deep(.ant-statistic-title) {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-item :deep(.ant-statistic-content) {
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stat-extra {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.trend {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.trend.up {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.trend.down {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.compare {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
</style>
|
||||
@@ -3,15 +3,20 @@ import { createPinia } from 'pinia'
|
||||
import Antd from 'ant-design-vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import { useAuthStore } from './store/auth.js'
|
||||
import 'ant-design-vue/dist/antd.css'
|
||||
import './styles/global.css'
|
||||
|
||||
// DataV组件按需引入,避免Vue 3兼容性问题
|
||||
const app = createApp(App)
|
||||
const pinia = createPinia()
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(pinia)
|
||||
app.use(router)
|
||||
app.use(Antd)
|
||||
|
||||
// 初始化认证状态
|
||||
const authStore = useAuthStore()
|
||||
authStore.initAuth()
|
||||
|
||||
app.mount('#app')
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { useAuthStore } from '../store/auth.js'
|
||||
import Dashboard from '@/views/Dashboard.vue'
|
||||
import Monitor from '@/views/Monitor.vue'
|
||||
import Government from '@/views/Government.vue'
|
||||
@@ -8,52 +9,89 @@ import Risk from '@/views/Risk.vue'
|
||||
import Eco from '@/views/Eco.vue'
|
||||
import Gov from '@/views/Gov.vue'
|
||||
import Trade from '@/views/Trade.vue'
|
||||
import Login from '@/views/Login.vue'
|
||||
import UserManagement from '@/views/UserManagement.vue'
|
||||
import CattleManagement from '@/views/CattleManagement.vue'
|
||||
import MallManagement from '@/views/MallManagement.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: Login,
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
name: 'Dashboard',
|
||||
component: Dashboard
|
||||
component: Dashboard,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/monitor',
|
||||
name: 'Monitor',
|
||||
component: Monitor
|
||||
component: Monitor,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/government',
|
||||
name: 'Government',
|
||||
component: Government
|
||||
component: Government,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/finance',
|
||||
name: 'Finance',
|
||||
component: Finance
|
||||
component: Finance,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/transport',
|
||||
name: 'Transport',
|
||||
component: Transport
|
||||
component: Transport,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/risk',
|
||||
name: 'Risk',
|
||||
component: Risk
|
||||
component: Risk,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/eco',
|
||||
name: 'Eco',
|
||||
component: Eco
|
||||
component: Eco,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/gov',
|
||||
name: 'Gov',
|
||||
component: Gov
|
||||
component: Gov,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/trade',
|
||||
name: 'Trade',
|
||||
component: Trade
|
||||
component: Trade,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/users',
|
||||
name: 'UserManagement',
|
||||
component: UserManagement,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/cattle',
|
||||
name: 'CattleManagement',
|
||||
component: CattleManagement,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/mall',
|
||||
name: 'MallManagement',
|
||||
component: MallManagement,
|
||||
meta: { requiresAuth: true }
|
||||
}
|
||||
]
|
||||
|
||||
@@ -62,4 +100,26 @@ const router = createRouter({
|
||||
routes
|
||||
})
|
||||
|
||||
// 路由守卫
|
||||
router.beforeEach((to, from, next) => {
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// 初始化认证状态
|
||||
if (!authStore.isAuthenticated) {
|
||||
authStore.initAuth()
|
||||
}
|
||||
|
||||
// 检查是否需要认证
|
||||
if (to.meta.requiresAuth !== false && !authStore.isAuthenticated) {
|
||||
// 需要认证但未登录,跳转到登录页
|
||||
next('/login')
|
||||
} else if (to.path === '/login' && authStore.isAuthenticated) {
|
||||
// 已登录用户访问登录页,跳转到首页
|
||||
next('/')
|
||||
} else {
|
||||
// 正常访问
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
268
admin-system/dashboard/src/services/api.js
Normal file
268
admin-system/dashboard/src/services/api.js
Normal file
@@ -0,0 +1,268 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// API配置
|
||||
const API_BASE_URL = 'http://localhost:8889';
|
||||
const API_VERSION = '/api/v1';
|
||||
|
||||
// 创建axios实例
|
||||
const apiClient = axios.create({
|
||||
baseURL: API_BASE_URL + API_VERSION,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// 请求拦截器 - 添加认证token
|
||||
apiClient.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器 - 处理错误
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => {
|
||||
return response.data;
|
||||
},
|
||||
(error) => {
|
||||
console.error('API请求错误:', error);
|
||||
|
||||
// 处理认证错误
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('auth_token');
|
||||
localStorage.removeItem('user_info');
|
||||
// 可以在这里跳转到登录页
|
||||
window.location.href = '/login';
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// ======================================
|
||||
// 认证相关API
|
||||
// ======================================
|
||||
export const authAPI = {
|
||||
// 用户登录
|
||||
login: (credentials) => apiClient.post('/auth/login', credentials),
|
||||
|
||||
// 获取用户信息
|
||||
getProfile: () => apiClient.get('/auth/profile'),
|
||||
|
||||
// 获取用户权限
|
||||
getPermissions: () => apiClient.get('/auth/permissions'),
|
||||
|
||||
// 用户注册
|
||||
register: (userData) => apiClient.post('/auth/register', userData),
|
||||
|
||||
// 刷新token
|
||||
refreshToken: () => apiClient.post('/auth/refresh'),
|
||||
|
||||
// 用户登出
|
||||
logout: () => apiClient.post('/auth/logout'),
|
||||
};
|
||||
|
||||
// ======================================
|
||||
// 用户管理API
|
||||
// ======================================
|
||||
export const userAPI = {
|
||||
// 获取用户列表
|
||||
getUsers: (params) => apiClient.get('/users', { params }),
|
||||
|
||||
// 创建用户
|
||||
createUser: (userData) => apiClient.post('/users', userData),
|
||||
|
||||
// 更新用户
|
||||
updateUser: (id, userData) => apiClient.put(`/users/${id}`, userData),
|
||||
|
||||
// 删除用户
|
||||
deleteUser: (id) => apiClient.delete(`/users/${id}`),
|
||||
|
||||
// 获取角色列表
|
||||
getRoles: () => apiClient.get('/users/roles'),
|
||||
|
||||
// 获取权限列表
|
||||
getPermissions: () => apiClient.get('/users/permissions'),
|
||||
};
|
||||
|
||||
// ======================================
|
||||
// 牛只档案API
|
||||
// ======================================
|
||||
export const cattleAPI = {
|
||||
// 获取牛只列表
|
||||
getCattle: (params) => apiClient.get('/cattle', { params }),
|
||||
|
||||
// 获取牛只详情
|
||||
getCattleDetail: (id) => apiClient.get(`/cattle/${id}`),
|
||||
|
||||
// 创建牛只档案
|
||||
createCattle: (cattleData) => apiClient.post('/cattle', cattleData),
|
||||
|
||||
// 更新牛只信息
|
||||
updateCattle: (id, cattleData) => apiClient.put(`/cattle/${id}`, cattleData),
|
||||
|
||||
// 删除牛只档案
|
||||
deleteCattle: (id) => apiClient.delete(`/cattle/${id}`),
|
||||
|
||||
// 获取饲养记录
|
||||
getFeedingRecords: (cattleId, params) => apiClient.get(`/cattle/${cattleId}/feeding`, { params }),
|
||||
|
||||
// 添加饲养记录
|
||||
addFeedingRecord: (cattleId, recordData) => apiClient.post(`/cattle/${cattleId}/feeding`, recordData),
|
||||
|
||||
// 获取统计数据
|
||||
getStatistics: () => apiClient.get('/cattle/statistics'),
|
||||
};
|
||||
|
||||
// ======================================
|
||||
// 金融服务API
|
||||
// ======================================
|
||||
export const financeAPI = {
|
||||
// 贷款管理
|
||||
getLoans: (params) => apiClient.get('/finance/loans', { params }),
|
||||
getLoanDetail: (id) => apiClient.get(`/finance/loans/${id}`),
|
||||
createLoan: (loanData) => apiClient.post('/finance/loans', loanData),
|
||||
updateLoanStatus: (id, statusData) => apiClient.put(`/finance/loans/${id}/status`, statusData),
|
||||
|
||||
// 保险管理
|
||||
getInsurance: (params) => apiClient.get('/finance/insurance', { params }),
|
||||
getInsuranceDetail: (id) => apiClient.get(`/finance/insurance/${id}`),
|
||||
createInsurance: (insuranceData) => apiClient.post('/finance/insurance', insuranceData),
|
||||
|
||||
// 理赔管理
|
||||
getClaims: (params) => apiClient.get('/finance/claims', { params }),
|
||||
createClaim: (claimData) => apiClient.post('/finance/claims', claimData),
|
||||
|
||||
// 统计数据
|
||||
getStatistics: () => apiClient.get('/finance/statistics'),
|
||||
};
|
||||
|
||||
// ======================================
|
||||
// 交易管理API
|
||||
// ======================================
|
||||
export const tradingAPI = {
|
||||
// 交易记录
|
||||
getTransactions: (params) => apiClient.get('/trading/transactions', { params }),
|
||||
getTransactionDetail: (id) => apiClient.get(`/trading/transactions/${id}`),
|
||||
createTransaction: (transactionData) => apiClient.post('/trading/transactions', transactionData),
|
||||
updateTransactionStatus: (id, statusData) => apiClient.put(`/trading/transactions/${id}/status`, statusData),
|
||||
|
||||
// 合同管理
|
||||
getContracts: (params) => apiClient.get('/trading/contracts', { params }),
|
||||
getContractDetail: (id) => apiClient.get(`/trading/contracts/${id}`),
|
||||
createContract: (contractData) => apiClient.post('/trading/contracts', contractData),
|
||||
|
||||
// 统计数据
|
||||
getStatistics: () => apiClient.get('/trading/statistics'),
|
||||
};
|
||||
|
||||
// ======================================
|
||||
// 政府监管API
|
||||
// ======================================
|
||||
export const governmentAPI = {
|
||||
// 牧场监管
|
||||
getFarmSupervision: (params) => apiClient.get('/government/farms/supervision', { params }),
|
||||
|
||||
// 检查记录
|
||||
getInspections: (params) => apiClient.get('/government/inspections', { params }),
|
||||
createInspection: (inspectionData) => apiClient.post('/government/inspections', inspectionData),
|
||||
|
||||
// 质量追溯
|
||||
getTraceability: (productId) => apiClient.get(`/government/traceability/${productId}`),
|
||||
|
||||
// 政策法规
|
||||
getPolicies: (params) => apiClient.get('/government/policies', { params }),
|
||||
|
||||
// 统计数据
|
||||
getStatistics: () => apiClient.get('/government/statistics'),
|
||||
|
||||
// 生成报告
|
||||
generateReport: (reportData) => apiClient.post('/government/reports', reportData),
|
||||
};
|
||||
|
||||
// ======================================
|
||||
// 商城管理API
|
||||
// ======================================
|
||||
export const mallAPI = {
|
||||
// 商品管理
|
||||
getProducts: (params) => apiClient.get('/mall/products', { params }),
|
||||
getProductDetail: (id) => apiClient.get(`/mall/products/${id}`),
|
||||
createProduct: (productData) => apiClient.post('/mall/products', productData),
|
||||
updateProduct: (id, productData) => apiClient.put(`/mall/products/${id}`, productData),
|
||||
deleteProduct: (id) => apiClient.delete(`/mall/products/${id}`),
|
||||
|
||||
// 订单管理
|
||||
getOrders: (params) => apiClient.get('/mall/orders', { params }),
|
||||
getOrderDetail: (id) => apiClient.get(`/mall/orders/${id}`),
|
||||
createOrder: (orderData) => apiClient.post('/mall/orders', orderData),
|
||||
updateOrderStatus: (id, statusData) => apiClient.put(`/mall/orders/${id}/status`, statusData),
|
||||
|
||||
// 商品评价
|
||||
getProductReviews: (productId, params) => apiClient.get(`/mall/products/${productId}/reviews`, { params }),
|
||||
|
||||
// 统计数据
|
||||
getStatistics: () => apiClient.get('/mall/statistics'),
|
||||
};
|
||||
|
||||
// ======================================
|
||||
// 大屏数据API
|
||||
// ======================================
|
||||
export const dashboardAPI = {
|
||||
// 概览数据
|
||||
getOverview: () => apiClient.get('/dashboard/overview'),
|
||||
|
||||
// 实时数据
|
||||
getRealtime: () => apiClient.get('/dashboard/realtime'),
|
||||
|
||||
// 地图数据
|
||||
getMapRegions: () => apiClient.get('/dashboard/map/regions'),
|
||||
getRegionDetail: (regionId) => apiClient.get(`/dashboard/map/region/${regionId}`),
|
||||
|
||||
// 各模块数据
|
||||
getFarmData: () => cattleAPI.getStatistics(),
|
||||
getFinanceData: () => financeAPI.getStatistics(),
|
||||
getTradingData: () => tradingAPI.getStatistics(),
|
||||
getGovernmentData: () => governmentAPI.getStatistics(),
|
||||
getMallData: () => mallAPI.getStatistics(),
|
||||
};
|
||||
|
||||
// ======================================
|
||||
// 系统管理API
|
||||
// ======================================
|
||||
export const systemAPI = {
|
||||
// 健康检查
|
||||
getHealth: () => axios.get(`${API_BASE_URL}/health`),
|
||||
|
||||
// 数据库状态
|
||||
getDatabaseStatus: () => apiClient.get('/database/status'),
|
||||
|
||||
// 数据库表信息
|
||||
getDatabaseTables: () => apiClient.get('/database/tables'),
|
||||
|
||||
// 操作日志
|
||||
getOperationLogs: (params) => apiClient.get('/logs/operations', { params }),
|
||||
};
|
||||
|
||||
// 导出所有API
|
||||
export default {
|
||||
auth: authAPI,
|
||||
user: userAPI,
|
||||
cattle: cattleAPI,
|
||||
finance: financeAPI,
|
||||
trading: tradingAPI,
|
||||
government: governmentAPI,
|
||||
mall: mallAPI,
|
||||
dashboard: dashboardAPI,
|
||||
system: systemAPI,
|
||||
};
|
||||
|
||||
// 导出axios实例供其他地方使用
|
||||
export { apiClient };
|
||||
@@ -1,11 +1,10 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const API_BASE_URL = 'http://localhost:8000/api/v1/dashboard';
|
||||
import { dashboardAPI } from './api.js';
|
||||
|
||||
// 使用新的API服务
|
||||
export const fetchOverviewData = async () => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/overview`);
|
||||
return response.data;
|
||||
const response = await dashboardAPI.getOverview();
|
||||
return response.data || {};
|
||||
} catch (error) {
|
||||
console.error('Error fetching overview data:', error);
|
||||
return {};
|
||||
@@ -14,8 +13,8 @@ export const fetchOverviewData = async () => {
|
||||
|
||||
export const fetchRealtimeData = async () => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/realtime`);
|
||||
return response.data;
|
||||
const response = await dashboardAPI.getRealtime();
|
||||
return response.data || {};
|
||||
} catch (error) {
|
||||
console.error('Error fetching realtime data:', error);
|
||||
return {};
|
||||
@@ -24,8 +23,8 @@ export const fetchRealtimeData = async () => {
|
||||
|
||||
export const fetchFarmData = async () => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/farm`);
|
||||
return response.data;
|
||||
const response = await dashboardAPI.getFarmData();
|
||||
return response.data || [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching farm data:', error);
|
||||
return [];
|
||||
@@ -34,8 +33,8 @@ export const fetchFarmData = async () => {
|
||||
|
||||
export const fetchGovernmentData = async (type) => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/government/${type}`);
|
||||
return response.data;
|
||||
const response = await dashboardAPI.getGovernmentData();
|
||||
return response.data || [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching government data:', error);
|
||||
return [];
|
||||
@@ -44,8 +43,8 @@ export const fetchGovernmentData = async (type) => {
|
||||
|
||||
export const fetchFinanceData = async (type) => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/finance/${type}`);
|
||||
return response.data;
|
||||
const response = await dashboardAPI.getFinanceData();
|
||||
return response.data || [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching finance data:', error);
|
||||
return [];
|
||||
@@ -54,8 +53,8 @@ export const fetchFinanceData = async (type) => {
|
||||
|
||||
export const fetchMapData = async () => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/map/regions`);
|
||||
return response.data;
|
||||
const response = await dashboardAPI.getMapRegions();
|
||||
return response.regions || [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching map data:', error);
|
||||
return [];
|
||||
@@ -64,8 +63,8 @@ export const fetchMapData = async () => {
|
||||
|
||||
export const fetchRegionDetail = async (regionId) => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/map/region/${regionId}`);
|
||||
return response.data;
|
||||
const response = await dashboardAPI.getRegionDetail(regionId);
|
||||
return response || {};
|
||||
} catch (error) {
|
||||
console.error('Error fetching region detail:', error);
|
||||
return {};
|
||||
|
||||
154
admin-system/dashboard/src/store/auth.js
Normal file
154
admin-system/dashboard/src/store/auth.js
Normal file
@@ -0,0 +1,154 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { authAPI } from '../services/api.js';
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: () => ({
|
||||
user: null,
|
||||
token: localStorage.getItem('auth_token'),
|
||||
permissions: [],
|
||||
isAuthenticated: false,
|
||||
loading: false,
|
||||
error: null,
|
||||
}),
|
||||
|
||||
getters: {
|
||||
// 检查用户是否有特定权限
|
||||
hasPermission: (state) => (permission) => {
|
||||
return state.permissions.includes(permission);
|
||||
},
|
||||
|
||||
// 检查用户是否有任一权限
|
||||
hasAnyPermission: (state) => (permissions) => {
|
||||
return permissions.some(permission => state.permissions.includes(permission));
|
||||
},
|
||||
|
||||
// 检查用户是否有所有权限
|
||||
hasAllPermissions: (state) => (permissions) => {
|
||||
return permissions.every(permission => state.permissions.includes(permission));
|
||||
},
|
||||
|
||||
// 获取用户类型
|
||||
userType: (state) => state.user?.user_type,
|
||||
|
||||
// 获取用户名
|
||||
username: (state) => state.user?.username,
|
||||
|
||||
// 获取真实姓名
|
||||
realName: (state) => state.user?.real_name,
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 用户登录
|
||||
async login(credentials) {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const response = await authAPI.login(credentials);
|
||||
|
||||
if (response.success) {
|
||||
const { token, user } = response.data;
|
||||
|
||||
// 保存token和用户信息
|
||||
this.token = token;
|
||||
this.user = user;
|
||||
this.isAuthenticated = true;
|
||||
|
||||
// 存储到localStorage
|
||||
localStorage.setItem('auth_token', token);
|
||||
localStorage.setItem('user_info', JSON.stringify(user));
|
||||
|
||||
// 获取用户权限
|
||||
await this.loadPermissions();
|
||||
|
||||
return { success: true };
|
||||
} else {
|
||||
this.error = response.message || '登录失败';
|
||||
return { success: false, message: this.error };
|
||||
}
|
||||
} catch (error) {
|
||||
this.error = error.response?.data?.message || '登录失败,请检查网络连接';
|
||||
return { success: false, message: this.error };
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 获取用户权限
|
||||
async loadPermissions() {
|
||||
try {
|
||||
const response = await authAPI.getPermissions();
|
||||
if (response.success) {
|
||||
this.permissions = response.data.permissions || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取权限失败:', error);
|
||||
this.permissions = [];
|
||||
}
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
async loadProfile() {
|
||||
try {
|
||||
const response = await authAPI.getProfile();
|
||||
if (response.success) {
|
||||
this.user = response.data;
|
||||
localStorage.setItem('user_info', JSON.stringify(this.user));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 用户登出
|
||||
async logout() {
|
||||
try {
|
||||
await authAPI.logout();
|
||||
} catch (error) {
|
||||
console.error('登出失败:', error);
|
||||
} finally {
|
||||
// 清除本地数据
|
||||
this.user = null;
|
||||
this.token = null;
|
||||
this.permissions = [];
|
||||
this.isAuthenticated = false;
|
||||
this.error = null;
|
||||
|
||||
localStorage.removeItem('auth_token');
|
||||
localStorage.removeItem('user_info');
|
||||
}
|
||||
},
|
||||
|
||||
// 初始化认证状态
|
||||
initAuth() {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
const userInfo = localStorage.getItem('user_info');
|
||||
|
||||
if (token && userInfo) {
|
||||
try {
|
||||
this.token = token;
|
||||
this.user = JSON.parse(userInfo);
|
||||
this.isAuthenticated = true;
|
||||
|
||||
// 重新获取权限
|
||||
this.loadPermissions();
|
||||
} catch (error) {
|
||||
console.error('初始化认证状态失败:', error);
|
||||
this.clearAuth();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 清除认证状态
|
||||
clearAuth() {
|
||||
this.user = null;
|
||||
this.token = null;
|
||||
this.permissions = [];
|
||||
this.isAuthenticated = false;
|
||||
this.error = null;
|
||||
|
||||
localStorage.removeItem('auth_token');
|
||||
localStorage.removeItem('user_info');
|
||||
},
|
||||
},
|
||||
});
|
||||
223
admin-system/dashboard/src/store/dashboard.js
Normal file
223
admin-system/dashboard/src/store/dashboard.js
Normal file
@@ -0,0 +1,223 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { dashboardAPI } from '../services/api.js';
|
||||
|
||||
export const useDashboardStore = defineStore('dashboard', {
|
||||
state: () => ({
|
||||
// 概览数据
|
||||
overview: {
|
||||
totalCattle: 0,
|
||||
totalFarms: 0,
|
||||
totalValue: 0,
|
||||
monthlyGrowth: 0,
|
||||
loading: false,
|
||||
},
|
||||
|
||||
// 实时数据
|
||||
realtime: {
|
||||
activeTransactions: 0,
|
||||
onlineUsers: 0,
|
||||
systemStatus: 'normal',
|
||||
lastUpdate: null,
|
||||
loading: false,
|
||||
},
|
||||
|
||||
// 地图数据
|
||||
mapData: {
|
||||
regions: [],
|
||||
selectedRegion: null,
|
||||
loading: false,
|
||||
},
|
||||
|
||||
// 各模块统计数据
|
||||
statistics: {
|
||||
cattle: null,
|
||||
finance: null,
|
||||
trading: null,
|
||||
government: null,
|
||||
mall: null,
|
||||
loading: false,
|
||||
},
|
||||
|
||||
// 错误状态
|
||||
error: null,
|
||||
}),
|
||||
|
||||
getters: {
|
||||
// 获取总览卡片数据
|
||||
overviewCards: (state) => [
|
||||
{
|
||||
title: '总牛只数量',
|
||||
value: state.overview.totalCattle,
|
||||
unit: '头',
|
||||
icon: 'cattle',
|
||||
trend: 'up',
|
||||
change: '+12%',
|
||||
},
|
||||
{
|
||||
title: '注册牧场',
|
||||
value: state.overview.totalFarms,
|
||||
unit: '个',
|
||||
icon: 'farm',
|
||||
trend: 'up',
|
||||
change: '+8%',
|
||||
},
|
||||
{
|
||||
title: '总产值',
|
||||
value: state.overview.totalValue,
|
||||
unit: '万元',
|
||||
icon: 'money',
|
||||
trend: 'up',
|
||||
change: '+15%',
|
||||
},
|
||||
{
|
||||
title: '月增长率',
|
||||
value: state.overview.monthlyGrowth,
|
||||
unit: '%',
|
||||
icon: 'growth',
|
||||
trend: 'up',
|
||||
change: '+2.3%',
|
||||
},
|
||||
],
|
||||
|
||||
// 地图区域数据
|
||||
mapRegions: (state) => state.mapData.regions,
|
||||
|
||||
// 选中的区域详情
|
||||
selectedRegionDetail: (state) => state.mapData.selectedRegion,
|
||||
|
||||
// 系统状态指示器
|
||||
systemStatus: (state) => ({
|
||||
status: state.realtime.systemStatus,
|
||||
color: state.realtime.systemStatus === 'normal' ? 'green' :
|
||||
state.realtime.systemStatus === 'warning' ? 'orange' : 'red',
|
||||
text: state.realtime.systemStatus === 'normal' ? '正常' :
|
||||
state.realtime.systemStatus === 'warning' ? '警告' : '异常',
|
||||
}),
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 加载概览数据
|
||||
async loadOverview() {
|
||||
this.overview.loading = true;
|
||||
try {
|
||||
const response = await dashboardAPI.getOverview();
|
||||
if (response.success) {
|
||||
this.overview = {
|
||||
...this.overview,
|
||||
...response.data,
|
||||
loading: false,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载概览数据失败:', error);
|
||||
this.error = '加载概览数据失败';
|
||||
} finally {
|
||||
this.overview.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载实时数据
|
||||
async loadRealtime() {
|
||||
this.realtime.loading = true;
|
||||
try {
|
||||
const response = await dashboardAPI.getRealtime();
|
||||
if (response.success) {
|
||||
this.realtime = {
|
||||
...this.realtime,
|
||||
...response.data,
|
||||
lastUpdate: new Date(),
|
||||
loading: false,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载实时数据失败:', error);
|
||||
this.error = '加载实时数据失败';
|
||||
} finally {
|
||||
this.realtime.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载地图数据
|
||||
async loadMapData() {
|
||||
this.mapData.loading = true;
|
||||
try {
|
||||
const response = await dashboardAPI.getMapRegions();
|
||||
if (response.regions) {
|
||||
this.mapData.regions = response.regions;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载地图数据失败:', error);
|
||||
this.error = '加载地图数据失败';
|
||||
} finally {
|
||||
this.mapData.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 选择地图区域
|
||||
async selectRegion(regionId) {
|
||||
try {
|
||||
const response = await dashboardAPI.getRegionDetail(regionId);
|
||||
this.mapData.selectedRegion = response;
|
||||
} catch (error) {
|
||||
console.error('加载区域详情失败:', error);
|
||||
this.error = '加载区域详情失败';
|
||||
}
|
||||
},
|
||||
|
||||
// 加载统计数据
|
||||
async loadStatistics() {
|
||||
this.statistics.loading = true;
|
||||
try {
|
||||
const [cattle, finance, trading, government, mall] = await Promise.all([
|
||||
dashboardAPI.getFarmData(),
|
||||
dashboardAPI.getFinanceData(),
|
||||
dashboardAPI.getTradingData(),
|
||||
dashboardAPI.getGovernmentData(),
|
||||
dashboardAPI.getMallData(),
|
||||
]);
|
||||
|
||||
this.statistics = {
|
||||
cattle: cattle.data,
|
||||
finance: finance.data,
|
||||
trading: trading.data,
|
||||
government: government.data,
|
||||
mall: mall.data,
|
||||
loading: false,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('加载统计数据失败:', error);
|
||||
this.error = '加载统计数据失败';
|
||||
} finally {
|
||||
this.statistics.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 初始化大屏数据
|
||||
async initDashboard() {
|
||||
await Promise.all([
|
||||
this.loadOverview(),
|
||||
this.loadRealtime(),
|
||||
this.loadMapData(),
|
||||
this.loadStatistics(),
|
||||
]);
|
||||
},
|
||||
|
||||
// 定时刷新数据
|
||||
startAutoRefresh(interval = 30000) {
|
||||
// 每30秒刷新一次实时数据
|
||||
setInterval(() => {
|
||||
this.loadRealtime();
|
||||
}, interval);
|
||||
|
||||
// 每5分钟刷新一次统计数据
|
||||
setInterval(() => {
|
||||
this.loadStatistics();
|
||||
}, interval * 10);
|
||||
},
|
||||
|
||||
// 清除错误
|
||||
clearError() {
|
||||
this.error = null;
|
||||
},
|
||||
},
|
||||
});
|
||||
2
admin-system/dashboard/src/store/index.js
Normal file
2
admin-system/dashboard/src/store/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export { useAuthStore } from './auth.js';
|
||||
export { useDashboardStore } from './dashboard.js';
|
||||
551
admin-system/dashboard/src/views/CattleManagement.vue
Normal file
551
admin-system/dashboard/src/views/CattleManagement.vue
Normal file
@@ -0,0 +1,551 @@
|
||||
<template>
|
||||
<div class="cattle-management">
|
||||
<a-card title="牛只档案管理" :bordered="false">
|
||||
<!-- 操作按钮 -->
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="showAddModal = true">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
添加牛只
|
||||
</a-button>
|
||||
<a-button @click="loadCattle">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
刷新
|
||||
</a-button>
|
||||
<a-button @click="exportData">
|
||||
<template #icon><ExportOutlined /></template>
|
||||
导出
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-section">
|
||||
<a-row :gutter="16" style="margin-bottom: 24px;">
|
||||
<a-col :span="6">
|
||||
<a-card :bordered="false" class="stat-card">
|
||||
<a-statistic
|
||||
title="总牛只数量"
|
||||
:value="stats.total"
|
||||
suffix="头"
|
||||
:value-style="{ color: '#3f8600' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card :bordered="false" class="stat-card">
|
||||
<a-statistic
|
||||
title="健康牛只"
|
||||
:value="stats.healthy"
|
||||
suffix="头"
|
||||
:value-style="{ color: '#52c41a' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card :bordered="false" class="stat-card">
|
||||
<a-statistic
|
||||
title="平均体重"
|
||||
:value="stats.avgWeight"
|
||||
suffix="kg"
|
||||
:value-style="{ color: '#1890ff' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card :bordered="false" class="stat-card">
|
||||
<a-statistic
|
||||
title="本月新增"
|
||||
:value="stats.monthlyNew"
|
||||
suffix="头"
|
||||
:value-style="{ color: '#722ed1' }"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<div class="search-form">
|
||||
<a-form layout="inline" :model="searchForm" @finish="handleSearch">
|
||||
<a-form-item label="耳标号">
|
||||
<a-input v-model:value="searchForm.ear_tag" placeholder="请输入耳标号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="品种">
|
||||
<a-select v-model:value="searchForm.breed" placeholder="请选择品种" style="width: 150px;">
|
||||
<a-select-option value="">全部品种</a-select-option>
|
||||
<a-select-option value="西门塔尔牛">西门塔尔牛</a-select-option>
|
||||
<a-select-option value="安格斯牛">安格斯牛</a-select-option>
|
||||
<a-select-option value="夏洛莱牛">夏洛莱牛</a-select-option>
|
||||
<a-select-option value="利木赞牛">利木赞牛</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="健康状态">
|
||||
<a-select v-model:value="searchForm.health_status" placeholder="请选择状态" style="width: 120px;">
|
||||
<a-select-option value="">全部状态</a-select-option>
|
||||
<a-select-option value="healthy">健康</a-select-option>
|
||||
<a-select-option value="sick">生病</a-select-option>
|
||||
<a-select-option value="quarantine">隔离</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="所有者">
|
||||
<a-input v-model:value="searchForm.owner_name" placeholder="请输入所有者" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" html-type="submit">搜索</a-button>
|
||||
<a-button style="margin-left: 8px;" @click="resetSearch">重置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<!-- 牛只表格 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="cattle"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
row-key="id"
|
||||
:scroll="{ x: 1500 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'ear_tag'">
|
||||
<a-tag color="blue">{{ record.ear_tag }}</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'health_status'">
|
||||
<a-tag :color="getHealthStatusColor(record.health_status)">
|
||||
{{ getHealthStatusText(record.health_status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'gender'">
|
||||
<a-tag :color="record.gender === 'male' ? 'blue' : 'pink'">
|
||||
{{ record.gender === 'male' ? '公牛' : '母牛' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'age'">
|
||||
{{ calculateAge(record.birth_date) }}个月
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="viewCattle(record)">查看</a-button>
|
||||
<a-button type="link" size="small" @click="editCattle(record)">编辑</a-button>
|
||||
<a-button type="link" size="small" @click="viewFeedingRecords(record)">饲养记录</a-button>
|
||||
<a-popconfirm
|
||||
title="确定要删除这头牛只吗?"
|
||||
@confirm="deleteCattle(record.id)"
|
||||
>
|
||||
<a-button type="link" size="small" danger>删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 添加/编辑牛只模态框 -->
|
||||
<a-modal
|
||||
v-model:open="showAddModal"
|
||||
:title="editingCattle ? '编辑牛只' : '添加牛只'"
|
||||
@ok="handleSaveCattle"
|
||||
@cancel="handleCancel"
|
||||
:confirm-loading="saving"
|
||||
width="800px"
|
||||
>
|
||||
<a-form :model="cattleForm" :rules="rules" ref="cattleFormRef" layout="vertical">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="耳标号" name="ear_tag">
|
||||
<a-input v-model:value="cattleForm.ear_tag" placeholder="请输入耳标号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="名称" name="name">
|
||||
<a-input v-model:value="cattleForm.name" placeholder="请输入牛只名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="品种" name="breed">
|
||||
<a-select v-model:value="cattleForm.breed" placeholder="请选择品种">
|
||||
<a-select-option value="西门塔尔牛">西门塔尔牛</a-select-option>
|
||||
<a-select-option value="安格斯牛">安格斯牛</a-select-option>
|
||||
<a-select-option value="夏洛莱牛">夏洛莱牛</a-select-option>
|
||||
<a-select-option value="利木赞牛">利木赞牛</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="性别" name="gender">
|
||||
<a-radio-group v-model:value="cattleForm.gender">
|
||||
<a-radio value="male">公牛</a-radio>
|
||||
<a-radio value="female">母牛</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="出生日期" name="birth_date">
|
||||
<a-date-picker
|
||||
v-model:value="cattleForm.birth_date"
|
||||
style="width: 100%;"
|
||||
placeholder="请选择出生日期"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="毛色" name="color">
|
||||
<a-input v-model:value="cattleForm.color" placeholder="请输入毛色" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="体重(kg)" name="weight">
|
||||
<a-input-number
|
||||
v-model:value="cattleForm.weight"
|
||||
:min="0"
|
||||
:max="2000"
|
||||
style="width: 100%;"
|
||||
placeholder="请输入体重"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="健康状态" name="health_status">
|
||||
<a-select v-model:value="cattleForm.health_status" placeholder="请选择健康状态">
|
||||
<a-select-option value="healthy">健康</a-select-option>
|
||||
<a-select-option value="sick">生病</a-select-option>
|
||||
<a-select-option value="quarantine">隔离</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="牧场位置" name="farm_location">
|
||||
<a-input v-model:value="cattleForm.farm_location" placeholder="请输入牧场位置" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 查看牛只详情模态框 -->
|
||||
<a-modal
|
||||
v-model:open="showDetailModal"
|
||||
title="牛只详细信息"
|
||||
:footer="null"
|
||||
width="900px"
|
||||
>
|
||||
<div v-if="selectedCattle" class="cattle-detail">
|
||||
<a-descriptions title="基本信息" :column="2" bordered>
|
||||
<a-descriptions-item label="耳标号">
|
||||
<a-tag color="blue">{{ selectedCattle.ear_tag }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="名称">{{ selectedCattle.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="品种">{{ selectedCattle.breed }}</a-descriptions-item>
|
||||
<a-descriptions-item label="性别">
|
||||
<a-tag :color="selectedCattle.gender === 'male' ? 'blue' : 'pink'">
|
||||
{{ selectedCattle.gender === 'male' ? '公牛' : '母牛' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="出生日期">{{ selectedCattle.birth_date }}</a-descriptions-item>
|
||||
<a-descriptions-item label="年龄">{{ calculateAge(selectedCattle.birth_date) }}个月</a-descriptions-item>
|
||||
<a-descriptions-item label="毛色">{{ selectedCattle.color }}</a-descriptions-item>
|
||||
<a-descriptions-item label="体重">{{ selectedCattle.weight }}kg</a-descriptions-item>
|
||||
<a-descriptions-item label="健康状态">
|
||||
<a-tag :color="getHealthStatusColor(selectedCattle.health_status)">
|
||||
{{ getHealthStatusText(selectedCattle.health_status) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="牧场位置">{{ selectedCattle.farm_location }}</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">{{ selectedCattle.created_at }}</a-descriptions-item>
|
||||
<a-descriptions-item label="更新时间">{{ selectedCattle.updated_at }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { PlusOutlined, ReloadOutlined, ExportOutlined } from '@ant-design/icons-vue';
|
||||
import { cattleAPI } from '../services/api.js';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// 响应式数据
|
||||
const cattle = ref([]);
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const showAddModal = ref(false);
|
||||
const showDetailModal = ref(false);
|
||||
const editingCattle = ref(null);
|
||||
const selectedCattle = ref(null);
|
||||
const cattleFormRef = ref();
|
||||
|
||||
// 统计数据
|
||||
const stats = ref({
|
||||
total: 0,
|
||||
healthy: 0,
|
||||
avgWeight: 0,
|
||||
monthlyNew: 0,
|
||||
});
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
ear_tag: '',
|
||||
breed: '',
|
||||
health_status: '',
|
||||
owner_name: '',
|
||||
});
|
||||
|
||||
// 牛只表单
|
||||
const cattleForm = reactive({
|
||||
ear_tag: '',
|
||||
name: '',
|
||||
breed: '',
|
||||
gender: '',
|
||||
birth_date: null,
|
||||
color: '',
|
||||
weight: null,
|
||||
health_status: 'healthy',
|
||||
farm_location: '',
|
||||
});
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`,
|
||||
});
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{ title: '耳标号', dataIndex: 'ear_tag', key: 'ear_tag', width: 120, fixed: 'left' },
|
||||
{ title: '名称', dataIndex: 'name', key: 'name', width: 100 },
|
||||
{ title: '品种', dataIndex: 'breed', key: 'breed', width: 120 },
|
||||
{ title: '性别', dataIndex: 'gender', key: 'gender', width: 80 },
|
||||
{ title: '年龄', key: 'age', width: 80 },
|
||||
{ title: '体重(kg)', dataIndex: 'weight', key: 'weight', width: 100 },
|
||||
{ title: '毛色', dataIndex: 'color', key: 'color', width: 80 },
|
||||
{ title: '健康状态', dataIndex: 'health_status', key: 'health_status', width: 120 },
|
||||
{ title: '牧场位置', dataIndex: 'farm_location', key: 'farm_location', width: 200 },
|
||||
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at', width: 160 },
|
||||
{ title: '操作', key: 'action', width: 250, fixed: 'right' },
|
||||
];
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
ear_tag: [{ required: true, message: '请输入耳标号' }],
|
||||
name: [{ required: true, message: '请输入牛只名称' }],
|
||||
breed: [{ required: true, message: '请选择品种' }],
|
||||
gender: [{ required: true, message: '请选择性别' }],
|
||||
birth_date: [{ required: true, message: '请选择出生日期' }],
|
||||
weight: [{ required: true, message: '请输入体重' }],
|
||||
health_status: [{ required: true, message: '请选择健康状态' }],
|
||||
};
|
||||
|
||||
// 加载牛只列表
|
||||
const loadCattle = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.current,
|
||||
limit: pagination.pageSize,
|
||||
...searchForm,
|
||||
};
|
||||
|
||||
const response = await cattleAPI.getCattle(params);
|
||||
|
||||
if (response.success) {
|
||||
cattle.value = response.data.cattle || [];
|
||||
pagination.total = response.data.pagination?.total || 0;
|
||||
} else {
|
||||
message.error(response.message || '获取牛只列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取牛只列表失败:', error);
|
||||
message.error('获取牛只列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载统计数据
|
||||
const loadStats = async () => {
|
||||
try {
|
||||
const response = await cattleAPI.getStatistics();
|
||||
if (response.success) {
|
||||
stats.value = response.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 计算年龄(月份)
|
||||
const calculateAge = (birthDate) => {
|
||||
if (!birthDate) return 0;
|
||||
return dayjs().diff(dayjs(birthDate), 'month');
|
||||
};
|
||||
|
||||
// 获取健康状态颜色
|
||||
const getHealthStatusColor = (status) => {
|
||||
const colors = {
|
||||
healthy: 'green',
|
||||
sick: 'red',
|
||||
quarantine: 'orange',
|
||||
};
|
||||
return colors[status] || 'default';
|
||||
};
|
||||
|
||||
// 获取健康状态文本
|
||||
const getHealthStatusText = (status) => {
|
||||
const texts = {
|
||||
healthy: '健康',
|
||||
sick: '生病',
|
||||
quarantine: '隔离',
|
||||
};
|
||||
return texts[status] || status;
|
||||
};
|
||||
|
||||
// 表格变化处理
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.current = pag.current;
|
||||
pagination.pageSize = pag.pageSize;
|
||||
loadCattle();
|
||||
};
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1;
|
||||
loadCattle();
|
||||
};
|
||||
|
||||
// 重置搜索
|
||||
const resetSearch = () => {
|
||||
Object.assign(searchForm, {
|
||||
ear_tag: '',
|
||||
breed: '',
|
||||
health_status: '',
|
||||
owner_name: '',
|
||||
});
|
||||
pagination.current = 1;
|
||||
loadCattle();
|
||||
};
|
||||
|
||||
// 查看牛只详情
|
||||
const viewCattle = (record) => {
|
||||
selectedCattle.value = record;
|
||||
showDetailModal.value = true;
|
||||
};
|
||||
|
||||
// 编辑牛只
|
||||
const editCattle = (record) => {
|
||||
editingCattle.value = record;
|
||||
Object.assign(cattleForm, {
|
||||
...record,
|
||||
birth_date: record.birth_date ? dayjs(record.birth_date) : null,
|
||||
});
|
||||
showAddModal.value = true;
|
||||
};
|
||||
|
||||
// 查看饲养记录
|
||||
const viewFeedingRecords = (record) => {
|
||||
message.info(`查看 ${record.name} 的饲养记录`);
|
||||
// TODO: 实现饲养记录查看
|
||||
};
|
||||
|
||||
// 删除牛只
|
||||
const deleteCattle = async (id) => {
|
||||
try {
|
||||
const response = await cattleAPI.deleteCattle(id);
|
||||
if (response.success) {
|
||||
message.success('删除成功');
|
||||
loadCattle();
|
||||
loadStats();
|
||||
} else {
|
||||
message.error(response.message || '删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除牛只失败:', error);
|
||||
message.error('删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 保存牛只
|
||||
const handleSaveCattle = async () => {
|
||||
try {
|
||||
await cattleFormRef.value.validate();
|
||||
saving.value = true;
|
||||
|
||||
const formData = {
|
||||
...cattleForm,
|
||||
birth_date: cattleForm.birth_date ? cattleForm.birth_date.format('YYYY-MM-DD') : null,
|
||||
};
|
||||
|
||||
let response;
|
||||
if (editingCattle.value) {
|
||||
response = await cattleAPI.updateCattle(editingCattle.value.id, formData);
|
||||
} else {
|
||||
response = await cattleAPI.createCattle(formData);
|
||||
}
|
||||
|
||||
if (response.success) {
|
||||
message.success(editingCattle.value ? '更新成功' : '创建成功');
|
||||
showAddModal.value = false;
|
||||
loadCattle();
|
||||
loadStats();
|
||||
} else {
|
||||
message.error(response.message || '保存失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存牛只失败:', error);
|
||||
message.error('保存失败');
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 取消操作
|
||||
const handleCancel = () => {
|
||||
showAddModal.value = false;
|
||||
editingCattle.value = null;
|
||||
cattleFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
// 导出数据
|
||||
const exportData = () => {
|
||||
message.success('导出功能开发中');
|
||||
};
|
||||
|
||||
// 组件挂载时加载数据
|
||||
onMounted(() => {
|
||||
loadCattle();
|
||||
loadStats();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cattle-management {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cattle-detail {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<div class="dashboard">
|
||||
<!-- 临时添加API测试组件 -->
|
||||
<div style="position: fixed; top: 80px; right: 20px; z-index: 9999; width: 350px;">
|
||||
<ApiTest />
|
||||
</div>
|
||||
|
||||
<header class="dashboard-header">
|
||||
<div class="header-decoration"></div>
|
||||
<div class="header-title">
|
||||
@@ -118,12 +123,14 @@
|
||||
import * as echarts from 'echarts'
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import ThreeDMap from '@/components/map/ThreeDMap.vue'
|
||||
import ApiTest from '@/components/ApiTest.vue'
|
||||
import { fetchMapData } from '@/services/dashboard.js'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
ThreeDMap
|
||||
ThreeDMap,
|
||||
ApiTest
|
||||
},
|
||||
setup() {
|
||||
const currentTime = ref(new Date().toLocaleString())
|
||||
|
||||
@@ -1,130 +1,523 @@
|
||||
<template>
|
||||
<div class="finance-container">
|
||||
<h1>金融服务</h1>
|
||||
<div v-if="loading" class="loading-indicator">数据加载中...</div>
|
||||
<div v-if="error" class="error-message">数据加载失败,请稍后重试。</div>
|
||||
<div v-if="!loading && !error">
|
||||
<div class="loan-section">
|
||||
<h3>贷款数据</h3>
|
||||
<div class="chart-container">
|
||||
<div id="loan-chart" style="width: 100%; height: 300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="insurance-section">
|
||||
<h3>保险数据</h3>
|
||||
<div class="chart-container">
|
||||
<div id="insurance-chart" style="width: 100%; height: 300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="finance-page">
|
||||
<!-- 页面标题和操作按钮 -->
|
||||
<div class="page-header">
|
||||
<a-page-header title="金融服务监管" sub-title="贷款和保险业务管理">
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="showAddModal('loan')">
|
||||
<PlusOutlined /> 新增贷款申请
|
||||
</a-button>
|
||||
<a-button @click="showAddModal('insurance')">
|
||||
<SafetyOutlined /> 新增保险申请
|
||||
</a-button>
|
||||
</template>
|
||||
</a-page-header>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<a-row :gutter="16" class="stats-cards">
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic title="贷款申请总数" :value="stats.totalLoans" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic title="保险申请总数" :value="stats.totalInsurance" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="贷款总金额"
|
||||
:value="stats.totalLoanAmount"
|
||||
suffix="万元"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="保险总金额"
|
||||
:value="stats.totalInsuranceAmount"
|
||||
suffix="万元"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 标签页 -->
|
||||
<a-tabs v-model:activeKey="activeTab" class="finance-tabs">
|
||||
<a-tab-pane key="loans" tab="贷款管理">
|
||||
<!-- 贷款搜索表单 -->
|
||||
<a-card class="search-card">
|
||||
<a-form layout="inline" :model="loanSearchForm">
|
||||
<a-form-item label="申请人">
|
||||
<a-input v-model:value="loanSearchForm.applicant" placeholder="请输入申请人姓名" />
|
||||
</a-form-item>
|
||||
<a-form-item label="贷款类型">
|
||||
<a-select v-model:value="loanSearchForm.loanType" placeholder="请选择贷款类型" style="width: 150px">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="cattle">牛只质押贷款</a-select-option>
|
||||
<a-select-option value="farm">牧场贷款</a-select-option>
|
||||
<a-select-option value="equipment">设备贷款</a-select-option>
|
||||
<a-select-option value="operating">经营贷款</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select v-model:value="loanSearchForm.status" placeholder="请选择状态" style="width: 150px">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="submitted">已提交</a-select-option>
|
||||
<a-select-option value="under_review">审核中</a-select-option>
|
||||
<a-select-option value="approved">已批准</a-select-option>
|
||||
<a-select-option value="rejected">已拒绝</a-select-option>
|
||||
<a-select-option value="disbursed">已放款</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="searchLoans">
|
||||
<SearchOutlined /> 搜索
|
||||
</a-button>
|
||||
<a-button @click="resetLoanSearch" style="margin-left: 8px">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- 贷款列表 -->
|
||||
<a-card>
|
||||
<a-table
|
||||
:columns="loanColumns"
|
||||
:data-source="loans"
|
||||
:loading="loanLoading"
|
||||
:pagination="loanPagination"
|
||||
@change="handleLoanTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getLoanStatusColor(record.status)">
|
||||
{{ getLoanStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="viewLoanDetail(record)">
|
||||
详情
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="record.status === 'under_review'"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="reviewLoan(record)"
|
||||
>
|
||||
审核
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="insurance" tab="保险管理">
|
||||
<!-- 保险搜索表单 -->
|
||||
<a-card class="search-card">
|
||||
<a-form layout="inline" :model="insuranceSearchForm">
|
||||
<a-form-item label="申请人">
|
||||
<a-input v-model:value="insuranceSearchForm.applicant" placeholder="请输入申请人姓名" />
|
||||
</a-form-item>
|
||||
<a-form-item label="保险类型">
|
||||
<a-select v-model:value="insuranceSearchForm.insuranceType" placeholder="请选择保险类型" style="width: 150px">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="cattle_death">牛只死亡险</a-select-option>
|
||||
<a-select-option value="cattle_health">牛只健康险</a-select-option>
|
||||
<a-select-option value="cattle_theft">牛只盗窃险</a-select-option>
|
||||
<a-select-option value="property">财产险</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select v-model:value="insuranceSearchForm.status" placeholder="请选择状态" style="width: 150px">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="applied">已申请</a-select-option>
|
||||
<a-select-option value="underwriting">核保中</a-select-option>
|
||||
<a-select-option value="issued">已出单</a-select-option>
|
||||
<a-select-option value="active">生效中</a-select-option>
|
||||
<a-select-option value="expired">已过期</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="searchInsurance">
|
||||
<SearchOutlined /> 搜索
|
||||
</a-button>
|
||||
<a-button @click="resetInsuranceSearch" style="margin-left: 8px">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- 保险列表 -->
|
||||
<a-card>
|
||||
<a-table
|
||||
:columns="insuranceColumns"
|
||||
:data-source="insuranceList"
|
||||
:loading="insuranceLoading"
|
||||
:pagination="insurancePagination"
|
||||
@change="handleInsuranceTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getInsuranceStatusColor(record.status)">
|
||||
{{ getInsuranceStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="viewInsuranceDetail(record)">
|
||||
详情
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="record.status === 'underwriting'"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="reviewInsurance(record)"
|
||||
>
|
||||
核保
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import axios from 'axios';
|
||||
import * as echarts from 'echarts';
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { financeAPI } from '@/services/api.js';
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
SafetyOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
name: 'Finance',
|
||||
components: {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
SafetyOutlined
|
||||
},
|
||||
setup() {
|
||||
const loanData = ref([]);
|
||||
const insuranceData = ref([]);
|
||||
const loading = ref(true);
|
||||
const error = ref(false);
|
||||
const activeTab = ref('loans');
|
||||
const loanLoading = ref(false);
|
||||
const insuranceLoading = ref(false);
|
||||
|
||||
// 统计数据
|
||||
const stats = reactive({
|
||||
totalLoans: 156,
|
||||
totalInsurance: 89,
|
||||
totalLoanAmount: 2850,
|
||||
totalInsuranceAmount: 1260
|
||||
});
|
||||
|
||||
const fetchData = async () => {
|
||||
// 贷款数据
|
||||
const loans = ref([]);
|
||||
const loanSearchForm = reactive({
|
||||
applicant: '',
|
||||
loanType: '',
|
||||
status: ''
|
||||
});
|
||||
const loanPagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true
|
||||
});
|
||||
|
||||
// 保险数据
|
||||
const insuranceList = ref([]);
|
||||
const insuranceSearchForm = reactive({
|
||||
applicant: '',
|
||||
insuranceType: '',
|
||||
status: ''
|
||||
});
|
||||
const insurancePagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true
|
||||
});
|
||||
|
||||
// 贷款表格列
|
||||
const loanColumns = [
|
||||
{ title: '申请编号', dataIndex: 'id', key: 'id', width: 120 },
|
||||
{ title: '申请人', dataIndex: 'applicant_name', key: 'applicant_name' },
|
||||
{ title: '贷款类型', dataIndex: 'loan_type', key: 'loan_type' },
|
||||
{ title: '申请金额(万元)', dataIndex: 'loan_amount', key: 'loan_amount' },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status' },
|
||||
{ title: '申请时间', dataIndex: 'created_at', key: 'created_at' },
|
||||
{ title: '操作', key: 'action', width: 150 }
|
||||
];
|
||||
|
||||
// 保险表格列
|
||||
const insuranceColumns = [
|
||||
{ title: '保单号', dataIndex: 'policy_number', key: 'policy_number', width: 120 },
|
||||
{ title: '申请人', dataIndex: 'applicant_name', key: 'applicant_name' },
|
||||
{ title: '保险类型', dataIndex: 'insurance_type', key: 'insurance_type' },
|
||||
{ title: '保险金额(万元)', dataIndex: 'insured_amount', key: 'insured_amount' },
|
||||
{ title: '保费(元)', dataIndex: 'premium', key: 'premium' },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status' },
|
||||
{ title: '申请时间', dataIndex: 'created_at', key: 'created_at' },
|
||||
{ title: '操作', key: 'action', width: 150 }
|
||||
];
|
||||
|
||||
// 加载贷款数据
|
||||
const loadLoans = async () => {
|
||||
loanLoading.value = true;
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = false;
|
||||
const [loanResponse, insuranceResponse] = await Promise.all([
|
||||
axios.get('/api/loan-data'),
|
||||
axios.get('/api/insurance-data')
|
||||
]);
|
||||
loanData.value = loanResponse.data;
|
||||
insuranceData.value = insuranceResponse.data;
|
||||
renderCharts();
|
||||
} catch (err) {
|
||||
error.value = true;
|
||||
console.error('获取数据失败:', err);
|
||||
const params = {
|
||||
page: loanPagination.current,
|
||||
limit: loanPagination.pageSize,
|
||||
...loanSearchForm
|
||||
};
|
||||
const response = await financeAPI.getLoans(params);
|
||||
if (response.success) {
|
||||
loans.value = response.data.loans || [];
|
||||
loanPagination.total = response.data.pagination?.total || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取贷款列表失败:', error);
|
||||
message.error('获取贷款列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
loanLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const renderCharts = () => {
|
||||
const loanChart = echarts.init(document.getElementById('loan-chart'));
|
||||
loanChart.setOption({
|
||||
tooltip: {},
|
||||
xAxis: { data: loanData.value.map(item => item.month) },
|
||||
yAxis: {},
|
||||
series: [{ type: 'bar', data: loanData.value.map(item => item.value) }]
|
||||
});
|
||||
// 加载保险数据
|
||||
const loadInsurance = async () => {
|
||||
insuranceLoading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
page: insurancePagination.current,
|
||||
limit: insurancePagination.pageSize,
|
||||
...insuranceSearchForm
|
||||
};
|
||||
const response = await financeAPI.getInsurance(params);
|
||||
if (response.success) {
|
||||
insuranceList.value = response.data.insurance || [];
|
||||
insurancePagination.total = response.data.pagination?.total || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取保险列表失败:', error);
|
||||
message.error('获取保险列表失败');
|
||||
} finally {
|
||||
insuranceLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const insuranceChart = echarts.init(document.getElementById('insurance-chart'));
|
||||
insuranceChart.setOption({
|
||||
tooltip: { trigger: 'item' },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
data: insuranceData.value.map(item => item)
|
||||
}]
|
||||
// 贷款状态颜色
|
||||
const getLoanStatusColor = (status) => {
|
||||
const colors = {
|
||||
'submitted': 'blue',
|
||||
'under_review': 'orange',
|
||||
'approved': 'green',
|
||||
'rejected': 'red',
|
||||
'disbursed': 'purple',
|
||||
'completed': 'green',
|
||||
'overdue': 'red'
|
||||
};
|
||||
return colors[status] || 'default';
|
||||
};
|
||||
|
||||
// 贷款状态文本
|
||||
const getLoanStatusText = (status) => {
|
||||
const texts = {
|
||||
'submitted': '已提交',
|
||||
'under_review': '审核中',
|
||||
'approved': '已批准',
|
||||
'rejected': '已拒绝',
|
||||
'disbursed': '已放款',
|
||||
'completed': '已完成',
|
||||
'overdue': '逾期'
|
||||
};
|
||||
return texts[status] || status;
|
||||
};
|
||||
|
||||
// 保险状态颜色
|
||||
const getInsuranceStatusColor = (status) => {
|
||||
const colors = {
|
||||
'applied': 'blue',
|
||||
'underwriting': 'orange',
|
||||
'issued': 'green',
|
||||
'active': 'green',
|
||||
'expired': 'red',
|
||||
'cancelled': 'red'
|
||||
};
|
||||
return colors[status] || 'default';
|
||||
};
|
||||
|
||||
// 保险状态文本
|
||||
const getInsuranceStatusText = (status) => {
|
||||
const texts = {
|
||||
'applied': '已申请',
|
||||
'underwriting': '核保中',
|
||||
'issued': '已出单',
|
||||
'active': '生效中',
|
||||
'expired': '已过期',
|
||||
'cancelled': '已取消'
|
||||
};
|
||||
return texts[status] || status;
|
||||
};
|
||||
|
||||
// 搜索贷款
|
||||
const searchLoans = () => {
|
||||
loanPagination.current = 1;
|
||||
loadLoans();
|
||||
};
|
||||
|
||||
// 重置贷款搜索
|
||||
const resetLoanSearch = () => {
|
||||
Object.assign(loanSearchForm, {
|
||||
applicant: '',
|
||||
loanType: '',
|
||||
status: ''
|
||||
});
|
||||
searchLoans();
|
||||
};
|
||||
|
||||
// 搜索保险
|
||||
const searchInsurance = () => {
|
||||
insurancePagination.current = 1;
|
||||
loadInsurance();
|
||||
};
|
||||
|
||||
// 重置保险搜索
|
||||
const resetInsuranceSearch = () => {
|
||||
Object.assign(insuranceSearchForm, {
|
||||
applicant: '',
|
||||
insuranceType: '',
|
||||
status: ''
|
||||
});
|
||||
searchInsurance();
|
||||
};
|
||||
|
||||
// 表格变化处理
|
||||
const handleLoanTableChange = (pagination) => {
|
||||
loanPagination.current = pagination.current;
|
||||
loanPagination.pageSize = pagination.pageSize;
|
||||
loadLoans();
|
||||
};
|
||||
|
||||
const handleInsuranceTableChange = (pagination) => {
|
||||
insurancePagination.current = pagination.current;
|
||||
insurancePagination.pageSize = pagination.pageSize;
|
||||
loadInsurance();
|
||||
};
|
||||
|
||||
// 显示添加模态框
|
||||
const showAddModal = (type) => {
|
||||
message.info(`添加${type === 'loan' ? '贷款' : '保险'}申请功能开发中`);
|
||||
};
|
||||
|
||||
// 查看详情
|
||||
const viewLoanDetail = (record) => {
|
||||
message.info(`查看贷款详情: ${record.id}`);
|
||||
};
|
||||
|
||||
const viewInsuranceDetail = (record) => {
|
||||
message.info(`查看保险详情: ${record.policy_number}`);
|
||||
};
|
||||
|
||||
// 审核
|
||||
const reviewLoan = (record) => {
|
||||
message.info(`审核贷款: ${record.id}`);
|
||||
};
|
||||
|
||||
const reviewInsurance = (record) => {
|
||||
message.info(`核保保险: ${record.policy_number}`);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchData();
|
||||
loadLoans();
|
||||
loadInsurance();
|
||||
});
|
||||
|
||||
return {
|
||||
loanData,
|
||||
insuranceData,
|
||||
loading,
|
||||
error
|
||||
activeTab,
|
||||
stats,
|
||||
loans,
|
||||
loanLoading,
|
||||
loanSearchForm,
|
||||
loanPagination,
|
||||
loanColumns,
|
||||
insuranceList,
|
||||
insuranceLoading,
|
||||
insuranceSearchForm,
|
||||
insurancePagination,
|
||||
insuranceColumns,
|
||||
loadLoans,
|
||||
loadInsurance,
|
||||
getLoanStatusColor,
|
||||
getLoanStatusText,
|
||||
getInsuranceStatusColor,
|
||||
getInsuranceStatusText,
|
||||
searchLoans,
|
||||
resetLoanSearch,
|
||||
searchInsurance,
|
||||
resetInsuranceSearch,
|
||||
handleLoanTableChange,
|
||||
handleInsuranceTableChange,
|
||||
showAddModal,
|
||||
viewLoanDetail,
|
||||
viewInsuranceDetail,
|
||||
reviewLoan,
|
||||
reviewInsurance
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.finance-container {
|
||||
padding: 20px;
|
||||
.finance-page {
|
||||
padding: 24px;
|
||||
background: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.loan-section,
|
||||
.insurance-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
.page-header {
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.loading-indicator,
|
||||
.error-message {
|
||||
.stats-cards {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stats-cards .ant-card {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff4d4f;
|
||||
.finance-tabs {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.loan-section,
|
||||
.insurance-section {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#loan-chart,
|
||||
#insurance-chart {
|
||||
height: 250px;
|
||||
}
|
||||
.search-card .ant-form {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,104 +1,647 @@
|
||||
<template>
|
||||
<div class="government-container">
|
||||
<h1>政府平台</h1>
|
||||
<div v-if="loading" class="loading-indicator">数据加载中...</div>
|
||||
<div v-if="error" class="error-message">数据加载失败,请稍后重试。</div>
|
||||
<div v-if="!loading && !error">
|
||||
<div class="policy-section">
|
||||
<h3>政策通知</h3>
|
||||
<ul>
|
||||
<li v-for="(policy, index) in policies" :key="index">
|
||||
{{ policy.title }} - {{ policy.date }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="data-section">
|
||||
<h3>政务数据</h3>
|
||||
<a-table :dataSource="governmentData" :columns="columns" />
|
||||
</div>
|
||||
<div class="government-page">
|
||||
<!-- 页面标题和操作按钮 -->
|
||||
<div class="page-header">
|
||||
<a-page-header title="政府监管" sub-title="检查记录和质量追溯">
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="showAddModal('inspection')">
|
||||
<AuditOutlined /> 新增检查
|
||||
</a-button>
|
||||
<a-button @click="showAddModal('trace')">
|
||||
<SearchOutlined /> 质量追溯
|
||||
</a-button>
|
||||
</template>
|
||||
</a-page-header>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<a-row :gutter="16" class="stats-cards">
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic title="总检查次数" :value="stats.totalInspections" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic title="合规率" :value="stats.complianceRate" suffix="%" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic title="追溯记录" :value="stats.totalTraces" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic title="本月检查" :value="stats.monthlyInspections" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 标签页 -->
|
||||
<a-tabs v-model:activeKey="activeTab" class="government-tabs">
|
||||
<a-tab-pane key="inspections" tab="检查记录">
|
||||
<!-- 检查搜索表单 -->
|
||||
<a-card class="search-card">
|
||||
<a-form layout="inline" :model="inspectionSearchForm">
|
||||
<a-form-item label="牧场名称">
|
||||
<a-input v-model:value="inspectionSearchForm.farmName" placeholder="请输入牧场名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="检查类型">
|
||||
<a-select v-model:value="inspectionSearchForm.inspectionType" placeholder="请选择检查类型" style="width: 150px">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="safety">安全检查</a-select-option>
|
||||
<a-select-option value="health">卫生检查</a-select-option>
|
||||
<a-select-option value="environment">环保检查</a-select-option>
|
||||
<a-select-option value="quality">质量检查</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="检查结果">
|
||||
<a-select v-model:value="inspectionSearchForm.result" placeholder="请选择结果" style="width: 120px">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="passed">通过</a-select-option>
|
||||
<a-select-option value="failed">不通过</a-select-option>
|
||||
<a-select-option value="conditional">有条件通过</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="searchInspections">
|
||||
<SearchOutlined /> 搜索
|
||||
</a-button>
|
||||
<a-button @click="resetInspectionSearch" style="margin-left: 8px">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- 检查列表 -->
|
||||
<a-card>
|
||||
<a-table
|
||||
:columns="inspectionColumns"
|
||||
:data-source="inspections"
|
||||
:loading="inspectionLoading"
|
||||
:pagination="inspectionPagination"
|
||||
@change="handleInspectionTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'inspection_type'">
|
||||
<a-tag color="blue">
|
||||
{{ getInspectionTypeText(record.inspection_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'result'">
|
||||
<a-tag :color="getInspectionResultColor(record.result)">
|
||||
{{ getInspectionResultText(record.result) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="viewInspectionDetail(record)">
|
||||
详情
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="record.result === 'failed'"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="createRectification(record)"
|
||||
>
|
||||
整改通知
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="traces" tab="质量追溯">
|
||||
<!-- 追溯搜索表单 -->
|
||||
<a-card class="search-card">
|
||||
<a-form layout="inline" :model="traceSearchForm">
|
||||
<a-form-item label="追溯编号">
|
||||
<a-input v-model:value="traceSearchForm.traceId" placeholder="请输入追溯编号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="牛只耳标">
|
||||
<a-input v-model:value="traceSearchForm.earTag" placeholder="请输入牛只耳标" />
|
||||
</a-form-item>
|
||||
<a-form-item label="追溯类型">
|
||||
<a-select v-model:value="traceSearchForm.traceType" placeholder="请选择类型" style="width: 150px">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="origin">源头追溯</a-select-option>
|
||||
<a-select-option value="feed">饵料追溯</a-select-option>
|
||||
<a-select-option value="medicine">药物追溯</a-select-option>
|
||||
<a-select-option value="transport">运输追溯</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="searchTraces">
|
||||
<SearchOutlined /> 搜索
|
||||
</a-button>
|
||||
<a-button @click="resetTraceSearch" style="margin-left: 8px">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- 追溯列表 -->
|
||||
<a-card>
|
||||
<a-table
|
||||
:columns="traceColumns"
|
||||
:data-source="traces"
|
||||
:loading="traceLoading"
|
||||
:pagination="tracePagination"
|
||||
@change="handleTraceTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'trace_type'">
|
||||
<a-tag color="green">
|
||||
{{ getTraceTypeText(record.trace_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="viewTraceDetail(record)">
|
||||
详情
|
||||
</a-button>
|
||||
<a-button size="small" @click="viewTraceChain(record)">
|
||||
追溯链
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="policies" tab="政策法规">
|
||||
<!-- 政策搜索表单 -->
|
||||
<a-card class="search-card">
|
||||
<a-form layout="inline" :model="policySearchForm">
|
||||
<a-form-item label="政策标题">
|
||||
<a-input v-model:value="policySearchForm.title" placeholder="请输入政策标题" />
|
||||
</a-form-item>
|
||||
<a-form-item label="政策类型">
|
||||
<a-select v-model:value="policySearchForm.policyType" placeholder="请选择类型" style="width: 150px">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="industry">行业政策</a-select-option>
|
||||
<a-select-option value="subsidy">补贴政策</a-select-option>
|
||||
<a-select-option value="regulation">监管政策</a-select-option>
|
||||
<a-select-option value="environment">环保政策</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="searchPolicies">
|
||||
<SearchOutlined /> 搜索
|
||||
</a-button>
|
||||
<a-button @click="resetPolicySearch" style="margin-left: 8px">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- 政策列表 -->
|
||||
<a-card>
|
||||
<a-table
|
||||
:columns="policyColumns"
|
||||
:data-source="policies"
|
||||
:loading="policyLoading"
|
||||
:pagination="policyPagination"
|
||||
@change="handlePolicyTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'policy_type'">
|
||||
<a-tag color="purple">
|
||||
{{ getPolicyTypeText(record.policy_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="viewPolicyDetail(record)">
|
||||
查看
|
||||
</a-button>
|
||||
<a-button size="small" @click="downloadPolicy(record)">
|
||||
下载
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { governmentAPI } from '@/services/api.js';
|
||||
import {
|
||||
AuditOutlined,
|
||||
SearchOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
name: 'Government',
|
||||
components: {
|
||||
AuditOutlined,
|
||||
SearchOutlined
|
||||
},
|
||||
setup() {
|
||||
const policies = ref([]);
|
||||
const governmentData = ref([]);
|
||||
const columns = ref([
|
||||
{ title: '指标', dataIndex: 'indicator', key: 'indicator' },
|
||||
{ title: '数值', dataIndex: 'value', key: 'value' },
|
||||
{ title: '单位', dataIndex: 'unit', key: 'unit' },
|
||||
]);
|
||||
const loading = ref(true);
|
||||
const error = ref(false);
|
||||
const activeTab = ref('inspections');
|
||||
const inspectionLoading = ref(false);
|
||||
const traceLoading = ref(false);
|
||||
const policyLoading = ref(false);
|
||||
|
||||
// 统计数据
|
||||
const stats = reactive({
|
||||
totalInspections: 856,
|
||||
complianceRate: 92.5,
|
||||
totalTraces: 1245,
|
||||
monthlyInspections: 128
|
||||
});
|
||||
|
||||
const fetchData = async () => {
|
||||
// 检查数据
|
||||
const inspections = ref([]);
|
||||
const inspectionSearchForm = reactive({
|
||||
farmName: '',
|
||||
inspectionType: '',
|
||||
result: ''
|
||||
});
|
||||
const inspectionPagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true
|
||||
});
|
||||
|
||||
// 追溯数据
|
||||
const traces = ref([]);
|
||||
const traceSearchForm = reactive({
|
||||
traceId: '',
|
||||
earTag: '',
|
||||
traceType: ''
|
||||
});
|
||||
const tracePagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true
|
||||
});
|
||||
|
||||
// 政策数据
|
||||
const policies = ref([]);
|
||||
const policySearchForm = reactive({
|
||||
title: '',
|
||||
policyType: ''
|
||||
});
|
||||
const policyPagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true
|
||||
});
|
||||
|
||||
// 检查表格列
|
||||
const inspectionColumns = [
|
||||
{ title: '检查编号', dataIndex: 'inspection_id', key: 'inspection_id', width: 120 },
|
||||
{ title: '牧场名称', dataIndex: 'farm_name', key: 'farm_name' },
|
||||
{ title: '检查类型', dataIndex: 'inspection_type', key: 'inspection_type' },
|
||||
{ title: '检查人员', dataIndex: 'inspector_name', key: 'inspector_name' },
|
||||
{ title: '检查结果', dataIndex: 'result', key: 'result' },
|
||||
{ title: '检查时间', dataIndex: 'inspection_date', key: 'inspection_date' },
|
||||
{ title: '操作', key: 'action', width: 150 }
|
||||
];
|
||||
|
||||
// 追溯表格列
|
||||
const traceColumns = [
|
||||
{ title: '追溯编号', dataIndex: 'trace_id', key: 'trace_id', width: 150 },
|
||||
{ title: '牛只耳标', dataIndex: 'ear_tag', key: 'ear_tag' },
|
||||
{ title: '追溯类型', dataIndex: 'trace_type', key: 'trace_type' },
|
||||
{ title: '纳入时间', dataIndex: 'record_date', key: 'record_date' },
|
||||
{ title: '操作员', dataIndex: 'operator_name', key: 'operator_name' },
|
||||
{ title: '操作', key: 'action', width: 150 }
|
||||
];
|
||||
|
||||
// 政策表格列
|
||||
const policyColumns = [
|
||||
{ title: '政策标题', dataIndex: 'title', key: 'title' },
|
||||
{ title: '政策类型', dataIndex: 'policy_type', key: 'policy_type' },
|
||||
{ title: '发布时间', dataIndex: 'publish_date', key: 'publish_date' },
|
||||
{ title: '生效时间', dataIndex: 'effective_date', key: 'effective_date' },
|
||||
{ title: '操作', key: 'action', width: 150 }
|
||||
];
|
||||
|
||||
// 加载检查数据
|
||||
const loadInspections = async () => {
|
||||
inspectionLoading.value = true;
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = false;
|
||||
const [policyResponse, dataResponse] = await Promise.all([
|
||||
axios.get('/api/policies'),
|
||||
axios.get('/api/government-data')
|
||||
]);
|
||||
policies.value = policyResponse.data;
|
||||
governmentData.value = dataResponse.data;
|
||||
} catch (err) {
|
||||
error.value = true;
|
||||
console.error('获取数据失败:', err);
|
||||
const params = {
|
||||
page: inspectionPagination.current,
|
||||
limit: inspectionPagination.pageSize,
|
||||
...inspectionSearchForm
|
||||
};
|
||||
const response = await governmentAPI.getInspections(params);
|
||||
if (response.success) {
|
||||
inspections.value = response.data.inspections || [];
|
||||
inspectionPagination.total = response.data.pagination?.total || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取检查列表失败:', error);
|
||||
message.error('获取检查列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
inspectionLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载追溯数据
|
||||
const loadTraces = async () => {
|
||||
traceLoading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
page: tracePagination.current,
|
||||
limit: tracePagination.pageSize,
|
||||
...traceSearchForm
|
||||
};
|
||||
const response = await governmentAPI.getTraces(params);
|
||||
if (response.success) {
|
||||
traces.value = response.data.traces || [];
|
||||
tracePagination.total = response.data.pagination?.total || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取追溯列表失败:', error);
|
||||
message.error('获取追溯列表失败');
|
||||
} finally {
|
||||
traceLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载政策数据
|
||||
const loadPolicies = async () => {
|
||||
policyLoading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
page: policyPagination.current,
|
||||
limit: policyPagination.pageSize,
|
||||
...policySearchForm
|
||||
};
|
||||
const response = await governmentAPI.getPolicies(params);
|
||||
if (response.success) {
|
||||
policies.value = response.data.policies || [];
|
||||
policyPagination.total = response.data.pagination?.total || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取政策列表失败:', error);
|
||||
message.error('获取政策列表失败');
|
||||
} finally {
|
||||
policyLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 检查类型文本
|
||||
const getInspectionTypeText = (type) => {
|
||||
const texts = {
|
||||
'safety': '安全检查',
|
||||
'health': '卫生检查',
|
||||
'environment': '环保检查',
|
||||
'quality': '质量检查'
|
||||
};
|
||||
return texts[type] || type;
|
||||
};
|
||||
|
||||
// 检查结果颜色
|
||||
const getInspectionResultColor = (result) => {
|
||||
const colors = {
|
||||
'passed': 'green',
|
||||
'failed': 'red',
|
||||
'conditional': 'orange'
|
||||
};
|
||||
return colors[result] || 'default';
|
||||
};
|
||||
|
||||
// 检查结果文本
|
||||
const getInspectionResultText = (result) => {
|
||||
const texts = {
|
||||
'passed': '通过',
|
||||
'failed': '不通过',
|
||||
'conditional': '有条件通过'
|
||||
};
|
||||
return texts[result] || result;
|
||||
};
|
||||
|
||||
// 追溯类型文本
|
||||
const getTraceTypeText = (type) => {
|
||||
const texts = {
|
||||
'origin': '源头追溯',
|
||||
'feed': '饵料追溯',
|
||||
'medicine': '药物追溯',
|
||||
'transport': '运输追溯'
|
||||
};
|
||||
return texts[type] || type;
|
||||
};
|
||||
|
||||
// 政策类型文本
|
||||
const getPolicyTypeText = (type) => {
|
||||
const texts = {
|
||||
'industry': '行业政策',
|
||||
'subsidy': '补贴政策',
|
||||
'regulation': '监管政策',
|
||||
'environment': '环保政策'
|
||||
};
|
||||
return texts[type] || type;
|
||||
};
|
||||
|
||||
// 搜索检查
|
||||
const searchInspections = () => {
|
||||
inspectionPagination.current = 1;
|
||||
loadInspections();
|
||||
};
|
||||
|
||||
// 重置检查搜索
|
||||
const resetInspectionSearch = () => {
|
||||
Object.assign(inspectionSearchForm, {
|
||||
farmName: '',
|
||||
inspectionType: '',
|
||||
result: ''
|
||||
});
|
||||
searchInspections();
|
||||
};
|
||||
|
||||
// 搜索追溯
|
||||
const searchTraces = () => {
|
||||
tracePagination.current = 1;
|
||||
loadTraces();
|
||||
};
|
||||
|
||||
// 重置追溯搜索
|
||||
const resetTraceSearch = () => {
|
||||
Object.assign(traceSearchForm, {
|
||||
traceId: '',
|
||||
earTag: '',
|
||||
traceType: ''
|
||||
});
|
||||
searchTraces();
|
||||
};
|
||||
|
||||
// 搜索政策
|
||||
const searchPolicies = () => {
|
||||
policyPagination.current = 1;
|
||||
loadPolicies();
|
||||
};
|
||||
|
||||
// 重置政策搜索
|
||||
const resetPolicySearch = () => {
|
||||
Object.assign(policySearchForm, {
|
||||
title: '',
|
||||
policyType: ''
|
||||
});
|
||||
searchPolicies();
|
||||
};
|
||||
|
||||
// 表格变化处理
|
||||
const handleInspectionTableChange = (pagination) => {
|
||||
inspectionPagination.current = pagination.current;
|
||||
inspectionPagination.pageSize = pagination.pageSize;
|
||||
loadInspections();
|
||||
};
|
||||
|
||||
const handleTraceTableChange = (pagination) => {
|
||||
tracePagination.current = pagination.current;
|
||||
tracePagination.pageSize = pagination.pageSize;
|
||||
loadTraces();
|
||||
};
|
||||
|
||||
const handlePolicyTableChange = (pagination) => {
|
||||
policyPagination.current = pagination.current;
|
||||
policyPagination.pageSize = pagination.pageSize;
|
||||
loadPolicies();
|
||||
};
|
||||
|
||||
// 显示添加模态框
|
||||
const showAddModal = (type) => {
|
||||
message.info(`添加${type === 'inspection' ? '检查' : '追溯'}功能开发中`);
|
||||
};
|
||||
|
||||
// 查看详情
|
||||
const viewInspectionDetail = (record) => {
|
||||
message.info(`查看检查详情: ${record.inspection_id}`);
|
||||
};
|
||||
|
||||
const viewTraceDetail = (record) => {
|
||||
message.info(`查看追溯详情: ${record.trace_id}`);
|
||||
};
|
||||
|
||||
const viewPolicyDetail = (record) => {
|
||||
message.info(`查看政策详情: ${record.title}`);
|
||||
};
|
||||
|
||||
// 其他操作
|
||||
const createRectification = (record) => {
|
||||
message.info(`创建整改通知: ${record.inspection_id}`);
|
||||
};
|
||||
|
||||
const viewTraceChain = (record) => {
|
||||
message.info(`查看追溯链: ${record.trace_id}`);
|
||||
};
|
||||
|
||||
const downloadPolicy = (record) => {
|
||||
message.info(`下载政策文件: ${record.title}`);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchData();
|
||||
loadInspections();
|
||||
loadTraces();
|
||||
loadPolicies();
|
||||
});
|
||||
|
||||
return {
|
||||
activeTab,
|
||||
stats,
|
||||
inspections,
|
||||
inspectionLoading,
|
||||
inspectionSearchForm,
|
||||
inspectionPagination,
|
||||
inspectionColumns,
|
||||
traces,
|
||||
traceLoading,
|
||||
traceSearchForm,
|
||||
tracePagination,
|
||||
traceColumns,
|
||||
policies,
|
||||
governmentData,
|
||||
columns,
|
||||
loading,
|
||||
error
|
||||
policyLoading,
|
||||
policySearchForm,
|
||||
policyPagination,
|
||||
policyColumns,
|
||||
loadInspections,
|
||||
loadTraces,
|
||||
loadPolicies,
|
||||
getInspectionTypeText,
|
||||
getInspectionResultColor,
|
||||
getInspectionResultText,
|
||||
getTraceTypeText,
|
||||
getPolicyTypeText,
|
||||
searchInspections,
|
||||
resetInspectionSearch,
|
||||
searchTraces,
|
||||
resetTraceSearch,
|
||||
searchPolicies,
|
||||
resetPolicySearch,
|
||||
handleInspectionTableChange,
|
||||
handleTraceTableChange,
|
||||
handlePolicyTableChange,
|
||||
showAddModal,
|
||||
viewInspectionDetail,
|
||||
viewTraceDetail,
|
||||
viewPolicyDetail,
|
||||
createRectification,
|
||||
viewTraceChain,
|
||||
downloadPolicy
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.government-container {
|
||||
padding: 20px;
|
||||
.government-page {
|
||||
padding: 24px;
|
||||
background: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.policy-section,
|
||||
.data-section {
|
||||
margin-bottom: 20px;
|
||||
.page-header {
|
||||
background: white;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.loading-indicator,
|
||||
.error-message {
|
||||
.stats-cards {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stats-cards .ant-card {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff4d4f;
|
||||
.government-tabs {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.policy-section ul,
|
||||
.data-section {
|
||||
padding: 10px;
|
||||
}
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.data-section .ant-table {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.search-card .ant-form {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
284
admin-system/dashboard/src/views/Login.vue
Normal file
284
admin-system/dashboard/src/views/Login.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<div class="login-box">
|
||||
<div class="login-header">
|
||||
<h1>锡林郭勒盟智慧养殖平台</h1>
|
||||
<p>数字化管理系统</p>
|
||||
</div>
|
||||
|
||||
<a-form
|
||||
:model="loginForm"
|
||||
:rules="rules"
|
||||
@finish="handleLogin"
|
||||
class="login-form"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form-item name="username" label="用户名">
|
||||
<a-input
|
||||
v-model:value="loginForm.username"
|
||||
placeholder="请输入用户名"
|
||||
size="large"
|
||||
:prefix="renderIcon('user')"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="password" label="密码">
|
||||
<a-input-password
|
||||
v-model:value="loginForm.password"
|
||||
placeholder="请输入密码"
|
||||
size="large"
|
||||
:prefix="renderIcon('lock')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-checkbox v-model:checked="loginForm.remember">
|
||||
记住登录状态
|
||||
</a-checkbox>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
size="large"
|
||||
:loading="loading"
|
||||
block
|
||||
>
|
||||
登录
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<div class="demo-accounts">
|
||||
<h4>演示账户</h4>
|
||||
<div class="account-list">
|
||||
<div
|
||||
v-for="account in demoAccounts"
|
||||
:key="account.username"
|
||||
@click="setDemoAccount(account)"
|
||||
class="account-item"
|
||||
>
|
||||
<span class="username">{{ account.username }}</span>
|
||||
<span class="role">{{ account.role }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="login-footer">
|
||||
<p>© 2024 锡林郭勒盟智慧养殖平台. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, h, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue';
|
||||
import { useAuthStore } from '../store/auth.js';
|
||||
|
||||
const router = useRouter();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
// 表单数据
|
||||
const loginForm = ref({
|
||||
username: '',
|
||||
password: '',
|
||||
remember: false,
|
||||
});
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{ min: 3, message: '用户名至少3个字符', trigger: 'blur' },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, message: '密码至少6个字符', trigger: 'blur' },
|
||||
],
|
||||
};
|
||||
|
||||
// 演示账户
|
||||
const demoAccounts = [
|
||||
{ username: 'admin', password: '123456', role: '系统管理员' },
|
||||
{ username: 'farmer001', password: '123456', role: '养殖户' },
|
||||
{ username: 'banker001', password: '123456', role: '银行职员' },
|
||||
{ username: 'insurer001', password: '123456', role: '保险员' },
|
||||
{ username: 'inspector001', password: '123456', role: '政府检查员' },
|
||||
{ username: 'trader001', password: '123456', role: '交易员' },
|
||||
];
|
||||
|
||||
// 渲染图标
|
||||
const renderIcon = (type) => {
|
||||
const icons = {
|
||||
user: UserOutlined,
|
||||
lock: LockOutlined,
|
||||
};
|
||||
return h(icons[type]);
|
||||
};
|
||||
|
||||
// 设置演示账户
|
||||
const setDemoAccount = (account) => {
|
||||
loginForm.value.username = account.username;
|
||||
loginForm.value.password = account.password;
|
||||
message.info(`已填入${account.role}演示账户信息`);
|
||||
};
|
||||
|
||||
// 处理登录
|
||||
const handleLogin = async () => {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const result = await authStore.login(loginForm.value);
|
||||
|
||||
if (result.success) {
|
||||
message.success('登录成功!');
|
||||
|
||||
// 跳转到首页
|
||||
router.push('/');
|
||||
} else {
|
||||
message.error(result.message || '登录失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录错误:', error);
|
||||
message.error('登录失败,请稍后重试');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 组件挂载时检查是否已登录
|
||||
onMounted(() => {
|
||||
if (authStore.isAuthenticated) {
|
||||
router.push('/');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.login-box {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: 40px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.login-header h1 {
|
||||
color: #2c3e50;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.login-header p {
|
||||
color: #7f8c8d;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.demo-accounts {
|
||||
border-top: 1px solid #eee;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.demo-accounts h4 {
|
||||
color: #34495e;
|
||||
font-size: 14px;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.account-list {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.account-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.account-item:hover {
|
||||
background: #e3f2fd;
|
||||
border-color: #2196f3;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.account-item .username {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.account-item .role {
|
||||
font-size: 10px;
|
||||
color: #7f8c8d;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-footer p {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 480px) {
|
||||
.login-box {
|
||||
padding: 30px 20px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.login-header h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.account-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
692
admin-system/dashboard/src/views/MallManagement.vue
Normal file
692
admin-system/dashboard/src/views/MallManagement.vue
Normal file
@@ -0,0 +1,692 @@
|
||||
<template>
|
||||
<div class="mall-page">
|
||||
<!-- 页面标题和操作按钮 -->
|
||||
<div class="page-header">
|
||||
<a-page-header title="商城管理" sub-title="商品和订单管理">
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="showAddModal('product')">
|
||||
<ShopOutlined /> 新增商品
|
||||
</a-button>
|
||||
<a-button @click="showAddModal('order')">
|
||||
<ShoppingCartOutlined /> 新增订单
|
||||
</a-button>
|
||||
</template>
|
||||
</a-page-header>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<a-row :gutter="16" class="stats-cards">
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic title="商品总数" :value="stats.totalProducts" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic title="订单总数" :value="stats.totalOrders" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="销售额"
|
||||
:value="stats.totalSales"
|
||||
suffix="万元"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic title="今日订单" :value="stats.todayOrders" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 标签页 -->
|
||||
<a-tabs v-model:activeKey="activeTab" class="mall-tabs">
|
||||
<a-tab-pane key="products" tab="商品管理">
|
||||
<!-- 商品搜索表单 -->
|
||||
<a-card class="search-card">
|
||||
<a-form layout="inline" :model="productSearchForm">
|
||||
<a-form-item label="商品名称">
|
||||
<a-input v-model:value="productSearchForm.name" placeholder="请输入商品名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="商品分类">
|
||||
<a-select v-model:value="productSearchForm.category" placeholder="请选择分类" style="width: 150px">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="beef">牛肉制品</a-select-option>
|
||||
<a-select-option value="dairy">乳制品</a-select-option>
|
||||
<a-select-option value="snacks">特产零食</a-select-option>
|
||||
<a-select-option value="equipment">养殖设备</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="商品状态">
|
||||
<a-select v-model:value="productSearchForm.status" placeholder="请选择状态" style="width: 120px">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="active">上架</a-select-option>
|
||||
<a-select-option value="inactive">下架</a-select-option>
|
||||
<a-select-option value="draft">草稿</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="searchProducts">
|
||||
<SearchOutlined /> 搜索
|
||||
</a-button>
|
||||
<a-button @click="resetProductSearch" style="margin-left: 8px">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- 商品列表 -->
|
||||
<a-card>
|
||||
<a-table
|
||||
:columns="productColumns"
|
||||
:data-source="products"
|
||||
:loading="productLoading"
|
||||
:pagination="productPagination"
|
||||
@change="handleProductTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image
|
||||
:width="50"
|
||||
:height="50"
|
||||
:src="record.image_url || '/placeholder.jpg'"
|
||||
:preview="true"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'category'">
|
||||
<a-tag color="blue">
|
||||
{{ getProductCategoryText(record.category) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getProductStatusColor(record.status)">
|
||||
{{ getProductStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'featured'">
|
||||
<a-tag :color="record.featured ? 'gold' : 'default'">
|
||||
{{ record.featured ? '推荐' : '普通' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="viewProductDetail(record)">
|
||||
详情
|
||||
</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="editProduct(record)"
|
||||
>
|
||||
编辑
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="orders" tab="订单管理">
|
||||
<!-- 订单搜索表单 -->
|
||||
<a-card class="search-card">
|
||||
<a-form layout="inline" :model="orderSearchForm">
|
||||
<a-form-item label="订单编号">
|
||||
<a-input v-model:value="orderSearchForm.orderNumber" placeholder="请输入订单编号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="买家姓名">
|
||||
<a-input v-model:value="orderSearchForm.buyerName" placeholder="请输入买家姓名" />
|
||||
</a-form-item>
|
||||
<a-form-item label="订单状态">
|
||||
<a-select v-model:value="orderSearchForm.status" placeholder="请选择状态" style="width: 150px">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="pending">待付款</a-select-option>
|
||||
<a-select-option value="paid">已付款</a-select-option>
|
||||
<a-select-option value="shipped">已发货</a-select-option>
|
||||
<a-select-option value="delivered">已送达</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
<a-select-option value="cancelled">已取消</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="searchOrders">
|
||||
<SearchOutlined /> 搜索
|
||||
</a-button>
|
||||
<a-button @click="resetOrderSearch" style="margin-left: 8px">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- 订单列表 -->
|
||||
<a-card>
|
||||
<a-table
|
||||
:columns="orderColumns"
|
||||
:data-source="orders"
|
||||
:loading="orderLoading"
|
||||
:pagination="orderPagination"
|
||||
@change="handleOrderTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getOrderStatusColor(record.status)">
|
||||
{{ getOrderStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="viewOrderDetail(record)">
|
||||
详情
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="record.status === 'pending'"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="confirmOrder(record)"
|
||||
>
|
||||
确认订单
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="record.status === 'paid'"
|
||||
size="small"
|
||||
@click="shipOrder(record)"
|
||||
>
|
||||
发货
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="reviews" tab="评价管理">
|
||||
<!-- 评价搜索表单 -->
|
||||
<a-card class="search-card">
|
||||
<a-form layout="inline" :model="reviewSearchForm">
|
||||
<a-form-item label="商品名称">
|
||||
<a-input v-model:value="reviewSearchForm.productName" placeholder="请输入商品名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="评分">
|
||||
<a-select v-model:value="reviewSearchForm.rating" placeholder="请选择评分" style="width: 120px">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="5">5星</a-select-option>
|
||||
<a-select-option value="4">4星</a-select-option>
|
||||
<a-select-option value="3">3星</a-select-option>
|
||||
<a-select-option value="2">2星</a-select-option>
|
||||
<a-select-option value="1">1星</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="searchReviews">
|
||||
<SearchOutlined /> 搜索
|
||||
</a-button>
|
||||
<a-button @click="resetReviewSearch" style="margin-left: 8px">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- 评价列表 -->
|
||||
<a-card>
|
||||
<a-table
|
||||
:columns="reviewColumns"
|
||||
:data-source="reviews"
|
||||
:loading="reviewLoading"
|
||||
:pagination="reviewPagination"
|
||||
@change="handleReviewTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'rating'">
|
||||
<a-rate :value="record.rating" disabled />
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="viewReviewDetail(record)">
|
||||
详情
|
||||
</a-button>
|
||||
<a-button size="small" @click="replyReview(record)">
|
||||
回复
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { mallAPI } from '@/services/api.js';
|
||||
import {
|
||||
ShopOutlined,
|
||||
ShoppingCartOutlined,
|
||||
SearchOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
name: 'MallManagement',
|
||||
components: {
|
||||
ShopOutlined,
|
||||
ShoppingCartOutlined,
|
||||
SearchOutlined
|
||||
},
|
||||
setup() {
|
||||
const activeTab = ref('products');
|
||||
const productLoading = ref(false);
|
||||
const orderLoading = ref(false);
|
||||
const reviewLoading = ref(false);
|
||||
|
||||
// 统计数据
|
||||
const stats = reactive({
|
||||
totalProducts: 456,
|
||||
totalOrders: 1289,
|
||||
totalSales: 3650,
|
||||
todayOrders: 28
|
||||
});
|
||||
|
||||
// 商品数据
|
||||
const products = ref([]);
|
||||
const productSearchForm = reactive({
|
||||
name: '',
|
||||
category: '',
|
||||
status: ''
|
||||
});
|
||||
const productPagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true
|
||||
});
|
||||
|
||||
// 订单数据
|
||||
const orders = ref([]);
|
||||
const orderSearchForm = reactive({
|
||||
orderNumber: '',
|
||||
buyerName: '',
|
||||
status: ''
|
||||
});
|
||||
const orderPagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true
|
||||
});
|
||||
|
||||
// 评价数据
|
||||
const reviews = ref([]);
|
||||
const reviewSearchForm = reactive({
|
||||
productName: '',
|
||||
rating: ''
|
||||
});
|
||||
const reviewPagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true
|
||||
});
|
||||
|
||||
// 商品表格列
|
||||
const productColumns = [
|
||||
{ title: '商品图片', key: 'image', width: 80 },
|
||||
{ title: '商品名称', dataIndex: 'name', key: 'name' },
|
||||
{ title: '分类', dataIndex: 'category', key: 'category' },
|
||||
{ title: '价格(元)', dataIndex: 'price', key: 'price' },
|
||||
{ title: '库存', dataIndex: 'stock', key: 'stock' },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status' },
|
||||
{ title: '推荐', dataIndex: 'featured', key: 'featured' },
|
||||
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at' },
|
||||
{ title: '操作', key: 'action', width: 150 }
|
||||
];
|
||||
|
||||
// 订单表格列
|
||||
const orderColumns = [
|
||||
{ title: '订单编号', dataIndex: 'order_number', key: 'order_number', width: 150 },
|
||||
{ title: '买家', dataIndex: 'buyer_name', key: 'buyer_name' },
|
||||
{ title: '商品名称', dataIndex: 'product_name', key: 'product_name' },
|
||||
{ title: '数量', dataIndex: 'quantity', key: 'quantity' },
|
||||
{ title: '总金额(元)', dataIndex: 'total_amount', key: 'total_amount' },
|
||||
{ title: '订单状态', dataIndex: 'status', key: 'status' },
|
||||
{ title: '下单时间', dataIndex: 'created_at', key: 'created_at' },
|
||||
{ title: '操作', key: 'action', width: 180 }
|
||||
];
|
||||
|
||||
// 评价表格列
|
||||
const reviewColumns = [
|
||||
{ title: '商品名称', dataIndex: 'product_name', key: 'product_name' },
|
||||
{ title: '评价人', dataIndex: 'reviewer_name', key: 'reviewer_name' },
|
||||
{ title: '评分', dataIndex: 'rating', key: 'rating' },
|
||||
{ title: '评价内容', dataIndex: 'content', key: 'content' },
|
||||
{ title: '评价时间', dataIndex: 'created_at', key: 'created_at' },
|
||||
{ title: '操作', key: 'action', width: 150 }
|
||||
];
|
||||
|
||||
// 加载商品数据
|
||||
const loadProducts = async () => {
|
||||
productLoading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
page: productPagination.current,
|
||||
limit: productPagination.pageSize,
|
||||
...productSearchForm
|
||||
};
|
||||
const response = await mallAPI.getProducts(params);
|
||||
if (response.success) {
|
||||
products.value = response.data.products || [];
|
||||
productPagination.total = response.data.pagination?.total || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取商品列表失败:', error);
|
||||
message.error('获取商品列表失败');
|
||||
} finally {
|
||||
productLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载订单数据
|
||||
const loadOrders = async () => {
|
||||
orderLoading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
page: orderPagination.current,
|
||||
limit: orderPagination.pageSize,
|
||||
...orderSearchForm
|
||||
};
|
||||
const response = await mallAPI.getOrders(params);
|
||||
if (response.success) {
|
||||
orders.value = response.data.orders || [];
|
||||
orderPagination.total = response.data.pagination?.total || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取订单列表失败:', error);
|
||||
message.error('获取订单列表失败');
|
||||
} finally {
|
||||
orderLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载评价数据
|
||||
const loadReviews = async () => {
|
||||
reviewLoading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
page: reviewPagination.current,
|
||||
limit: reviewPagination.pageSize,
|
||||
...reviewSearchForm
|
||||
};
|
||||
const response = await mallAPI.getReviews(params);
|
||||
if (response.success) {
|
||||
reviews.value = response.data.reviews || [];
|
||||
reviewPagination.total = response.data.pagination?.total || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取评价列表失败:', error);
|
||||
message.error('获取评价列表失败');
|
||||
} finally {
|
||||
reviewLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 商品分类文本
|
||||
const getProductCategoryText = (category) => {
|
||||
const texts = {
|
||||
'beef': '牛肉制品',
|
||||
'dairy': '乳制品',
|
||||
'snacks': '特产零食',
|
||||
'equipment': '养殖设备'
|
||||
};
|
||||
return texts[category] || category;
|
||||
};
|
||||
|
||||
// 商品状态颜色
|
||||
const getProductStatusColor = (status) => {
|
||||
const colors = {
|
||||
'active': 'green',
|
||||
'inactive': 'red',
|
||||
'draft': 'orange'
|
||||
};
|
||||
return colors[status] || 'default';
|
||||
};
|
||||
|
||||
// 商品状态文本
|
||||
const getProductStatusText = (status) => {
|
||||
const texts = {
|
||||
'active': '上架',
|
||||
'inactive': '下架',
|
||||
'draft': '草稿'
|
||||
};
|
||||
return texts[status] || status;
|
||||
};
|
||||
|
||||
// 订单状态颜色
|
||||
const getOrderStatusColor = (status) => {
|
||||
const colors = {
|
||||
'pending': 'orange',
|
||||
'paid': 'blue',
|
||||
'shipped': 'purple',
|
||||
'delivered': 'cyan',
|
||||
'completed': 'green',
|
||||
'cancelled': 'red'
|
||||
};
|
||||
return colors[status] || 'default';
|
||||
};
|
||||
|
||||
// 订单状态文本
|
||||
const getOrderStatusText = (status) => {
|
||||
const texts = {
|
||||
'pending': '待付款',
|
||||
'paid': '已付款',
|
||||
'shipped': '已发货',
|
||||
'delivered': '已送达',
|
||||
'completed': '已完成',
|
||||
'cancelled': '已取消'
|
||||
};
|
||||
return texts[status] || status;
|
||||
};
|
||||
|
||||
// 搜索商品
|
||||
const searchProducts = () => {
|
||||
productPagination.current = 1;
|
||||
loadProducts();
|
||||
};
|
||||
|
||||
// 重置商品搜索
|
||||
const resetProductSearch = () => {
|
||||
Object.assign(productSearchForm, {
|
||||
name: '',
|
||||
category: '',
|
||||
status: ''
|
||||
});
|
||||
searchProducts();
|
||||
};
|
||||
|
||||
// 搜索订单
|
||||
const searchOrders = () => {
|
||||
orderPagination.current = 1;
|
||||
loadOrders();
|
||||
};
|
||||
|
||||
// 重置订单搜索
|
||||
const resetOrderSearch = () => {
|
||||
Object.assign(orderSearchForm, {
|
||||
orderNumber: '',
|
||||
buyerName: '',
|
||||
status: ''
|
||||
});
|
||||
searchOrders();
|
||||
};
|
||||
|
||||
// 搜索评价
|
||||
const searchReviews = () => {
|
||||
reviewPagination.current = 1;
|
||||
loadReviews();
|
||||
};
|
||||
|
||||
// 重置评价搜索
|
||||
const resetReviewSearch = () => {
|
||||
Object.assign(reviewSearchForm, {
|
||||
productName: '',
|
||||
rating: ''
|
||||
});
|
||||
searchReviews();
|
||||
};
|
||||
|
||||
// 表格变化处理
|
||||
const handleProductTableChange = (pagination) => {
|
||||
productPagination.current = pagination.current;
|
||||
productPagination.pageSize = pagination.pageSize;
|
||||
loadProducts();
|
||||
};
|
||||
|
||||
const handleOrderTableChange = (pagination) => {
|
||||
orderPagination.current = pagination.current;
|
||||
orderPagination.pageSize = pagination.pageSize;
|
||||
loadOrders();
|
||||
};
|
||||
|
||||
const handleReviewTableChange = (pagination) => {
|
||||
reviewPagination.current = pagination.current;
|
||||
reviewPagination.pageSize = pagination.pageSize;
|
||||
loadReviews();
|
||||
};
|
||||
|
||||
// 显示添加模态框
|
||||
const showAddModal = (type) => {
|
||||
message.info(`添加${type === 'product' ? '商品' : '订单'}功能开发中`);
|
||||
};
|
||||
|
||||
// 查看详情
|
||||
const viewProductDetail = (record) => {
|
||||
message.info(`查看商品详情: ${record.name}`);
|
||||
};
|
||||
|
||||
const viewOrderDetail = (record) => {
|
||||
message.info(`查看订单详情: ${record.order_number}`);
|
||||
};
|
||||
|
||||
const viewReviewDetail = (record) => {
|
||||
message.info(`查看评价详情: ${record.product_name}`);
|
||||
};
|
||||
|
||||
// 其他操作
|
||||
const editProduct = (record) => {
|
||||
message.info(`编辑商品: ${record.name}`);
|
||||
};
|
||||
|
||||
const confirmOrder = (record) => {
|
||||
message.info(`确认订单: ${record.order_number}`);
|
||||
};
|
||||
|
||||
const shipOrder = (record) => {
|
||||
message.info(`发货订单: ${record.order_number}`);
|
||||
};
|
||||
|
||||
const replyReview = (record) => {
|
||||
message.info(`回复评价: ${record.product_name}`);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadProducts();
|
||||
loadOrders();
|
||||
loadReviews();
|
||||
});
|
||||
|
||||
return {
|
||||
activeTab,
|
||||
stats,
|
||||
products,
|
||||
productLoading,
|
||||
productSearchForm,
|
||||
productPagination,
|
||||
productColumns,
|
||||
orders,
|
||||
orderLoading,
|
||||
orderSearchForm,
|
||||
orderPagination,
|
||||
orderColumns,
|
||||
reviews,
|
||||
reviewLoading,
|
||||
reviewSearchForm,
|
||||
reviewPagination,
|
||||
reviewColumns,
|
||||
loadProducts,
|
||||
loadOrders,
|
||||
loadReviews,
|
||||
getProductCategoryText,
|
||||
getProductStatusColor,
|
||||
getProductStatusText,
|
||||
getOrderStatusColor,
|
||||
getOrderStatusText,
|
||||
searchProducts,
|
||||
resetProductSearch,
|
||||
searchOrders,
|
||||
resetOrderSearch,
|
||||
searchReviews,
|
||||
resetReviewSearch,
|
||||
handleProductTableChange,
|
||||
handleOrderTableChange,
|
||||
handleReviewTableChange,
|
||||
showAddModal,
|
||||
viewProductDetail,
|
||||
viewOrderDetail,
|
||||
viewReviewDetail,
|
||||
editProduct,
|
||||
confirmOrder,
|
||||
shipOrder,
|
||||
replyReview
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mall-page {
|
||||
padding: 24px;
|
||||
background: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: white;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.stats-cards {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stats-cards .ant-card {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mall-tabs {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-card .ant-form {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,478 +1,557 @@
|
||||
<template>
|
||||
<div class="trade-container">
|
||||
<h1>交易统计</h1>
|
||||
<div v-if="loading" class="loading-indicator">
|
||||
<div class="loading-spinner"></div>
|
||||
数据加载中...
|
||||
</div>
|
||||
<div v-if="error" class="error-message">数据加载失败,请稍后重试。</div>
|
||||
<div v-if="!loading && !error" class="trade-content">
|
||||
<!-- 牛只交易量统计 -->
|
||||
<div class="volume-section card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">牛只交易量统计</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="volume-content">
|
||||
<div class="volume-cards">
|
||||
<div class="volume-card card" v-for="(item, index) in volumeData" :key="index">
|
||||
<div class="card-body">
|
||||
<div class="card-title">{{ item.title }}</div>
|
||||
<div class="card-value">{{ item.value }}</div>
|
||||
<div class="card-change" :class="item.change > 0 ? 'positive' : 'negative'">
|
||||
{{ item.change > 0 ? '↑' : '↓' }} {{ Math.abs(item.change) }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="volume-chart">
|
||||
<div ref="volumeChart" class="chart-placeholder"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 价格趋势和区域分布 -->
|
||||
<div class="price-section card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">价格趋势和区域分布</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="price-content">
|
||||
<div class="trend-chart card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">价格趋势</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div ref="trendChart" class="chart-placeholder"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="distribution-chart card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">区域价格分布</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div ref="distributionChart" class="chart-placeholder"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 交易类型分析 -->
|
||||
<div class="type-section card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">交易类型分析</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="type-content">
|
||||
<div class="type-chart">
|
||||
<div ref="typeChart" class="chart-placeholder"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 交易排行榜 -->
|
||||
<div class="ranking-section card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">交易排行榜</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="ranking-content">
|
||||
<div class="farm-ranking card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">热门牧场</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a-table :dataSource="farmRankingData" :columns="farmRankingColumns" :pagination="false" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="trader-ranking card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">活跃交易员</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a-table :dataSource="traderRankingData" :columns="traderRankingColumns" :pagination="false" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trade-page">
|
||||
<!-- 页面标题和操作按钮 -->
|
||||
<div class="page-header">
|
||||
<a-page-header title="交易管理" sub-title="交易记录和合同管理">
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="showAddModal('transaction')">
|
||||
<PlusOutlined /> 新增交易
|
||||
</a-button>
|
||||
<a-button @click="showAddModal('contract')">
|
||||
<FileTextOutlined /> 新增合同
|
||||
</a-button>
|
||||
</template>
|
||||
</a-page-header>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<a-row :gutter="16" class="stats-cards">
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic title="总交易量" :value="stats.totalTransactions" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic title="有效合同" :value="stats.totalContracts" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="交易总金额"
|
||||
:value="stats.totalAmount"
|
||||
suffix="万元"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<a-statistic
|
||||
title="今日交易"
|
||||
:value="stats.todayTransactions"
|
||||
/>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 标签页 -->
|
||||
<a-tabs v-model:activeKey="activeTab" class="trade-tabs">
|
||||
<a-tab-pane key="transactions" tab="交易记录">
|
||||
<!-- 交易搜索表单 -->
|
||||
<a-card class="search-card">
|
||||
<a-form layout="inline" :model="transactionSearchForm">
|
||||
<a-form-item label="交易编号">
|
||||
<a-input v-model:value="transactionSearchForm.transactionNumber" placeholder="请输入交易编号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="交易类型">
|
||||
<a-select v-model:value="transactionSearchForm.transactionType" placeholder="请选择交易类型" style="width: 150px">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="cattle_sale">牛只销售</a-select-option>
|
||||
<a-select-option value="feed_purchase">饵料采购</a-select-option>
|
||||
<a-select-option value="equipment_sale">设备销售</a-select-option>
|
||||
<a-select-option value="service">服务</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="交易状态">
|
||||
<a-select v-model:value="transactionSearchForm.status" placeholder="请选择状态" style="width: 150px">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="pending">待处理</a-select-option>
|
||||
<a-select-option value="confirmed">已确认</a-select-option>
|
||||
<a-select-option value="in_progress">进行中</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
<a-select-option value="cancelled">已取消</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="searchTransactions">
|
||||
<SearchOutlined /> 搜索
|
||||
</a-button>
|
||||
<a-button @click="resetTransactionSearch" style="margin-left: 8px">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- 交易列表 -->
|
||||
<a-card>
|
||||
<a-table
|
||||
:columns="transactionColumns"
|
||||
:data-source="transactions"
|
||||
:loading="transactionLoading"
|
||||
:pagination="transactionPagination"
|
||||
@change="handleTransactionTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'transaction_type'">
|
||||
<a-tag color="blue">
|
||||
{{ getTransactionTypeText(record.transaction_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getTransactionStatusColor(record.status)">
|
||||
{{ getTransactionStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="viewTransactionDetail(record)">
|
||||
详情
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="record.status === 'pending'"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="confirmTransaction(record)"
|
||||
>
|
||||
确认
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="contracts" tab="合同管理">
|
||||
<!-- 合同搜索表单 -->
|
||||
<a-card class="search-card">
|
||||
<a-form layout="inline" :model="contractSearchForm">
|
||||
<a-form-item label="合同编号">
|
||||
<a-input v-model:value="contractSearchForm.contractNumber" placeholder="请输入合同编号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="合同类型">
|
||||
<a-select v-model:value="contractSearchForm.contractType" placeholder="请选择合同类型" style="width: 150px">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="sale">销售合同</a-select-option>
|
||||
<a-select-option value="purchase">采购合同</a-select-option>
|
||||
<a-select-option value="service">服务合同</a-select-option>
|
||||
<a-select-option value="lease">租赁合同</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="合同状态">
|
||||
<a-select v-model:value="contractSearchForm.status" placeholder="请选择状态" style="width: 150px">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="draft">草稿</a-select-option>
|
||||
<a-select-option value="pending">待签署</a-select-option>
|
||||
<a-select-option value="signed">已签署</a-select-option>
|
||||
<a-select-option value="executing">执行中</a-select-option>
|
||||
<a-select-option value="completed">已完成</a-select-option>
|
||||
<a-select-option value="terminated">已终止</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="searchContracts">
|
||||
<SearchOutlined /> 搜索
|
||||
</a-button>
|
||||
<a-button @click="resetContractSearch" style="margin-left: 8px">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- 合同列表 -->
|
||||
<a-card>
|
||||
<a-table
|
||||
:columns="contractColumns"
|
||||
:data-source="contracts"
|
||||
:loading="contractLoading"
|
||||
:pagination="contractPagination"
|
||||
@change="handleContractTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'contract_type'">
|
||||
<a-tag color="green">
|
||||
{{ getContractTypeText(record.contract_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="getContractStatusColor(record.status)">
|
||||
{{ getContractStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button size="small" @click="viewContractDetail(record)">
|
||||
详情
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="record.status === 'pending'"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="signContract(record)"
|
||||
>
|
||||
签署
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { tradingAPI } from '@/services/api.js';
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
FileTextOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
name: 'Trade',
|
||||
components: {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
FileTextOutlined
|
||||
},
|
||||
setup() {
|
||||
const volumeData = ref([]);
|
||||
const farmRankingData = ref([]);
|
||||
const traderRankingData = ref([]);
|
||||
const loading = ref(true);
|
||||
const error = ref(false);
|
||||
const volumeChart = ref(null);
|
||||
const trendChart = ref(null);
|
||||
const distributionChart = ref(null);
|
||||
const typeChart = ref(null);
|
||||
const activeTab = ref('transactions');
|
||||
const transactionLoading = ref(false);
|
||||
const contractLoading = ref(false);
|
||||
|
||||
let volumeChartInstance = null;
|
||||
let trendChartInstance = null;
|
||||
let distributionChartInstance = null;
|
||||
let typeChartInstance = null;
|
||||
|
||||
// 交易量数据
|
||||
volumeData.value = [
|
||||
{ title: '今日交易量', value: '1,245头', change: 5.2 },
|
||||
{ title: '本月交易量', value: '38,650头', change: 8.7 },
|
||||
{ title: '年度交易量', value: '420,860头', change: 12.3 }
|
||||
];
|
||||
|
||||
// 热门牧场排行榜列定义
|
||||
const farmRankingColumns = ref([
|
||||
{ title: '排名', dataIndex: 'rank', key: 'rank' },
|
||||
{ title: '牧场名称', dataIndex: 'farm', key: 'farm' },
|
||||
{ title: '交易量', dataIndex: 'volume', key: 'volume' },
|
||||
{ title: '交易额', dataIndex: 'amount', key: 'amount' },
|
||||
]);
|
||||
|
||||
// 热门牧场排行榜数据
|
||||
farmRankingData.value = [
|
||||
{ key: '1', rank: '1', farm: '锡市牧场A', volume: '2,450头', amount: '¥1,245万' },
|
||||
{ key: '2', rank: '2', farm: '东乌旗牧场B', volume: '1,980头', amount: '¥980万' },
|
||||
{ key: '3', rank: '3', farm: '西乌旗牧场C', volume: '1,650头', amount: '¥820万' },
|
||||
{ key: '4', rank: '4', farm: '镶黄旗牧场D', volume: '1,320头', amount: '¥650万' },
|
||||
{ key: '5', rank: '5', farm: '正蓝旗牧场E', volume: '1,150头', amount: '¥580万' },
|
||||
];
|
||||
|
||||
// 活跃交易员排行榜列定义
|
||||
const traderRankingColumns = ref([
|
||||
{ title: '排名', dataIndex: 'rank', key: 'rank' },
|
||||
{ title: '交易员', dataIndex: 'trader', key: 'trader' },
|
||||
{ title: '交易数', dataIndex: 'count', key: 'count' },
|
||||
{ title: '交易额', dataIndex: 'amount', key: 'amount' },
|
||||
]);
|
||||
|
||||
// 活跃交易员排行榜数据
|
||||
traderRankingData.value = [
|
||||
{ key: '1', rank: '1', trader: '张三', count: '126笔', amount: '¥860万' },
|
||||
{ key: '2', rank: '2', trader: '李四', count: '98笔', amount: '¥620万' },
|
||||
{ key: '3', rank: '3', trader: '王五', count: '85笔', amount: '¥540万' },
|
||||
{ key: '4', rank: '4', trader: '赵六', count: '72笔', amount: '¥420万' },
|
||||
{ key: '5', rank: '5', trader: '孙七', count: '65笔', amount: '¥380万' },
|
||||
// 统计数据
|
||||
const stats = reactive({
|
||||
totalTransactions: 2456,
|
||||
totalContracts: 189,
|
||||
totalAmount: 8650,
|
||||
todayTransactions: 45
|
||||
});
|
||||
|
||||
// 交易数据
|
||||
const transactions = ref([]);
|
||||
const transactionSearchForm = reactive({
|
||||
transactionNumber: '',
|
||||
transactionType: '',
|
||||
status: ''
|
||||
});
|
||||
const transactionPagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true
|
||||
});
|
||||
|
||||
// 合同数据
|
||||
const contracts = ref([]);
|
||||
const contractSearchForm = reactive({
|
||||
contractNumber: '',
|
||||
contractType: '',
|
||||
status: ''
|
||||
});
|
||||
const contractPagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true
|
||||
});
|
||||
|
||||
// 交易表格列
|
||||
const transactionColumns = [
|
||||
{ title: '交易编号', dataIndex: 'transaction_number', key: 'transaction_number', width: 150 },
|
||||
{ title: '交易类型', dataIndex: 'transaction_type', key: 'transaction_type' },
|
||||
{ title: '买方', dataIndex: 'buyer_name', key: 'buyer_name' },
|
||||
{ title: '卖方', dataIndex: 'seller_name', key: 'seller_name' },
|
||||
{ title: '交易金额(万元)', dataIndex: 'total_amount', key: 'total_amount' },
|
||||
{ title: '交易状态', dataIndex: 'status', key: 'status' },
|
||||
{ title: '交易时间', dataIndex: 'created_at', key: 'created_at' },
|
||||
{ title: '操作', key: 'action', width: 150 }
|
||||
];
|
||||
|
||||
// 初始化交易量图表
|
||||
const initVolumeChart = () => {
|
||||
if (volumeChart.value) {
|
||||
volumeChartInstance = echarts.init(volumeChart.value);
|
||||
volumeChartInstance.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [32000, 35000, 38000, 40000, 42000, 45000],
|
||||
type: 'bar',
|
||||
itemStyle: { color: '#4CAF50' }
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化价格趋势图表
|
||||
const initTrendChart = () => {
|
||||
if (trendChart.value) {
|
||||
trendChartInstance = echarts.init(trendChart.value);
|
||||
trendChartInstance.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [28000, 29500, 31000, 30500, 32000, 33500],
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
itemStyle: { color: '#2196F3' }
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化区域分布图表
|
||||
const initDistributionChart = () => {
|
||||
if (distributionChart.value) {
|
||||
distributionChartInstance = echarts.init(distributionChart.value);
|
||||
distributionChartInstance.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
bottom: '0'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
data: [
|
||||
{ value: 35, name: '锡市' },
|
||||
{ value: 25, name: '东乌旗' },
|
||||
{ value: 20, name: '西乌旗' },
|
||||
{ value: 10, name: '镶黄旗' },
|
||||
{ value: 10, name: '其他' }
|
||||
],
|
||||
itemStyle: {
|
||||
color: function(params) {
|
||||
const colorList = ['#4CAF50', '#2196F3', '#FF9800', '#f44336', '#9C27B0'];
|
||||
return colorList[params.dataIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化交易类型图表
|
||||
const initTypeChart = () => {
|
||||
if (typeChart.value) {
|
||||
typeChartInstance = echarts.init(typeChart.value);
|
||||
typeChartInstance.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['活牛交易', '牛肉制品']
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '活牛交易',
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
data: [28000, 30000, 32000, 31000, 33000, 35000],
|
||||
itemStyle: { color: '#4CAF50' }
|
||||
},
|
||||
{
|
||||
name: '牛肉制品',
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
data: [4000, 5000, 6000, 5500, 7000, 8000],
|
||||
itemStyle: { color: '#2196F3' }
|
||||
}
|
||||
]
|
||||
});
|
||||
// 合同表格列
|
||||
const contractColumns = [
|
||||
{ title: '合同编号', dataIndex: 'contract_number', key: 'contract_number', width: 150 },
|
||||
{ title: '合同类型', dataIndex: 'contract_type', key: 'contract_type' },
|
||||
{ title: '甲方', dataIndex: 'party_a_name', key: 'party_a_name' },
|
||||
{ title: '乙方', dataIndex: 'party_b_name', key: 'party_b_name' },
|
||||
{ title: '合同金额(万元)', dataIndex: 'contract_amount', key: 'contract_amount' },
|
||||
{ title: '合同状态', dataIndex: 'status', key: 'status' },
|
||||
{ title: '签署时间', dataIndex: 'signed_date', key: 'signed_date' },
|
||||
{ title: '操作', key: 'action', width: 150 }
|
||||
];
|
||||
|
||||
// 加载交易数据
|
||||
const loadTransactions = async () => {
|
||||
transactionLoading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
page: transactionPagination.current,
|
||||
limit: transactionPagination.pageSize,
|
||||
...transactionSearchForm
|
||||
};
|
||||
const response = await tradingAPI.getTransactions(params);
|
||||
if (response.success) {
|
||||
transactions.value = response.data.transactions || [];
|
||||
transactionPagination.total = response.data.pagination?.total || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取交易列表失败:', error);
|
||||
message.error('获取交易列表失败');
|
||||
} finally {
|
||||
transactionLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 窗口大小改变时重绘图表
|
||||
const resizeCharts = () => {
|
||||
if (volumeChartInstance) volumeChartInstance.resize();
|
||||
if (trendChartInstance) trendChartInstance.resize();
|
||||
if (distributionChartInstance) distributionChartInstance.resize();
|
||||
if (typeChartInstance) typeChartInstance.resize();
|
||||
// 加载合同数据
|
||||
const loadContracts = async () => {
|
||||
contractLoading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
page: contractPagination.current,
|
||||
limit: contractPagination.pageSize,
|
||||
...contractSearchForm
|
||||
};
|
||||
const response = await tradingAPI.getContracts(params);
|
||||
if (response.success) {
|
||||
contracts.value = response.data.contracts || [];
|
||||
contractPagination.total = response.data.pagination?.total || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取合同列表失败:', error);
|
||||
message.error('获取合同列表失败');
|
||||
} finally {
|
||||
contractLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 交易类型文本
|
||||
const getTransactionTypeText = (type) => {
|
||||
const texts = {
|
||||
'cattle_sale': '牛只销售',
|
||||
'feed_purchase': '饲料采购',
|
||||
'equipment_sale': '设备销售',
|
||||
'service': '服务'
|
||||
};
|
||||
return texts[type] || type;
|
||||
};
|
||||
|
||||
// 交易状态颜色
|
||||
const getTransactionStatusColor = (status) => {
|
||||
const colors = {
|
||||
'pending': 'orange',
|
||||
'confirmed': 'blue',
|
||||
'in_progress': 'purple',
|
||||
'completed': 'green',
|
||||
'cancelled': 'red',
|
||||
'refunded': 'red'
|
||||
};
|
||||
return colors[status] || 'default';
|
||||
};
|
||||
|
||||
// 交易状态文本
|
||||
const getTransactionStatusText = (status) => {
|
||||
const texts = {
|
||||
'pending': '待处理',
|
||||
'confirmed': '已确认',
|
||||
'in_progress': '进行中',
|
||||
'completed': '已完成',
|
||||
'cancelled': '已取消',
|
||||
'refunded': '已退款'
|
||||
};
|
||||
return texts[status] || status;
|
||||
};
|
||||
|
||||
// 合同类型文本
|
||||
const getContractTypeText = (type) => {
|
||||
const texts = {
|
||||
'sale': '销售合同',
|
||||
'purchase': '采购合同',
|
||||
'service': '服务合同',
|
||||
'lease': '租赁合同'
|
||||
};
|
||||
return texts[type] || type;
|
||||
};
|
||||
|
||||
// 合同状态颜色
|
||||
const getContractStatusColor = (status) => {
|
||||
const colors = {
|
||||
'draft': 'default',
|
||||
'pending': 'orange',
|
||||
'signed': 'blue',
|
||||
'executing': 'purple',
|
||||
'completed': 'green',
|
||||
'terminated': 'red'
|
||||
};
|
||||
return colors[status] || 'default';
|
||||
};
|
||||
|
||||
// 合同状态文本
|
||||
const getContractStatusText = (status) => {
|
||||
const texts = {
|
||||
'draft': '草稿',
|
||||
'pending': '待签署',
|
||||
'signed': '已签署',
|
||||
'executing': '执行中',
|
||||
'completed': '已完成',
|
||||
'terminated': '已终止'
|
||||
};
|
||||
return texts[status] || status;
|
||||
};
|
||||
|
||||
// 搜索交易
|
||||
const searchTransactions = () => {
|
||||
transactionPagination.current = 1;
|
||||
loadTransactions();
|
||||
};
|
||||
|
||||
// 重置交易搜索
|
||||
const resetTransactionSearch = () => {
|
||||
Object.assign(transactionSearchForm, {
|
||||
transactionNumber: '',
|
||||
transactionType: '',
|
||||
status: ''
|
||||
});
|
||||
searchTransactions();
|
||||
};
|
||||
|
||||
// 搜索合同
|
||||
const searchContracts = () => {
|
||||
contractPagination.current = 1;
|
||||
loadContracts();
|
||||
};
|
||||
|
||||
// 重置合同搜索
|
||||
const resetContractSearch = () => {
|
||||
Object.assign(contractSearchForm, {
|
||||
contractNumber: '',
|
||||
contractType: '',
|
||||
status: ''
|
||||
});
|
||||
searchContracts();
|
||||
};
|
||||
|
||||
// 表格变化处理
|
||||
const handleTransactionTableChange = (pagination) => {
|
||||
transactionPagination.current = pagination.current;
|
||||
transactionPagination.pageSize = pagination.pageSize;
|
||||
loadTransactions();
|
||||
};
|
||||
|
||||
const handleContractTableChange = (pagination) => {
|
||||
contractPagination.current = pagination.current;
|
||||
contractPagination.pageSize = pagination.pageSize;
|
||||
loadContracts();
|
||||
};
|
||||
|
||||
// 显示添加模态框
|
||||
const showAddModal = (type) => {
|
||||
message.info(`添加${type === 'transaction' ? '交易' : '合同'}功能开发中`);
|
||||
};
|
||||
|
||||
// 查看详情
|
||||
const viewTransactionDetail = (record) => {
|
||||
message.info(`查看交易详情: ${record.transaction_number}`);
|
||||
};
|
||||
|
||||
const viewContractDetail = (record) => {
|
||||
message.info(`查看合同详情: ${record.contract_number}`);
|
||||
};
|
||||
|
||||
// 确认交易
|
||||
const confirmTransaction = (record) => {
|
||||
message.info(`确认交易: ${record.transaction_number}`);
|
||||
};
|
||||
|
||||
// 签署合同
|
||||
const signContract = (record) => {
|
||||
message.info(`签署合同: ${record.contract_number}`);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loading.value = false;
|
||||
initVolumeChart();
|
||||
initTrendChart();
|
||||
initDistributionChart();
|
||||
initTypeChart();
|
||||
window.addEventListener('resize', resizeCharts);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resizeCharts);
|
||||
if (volumeChartInstance) volumeChartInstance.dispose();
|
||||
if (trendChartInstance) trendChartInstance.dispose();
|
||||
if (distributionChartInstance) distributionChartInstance.dispose();
|
||||
if (typeChartInstance) typeChartInstance.dispose();
|
||||
loadTransactions();
|
||||
loadContracts();
|
||||
});
|
||||
|
||||
return {
|
||||
volumeData,
|
||||
farmRankingData,
|
||||
traderRankingData,
|
||||
farmRankingColumns,
|
||||
traderRankingColumns,
|
||||
loading,
|
||||
error,
|
||||
volumeChart,
|
||||
trendChart,
|
||||
distributionChart,
|
||||
typeChart
|
||||
activeTab,
|
||||
stats,
|
||||
transactions,
|
||||
transactionLoading,
|
||||
transactionSearchForm,
|
||||
transactionPagination,
|
||||
transactionColumns,
|
||||
contracts,
|
||||
contractLoading,
|
||||
contractSearchForm,
|
||||
contractPagination,
|
||||
contractColumns,
|
||||
loadTransactions,
|
||||
loadContracts,
|
||||
getTransactionTypeText,
|
||||
getTransactionStatusColor,
|
||||
getTransactionStatusText,
|
||||
getContractTypeText,
|
||||
getContractStatusColor,
|
||||
getContractStatusText,
|
||||
searchTransactions,
|
||||
resetTransactionSearch,
|
||||
searchContracts,
|
||||
resetContractSearch,
|
||||
handleTransactionTableChange,
|
||||
handleContractTableChange,
|
||||
showAddModal,
|
||||
viewTransactionDetail,
|
||||
viewContractDetail,
|
||||
confirmTransaction,
|
||||
signContract
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.trade-container {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background: linear-gradient(135deg, #0f2027, #20555d, #2c5364);
|
||||
.trade-page {
|
||||
padding: 24px;
|
||||
background: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.trade-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
.page-header {
|
||||
background: white;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.volume-section,
|
||||
.price-section,
|
||||
.type-section,
|
||||
.ranking-section {
|
||||
box-shadow: var(--box-shadow);
|
||||
border-radius: var(--border-radius-lg);
|
||||
.stats-cards {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.volume-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.volume-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.volume-card {
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.card-change {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.card-change.positive {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.card-change.negative {
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
.chart-placeholder {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.price-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.type-content {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.ranking-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.loading-indicator {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 200px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(76, 175, 80, 0.3);
|
||||
border-top: 4px solid var(--primary-color);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.error-message {
|
||||
padding: 20px;
|
||||
.stats-cards .ant-card {
|
||||
text-align: center;
|
||||
color: var(--danger-color);
|
||||
background: rgba(244, 67, 54, 0.1);
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid rgba(244, 67, 54, 0.3);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.trade-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.volume-content,
|
||||
.price-content,
|
||||
.ranking-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.chart-placeholder {
|
||||
height: 250px;
|
||||
}
|
||||
.trade-tabs {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-card .ant-form {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
345
admin-system/dashboard/src/views/UserManagement.vue
Normal file
345
admin-system/dashboard/src/views/UserManagement.vue
Normal file
@@ -0,0 +1,345 @@
|
||||
<template>
|
||||
<div class="user-management">
|
||||
<a-card title="用户管理" :bordered="false">
|
||||
<!-- 操作按钮 -->
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="showAddModal = true">
|
||||
<template #icon><UserAddOutlined /></template>
|
||||
添加用户
|
||||
</a-button>
|
||||
<a-button @click="loadUsers">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
刷新
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 搜索表单 -->
|
||||
<div class="search-form">
|
||||
<a-form layout="inline" :model="searchForm" @finish="handleSearch">
|
||||
<a-form-item label="用户名">
|
||||
<a-input v-model:value="searchForm.username" placeholder="请输入用户名" />
|
||||
</a-form-item>
|
||||
<a-form-item label="用户类型">
|
||||
<a-select v-model:value="searchForm.user_type" placeholder="请选择用户类型" style="width: 120px;">
|
||||
<a-select-option value="">全部</a-select-option>
|
||||
<a-select-option value="admin">管理员</a-select-option>
|
||||
<a-select-option value="farmer">养殖户</a-select-option>
|
||||
<a-select-option value="banker">银行职员</a-select-option>
|
||||
<a-select-option value="insurer">保险员</a-select-option>
|
||||
<a-select-option value="government">政府人员</a-select-option>
|
||||
<a-select-option value="trader">交易员</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" html-type="submit">搜索</a-button>
|
||||
<a-button style="margin-left: 8px;" @click="resetSearch">重置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<!-- 用户表格 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="users"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'user_type'">
|
||||
<a-tag :color="getUserTypeColor(record.user_type)">
|
||||
{{ getUserTypeText(record.user_type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-tag :color="record.status === 1 ? 'green' : 'red'">
|
||||
{{ record.status === 1 ? '正常' : '禁用' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="editUser(record)">编辑</a-button>
|
||||
<a-button type="link" size="small" @click="viewUser(record)">查看</a-button>
|
||||
<a-popconfirm
|
||||
title="确定要删除这个用户吗?"
|
||||
@confirm="deleteUser(record.id)"
|
||||
>
|
||||
<a-button type="link" size="small" danger>删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 添加/编辑用户模态框 -->
|
||||
<a-modal
|
||||
v-model:open="showAddModal"
|
||||
:title="editingUser ? '编辑用户' : '添加用户'"
|
||||
@ok="handleSaveUser"
|
||||
@cancel="handleCancel"
|
||||
:confirm-loading="saving"
|
||||
>
|
||||
<a-form :model="userForm" :rules="rules" ref="userFormRef" layout="vertical">
|
||||
<a-form-item label="用户名" name="username">
|
||||
<a-input v-model:value="userForm.username" placeholder="请输入用户名" />
|
||||
</a-form-item>
|
||||
<a-form-item label="真实姓名" name="real_name">
|
||||
<a-input v-model:value="userForm.real_name" placeholder="请输入真实姓名" />
|
||||
</a-form-item>
|
||||
<a-form-item label="邮箱" name="email">
|
||||
<a-input v-model:value="userForm.email" placeholder="请输入邮箱" />
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号" name="phone">
|
||||
<a-input v-model:value="userForm.phone" placeholder="请输入手机号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="用户类型" name="user_type">
|
||||
<a-select v-model:value="userForm.user_type" placeholder="请选择用户类型">
|
||||
<a-select-option value="admin">管理员</a-select-option>
|
||||
<a-select-option value="farmer">养殖户</a-select-option>
|
||||
<a-select-option value="banker">银行职员</a-select-option>
|
||||
<a-select-option value="insurer">保险员</a-select-option>
|
||||
<a-select-option value="government">政府人员</a-select-option>
|
||||
<a-select-option value="trader">交易员</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="!editingUser" label="密码" name="password">
|
||||
<a-input-password v-model:value="userForm.password" placeholder="请输入密码" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { UserAddOutlined, ReloadOutlined } from '@ant-design/icons-vue';
|
||||
import { userAPI } from '../services/api.js';
|
||||
|
||||
// 响应式数据
|
||||
const users = ref([]);
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const showAddModal = ref(false);
|
||||
const editingUser = ref(null);
|
||||
const userFormRef = ref();
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
username: '',
|
||||
user_type: '',
|
||||
});
|
||||
|
||||
// 用户表单
|
||||
const userForm = reactive({
|
||||
username: '',
|
||||
real_name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
user_type: '',
|
||||
password: '',
|
||||
});
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`,
|
||||
});
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{ title: 'ID', dataIndex: 'id', key: 'id', width: 80 },
|
||||
{ title: '用户名', dataIndex: 'username', key: 'username' },
|
||||
{ title: '真实姓名', dataIndex: 'real_name', key: 'real_name' },
|
||||
{ title: '邮箱', dataIndex: 'email', key: 'email' },
|
||||
{ title: '手机号', dataIndex: 'phone', key: 'phone' },
|
||||
{ title: '用户类型', dataIndex: 'user_type', key: 'user_type' },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status', width: 100 },
|
||||
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at', width: 160 },
|
||||
{ title: '操作', key: 'action', width: 200 },
|
||||
];
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
username: [{ required: true, message: '请输入用户名' }],
|
||||
real_name: [{ required: true, message: '请输入真实姓名' }],
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱' },
|
||||
{ type: 'email', message: '邮箱格式不正确' },
|
||||
],
|
||||
phone: [{ required: true, message: '请输入手机号' }],
|
||||
user_type: [{ required: true, message: '请选择用户类型' }],
|
||||
password: [{ required: true, message: '请输入密码', min: 6 }],
|
||||
};
|
||||
|
||||
// 加载用户列表
|
||||
const loadUsers = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.current,
|
||||
limit: pagination.pageSize,
|
||||
...searchForm,
|
||||
};
|
||||
|
||||
const response = await userAPI.getUsers(params);
|
||||
|
||||
if (response.success) {
|
||||
users.value = response.data.users || [];
|
||||
pagination.total = response.data.pagination?.total || 0;
|
||||
} else {
|
||||
message.error(response.message || '获取用户列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户列表失败:', error);
|
||||
message.error('获取用户列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 表格变化处理
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.current = pag.current;
|
||||
pagination.pageSize = pag.pageSize;
|
||||
loadUsers();
|
||||
};
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1;
|
||||
loadUsers();
|
||||
};
|
||||
|
||||
// 重置搜索
|
||||
const resetSearch = () => {
|
||||
Object.assign(searchForm, {
|
||||
username: '',
|
||||
user_type: '',
|
||||
});
|
||||
pagination.current = 1;
|
||||
loadUsers();
|
||||
};
|
||||
|
||||
// 获取用户类型颜色
|
||||
const getUserTypeColor = (type) => {
|
||||
const colors = {
|
||||
admin: 'red',
|
||||
farmer: 'green',
|
||||
banker: 'blue',
|
||||
insurer: 'orange',
|
||||
government: 'purple',
|
||||
trader: 'cyan',
|
||||
};
|
||||
return colors[type] || 'default';
|
||||
};
|
||||
|
||||
// 获取用户类型文本
|
||||
const getUserTypeText = (type) => {
|
||||
const texts = {
|
||||
admin: '管理员',
|
||||
farmer: '养殖户',
|
||||
banker: '银行职员',
|
||||
insurer: '保险员',
|
||||
government: '政府人员',
|
||||
trader: '交易员',
|
||||
};
|
||||
return texts[type] || type;
|
||||
};
|
||||
|
||||
// 编辑用户
|
||||
const editUser = (record) => {
|
||||
editingUser.value = record;
|
||||
Object.assign(userForm, {
|
||||
username: record.username,
|
||||
real_name: record.real_name,
|
||||
email: record.email,
|
||||
phone: record.phone,
|
||||
user_type: record.user_type,
|
||||
password: '',
|
||||
});
|
||||
showAddModal.value = true;
|
||||
};
|
||||
|
||||
// 查看用户
|
||||
const viewUser = (record) => {
|
||||
message.info(`查看用户: ${record.real_name}`);
|
||||
};
|
||||
|
||||
// 删除用户
|
||||
const deleteUser = async (id) => {
|
||||
try {
|
||||
const response = await userAPI.deleteUser(id);
|
||||
if (response.success) {
|
||||
message.success('删除成功');
|
||||
loadUsers();
|
||||
} else {
|
||||
message.error(response.message || '删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除用户失败:', error);
|
||||
message.error('删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 保存用户
|
||||
const handleSaveUser = async () => {
|
||||
try {
|
||||
await userFormRef.value.validate();
|
||||
saving.value = true;
|
||||
|
||||
let response;
|
||||
if (editingUser.value) {
|
||||
response = await userAPI.updateUser(editingUser.value.id, userForm);
|
||||
} else {
|
||||
response = await userAPI.createUser(userForm);
|
||||
}
|
||||
|
||||
if (response.success) {
|
||||
message.success(editingUser.value ? '更新成功' : '创建成功');
|
||||
showAddModal.value = false;
|
||||
loadUsers();
|
||||
} else {
|
||||
message.error(response.message || '保存失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存用户失败:', error);
|
||||
message.error('保存失败');
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 取消操作
|
||||
const handleCancel = () => {
|
||||
showAddModal.value = false;
|
||||
editingUser.value = null;
|
||||
userFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
// 组件挂载时加载数据
|
||||
onMounted(() => {
|
||||
loadUsers();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-management {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
}
|
||||
</style>
|
||||
426
api-test.html
Normal file
426
api-test.html
Normal file
@@ -0,0 +1,426 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>锡林郭勒盟智慧养殖API测试页面</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Microsoft YaHei', Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #2c3e50;
|
||||
text-align: center;
|
||||
}
|
||||
.section {
|
||||
margin: 20px 0;
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.section h3 {
|
||||
color: #34495e;
|
||||
margin-top: 0;
|
||||
}
|
||||
button {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 15px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
margin: 5px;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
.result {
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
white-space: pre-wrap;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.success {
|
||||
border-color: #28a745;
|
||||
background-color: #d4edda;
|
||||
}
|
||||
.error {
|
||||
border-color: #dc3545;
|
||||
background-color: #f8d7da;
|
||||
}
|
||||
.token-display {
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🐄 锡林郭勒盟智慧养殖产业平台 API 测试</h1>
|
||||
|
||||
<div class="section">
|
||||
<h3>🔧 系统状态检查</h3>
|
||||
<button onclick="checkHealth()">检查系统健康状态</button>
|
||||
<button onclick="checkDatabase()">检查数据库状态</button>
|
||||
<div id="health-result" class="result"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>🔐 用户认证</h3>
|
||||
<button onclick="login()">管理员登录</button>
|
||||
<button onclick="getProfile()">获取用户信息</button>
|
||||
<button onclick="getPermissions()">获取用户权限</button>
|
||||
<div id="token-display" class="token-display" style="display:none;">
|
||||
<strong>当前Token:</strong> <span id="current-token"></span>
|
||||
</div>
|
||||
<div id="auth-result" class="result"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>👥 用户管理</h3>
|
||||
<button onclick="getUserList()">获取用户列表</button>
|
||||
<button onclick="getRoleList()">获取角色列表</button>
|
||||
<button onclick="createTestUser()">创建测试用户</button>
|
||||
<div id="user-result" class="result"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>🐮 牛只档案管理</h3>
|
||||
<button onclick="getCattleList()">获取牛只列表</button>
|
||||
<button onclick="getCattleStats()">获取牛只统计</button>
|
||||
<button onclick="createTestCattle()">创建测试牛只</button>
|
||||
<button onclick="getCattleDetail()">获取牛只详情</button>
|
||||
<div id="cattle-result" class="result"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>📊 大屏数据</h3>
|
||||
<button onclick="getRegions()">获取区域数据</button>
|
||||
<button onclick="getRegionDetail()">获取区域详情</button>
|
||||
<div id="dashboard-result" class="result"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>💰 金融服务监管</h3>
|
||||
<button onclick="getLoanList()">获取贷款列表</button>
|
||||
<button onclick="getFinanceStats()">获取金融统计</button>
|
||||
<button onclick="createTestLoan()">创建测试贷款</button>
|
||||
<div id="finance-result" class="result"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>📝 交易管理</h3>
|
||||
<button onclick="getTransactionList()">获取交易列表</button>
|
||||
<button onclick="getTradingStats()">获取交易统计</button>
|
||||
<button onclick="getContractList()">获取合同列表</button>
|
||||
<div id="trading-result" class="result"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>🏢 政府监管</h3>
|
||||
<button onclick="getFarmSupervision()">获取牧场监管</button>
|
||||
<button onclick="getInspectionList()">获取检查记录</button>
|
||||
<button onclick="getGovStats()">获取监管统计</button>
|
||||
<button onclick="getPolicyList()">获取政策法规</button>
|
||||
<div id="government-result" class="result"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>🛍️ 商城管理</h3>
|
||||
<button onclick="getProductList()">获取商品列表</button>
|
||||
<button onclick="getOrderList()">获取订单列表</button>
|
||||
<button onclick="getMallStats()">获取商城统计</button>
|
||||
<button onclick="getProductDetail()">获取商品详情</button>
|
||||
<div id="mall-result" class="result"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API_BASE = 'http://localhost:8889';
|
||||
let currentToken = '';
|
||||
|
||||
function displayResult(elementId, data, isSuccess = true) {
|
||||
const element = document.getElementById(elementId);
|
||||
element.textContent = JSON.stringify(data, null, 2);
|
||||
element.className = isSuccess ? 'result success' : 'result error';
|
||||
}
|
||||
|
||||
function updateToken(token) {
|
||||
currentToken = token;
|
||||
document.getElementById('current-token').textContent = token;
|
||||
document.getElementById('token-display').style.display = 'block';
|
||||
}
|
||||
|
||||
async function apiRequest(url, options = {}) {
|
||||
try {
|
||||
const response = await fetch(API_BASE + url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': currentToken ? `Bearer ${currentToken}` : '',
|
||||
...options.headers
|
||||
},
|
||||
...options
|
||||
});
|
||||
const data = await response.json();
|
||||
return { data, status: response.status };
|
||||
} catch (error) {
|
||||
return { error: error.message, status: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
async function checkHealth() {
|
||||
const result = await apiRequest('/health');
|
||||
displayResult('health-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function checkDatabase() {
|
||||
const result = await apiRequest('/api/v1/database/status');
|
||||
displayResult('health-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function login() {
|
||||
const result = await apiRequest('/api/v1/auth/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
username: 'admin',
|
||||
password: 'admin123'
|
||||
})
|
||||
});
|
||||
|
||||
if (result.data && result.data.success) {
|
||||
updateToken(result.data.data.token);
|
||||
}
|
||||
|
||||
displayResult('auth-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function getProfile() {
|
||||
if (!currentToken) {
|
||||
alert('请先登录');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await apiRequest('/api/v1/auth/profile');
|
||||
displayResult('auth-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function getPermissions() {
|
||||
if (!currentToken) {
|
||||
alert('请先登录');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await apiRequest('/api/v1/auth/permissions');
|
||||
displayResult('auth-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function getUserList() {
|
||||
if (!currentToken) {
|
||||
alert('请先登录');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await apiRequest('/api/v1/users');
|
||||
displayResult('user-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function getRoleList() {
|
||||
if (!currentToken) {
|
||||
alert('请先登录');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await apiRequest('/api/v1/users/roles/list');
|
||||
displayResult('user-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function createTestUser() {
|
||||
if (!currentToken) {
|
||||
alert('请先登录');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await apiRequest('/api/v1/users', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
username: 'test_farmer',
|
||||
password: 'test123',
|
||||
real_name: '测试养殖户',
|
||||
user_type: 'farmer',
|
||||
email: 'test@example.com'
|
||||
})
|
||||
});
|
||||
displayResult('user-result', result.data || result.error, result.status === 201);
|
||||
}
|
||||
|
||||
async function getCattleList() {
|
||||
if (!currentToken) {
|
||||
alert('请先登录');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await apiRequest('/api/v1/cattle');
|
||||
displayResult('cattle-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function getCattleStats() {
|
||||
if (!currentToken) {
|
||||
alert('请先登录');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await apiRequest('/api/v1/cattle/stats/overview');
|
||||
displayResult('cattle-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function createTestCattle() {
|
||||
if (!currentToken) {
|
||||
alert('请先登录');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await apiRequest('/api/v1/cattle', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
ear_tag: 'TEST' + Date.now(),
|
||||
name: '测试牛只',
|
||||
breed: '西门塔尔牛',
|
||||
gender: 'female',
|
||||
birth_date: '2023-01-01',
|
||||
color: '黄白花',
|
||||
weight: 350.5,
|
||||
farm_location: '测试牧场'
|
||||
})
|
||||
});
|
||||
displayResult('cattle-result', result.data || result.error, result.status === 201);
|
||||
}
|
||||
|
||||
async function getCattleDetail() {
|
||||
if (!currentToken) {
|
||||
alert('请先登录');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await apiRequest('/api/v1/cattle/1');
|
||||
displayResult('cattle-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function getRegions() {
|
||||
const result = await apiRequest('/api/v1/dashboard/map/regions');
|
||||
displayResult('dashboard-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function getRegionDetail() {
|
||||
const result = await apiRequest('/api/v1/dashboard/map/region/xlg');
|
||||
displayResult('dashboard-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
// 金融服务测试函数
|
||||
async function getLoanList() {
|
||||
const result = await apiRequest('/api/v1/finance/loans');
|
||||
displayResult('finance-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function getFinanceStats() {
|
||||
const result = await apiRequest('/api/v1/finance/statistics');
|
||||
displayResult('finance-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function createTestLoan() {
|
||||
const result = await apiRequest('/api/v1/finance/loans', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
loan_type: 'cattle',
|
||||
loan_amount: 500000,
|
||||
purpose: '购买优质肉牛',
|
||||
term_months: 24
|
||||
})
|
||||
});
|
||||
displayResult('finance-result', result.data || result.error, result.status === 201 || result.status === 200);
|
||||
}
|
||||
|
||||
// 交易管理测试函数
|
||||
async function getTransactionList() {
|
||||
const result = await apiRequest('/api/v1/trading/transactions');
|
||||
displayResult('trading-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function getTradingStats() {
|
||||
const result = await apiRequest('/api/v1/trading/statistics');
|
||||
displayResult('trading-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function getContractList() {
|
||||
const result = await apiRequest('/api/v1/trading/contracts');
|
||||
displayResult('trading-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
// 政府监管测试函数
|
||||
async function getFarmSupervision() {
|
||||
const result = await apiRequest('/api/v1/government/farms/supervision');
|
||||
displayResult('government-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function getInspectionList() {
|
||||
const result = await apiRequest('/api/v1/government/inspections');
|
||||
displayResult('government-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function getGovStats() {
|
||||
const result = await apiRequest('/api/v1/government/statistics');
|
||||
displayResult('government-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function getPolicyList() {
|
||||
const result = await apiRequest('/api/v1/government/policies');
|
||||
displayResult('government-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
// 商城管理测试函数
|
||||
async function getProductList() {
|
||||
const result = await apiRequest('/api/v1/mall/products');
|
||||
displayResult('mall-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function getOrderList() {
|
||||
const result = await apiRequest('/api/v1/mall/orders');
|
||||
displayResult('mall-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function getMallStats() {
|
||||
const result = await apiRequest('/api/v1/mall/statistics');
|
||||
displayResult('mall-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
async function getProductDetail() {
|
||||
const result = await apiRequest('/api/v1/mall/products/1');
|
||||
displayResult('mall-result', result.data || result.error, result.status === 200);
|
||||
}
|
||||
|
||||
// 页面加载时自动检查系统状态
|
||||
window.onload = function() {
|
||||
checkHealth();
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
100
backend-java/README.md
Normal file
100
backend-java/README.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# xlxumu Java后端项目
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
backend-java/
|
||||
├── api/ # API定义
|
||||
├── common/ # 公共模块
|
||||
├── config-server/ # 配置服务器
|
||||
├── docs/ # 文档
|
||||
├── gateway/ # 网关服务
|
||||
├── registry/ # 服务注册中心
|
||||
├── scripts/ # 脚本
|
||||
└── services/ # 微服务
|
||||
├── ai-service/ # AI服务
|
||||
├── data-platform-service/ # 数据平台服务
|
||||
├── farming-service/ # 农业管理服务 (端口: 8081)
|
||||
├── finance-service/ # 金融服务
|
||||
├── government-service/ # 政府监管服务
|
||||
├── mall-service/ # 商城服务
|
||||
├── trade-service/ # 交易服务
|
||||
└── user-center-service/ # 用户中心服务 (端口: 8082)
|
||||
```
|
||||
|
||||
## 技术栈
|
||||
|
||||
- Java 8+
|
||||
- Spring Boot 2.7.x
|
||||
- Spring Cloud 2021.x
|
||||
- Maven 3.8.x
|
||||
- MySQL 8.0
|
||||
|
||||
## 环境要求
|
||||
|
||||
1. JDK 8或更高版本
|
||||
2. Maven 3.8或更高版本
|
||||
3. MySQL 8.0
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 安装依赖
|
||||
|
||||
```bash
|
||||
# 在backend-java根目录下执行
|
||||
mvn clean install
|
||||
```
|
||||
|
||||
### 2. 配置数据库
|
||||
|
||||
确保MySQL服务正在运行,并创建相应的数据库:
|
||||
|
||||
```sql
|
||||
CREATE DATABASE IF NOT EXISTS xlxumu_farming;
|
||||
CREATE DATABASE IF NOT EXISTS xlxumu_user;
|
||||
```
|
||||
|
||||
### 3. 运行服务
|
||||
|
||||
#### 运行farming-service
|
||||
|
||||
```bash
|
||||
cd services/farming-service
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
服务将在 http://localhost:8081 启动
|
||||
|
||||
#### 运行user-center-service
|
||||
|
||||
```bash
|
||||
cd services/user-center-service
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
服务将在 http://localhost:8082 启动
|
||||
|
||||
## 服务端口分配
|
||||
|
||||
| 服务名称 | 端口 |
|
||||
|---------|------|
|
||||
| farming-service | 8081 |
|
||||
| user-center-service | 8082 |
|
||||
|
||||
## 开发指南
|
||||
|
||||
1. 所有微服务都继承自根pom.xml
|
||||
2. 每个服务都有独立的数据库
|
||||
3. 使用Spring Boot Actuator进行健康检查
|
||||
4. 使用Spring Cloud Gateway作为API网关
|
||||
|
||||
## 构建和部署
|
||||
|
||||
```bash
|
||||
# 构建所有服务
|
||||
mvn clean package
|
||||
|
||||
# 构建单个服务
|
||||
cd services/farming-service
|
||||
mvn clean package
|
||||
```
|
||||
63
backend-java/pom.xml
Normal file
63
backend-java/pom.xml
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.xlxumu</groupId>
|
||||
<artifactId>backend-java</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>XlMuMu Backend Java</name>
|
||||
<description>锡林郭勒盟地区智慧养殖产业平台后端Java服务</description>
|
||||
|
||||
<modules>
|
||||
<module>api</module>
|
||||
<module>gateway</module>
|
||||
<module>registry</module>
|
||||
<module>config-server</module>
|
||||
<module>services</module>
|
||||
<module>common</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<spring.boot.version>2.7.5</spring.boot.version>
|
||||
<spring.cloud.version>2021.0.5</spring.cloud.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring.cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</project>
|
||||
43
backend-java/services/farming-service/pom.xml
Normal file
43
backend-java/services/farming-service/pom.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.xlxumu</groupId>
|
||||
<artifactId>backend-java</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>farming-service</artifactId>
|
||||
<name>Farming Service</name>
|
||||
<description>养殖管理服务</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.xlxumu.farm;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class FarmingServiceApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(FarmingServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
server.port=8081
|
||||
|
||||
spring.datasource.url=jdbc:mysql://localhost:3306/xlxumu_farming?useSSL=false&serverTimezone=UTC
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
|
||||
@@ -0,0 +1,10 @@
|
||||
server.port=8081
|
||||
|
||||
spring.datasource.url=jdbc:mysql://localhost:3306/xlxumu_farming?useSSL=false&serverTimezone=UTC
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
artifactId=farming-service
|
||||
groupId=com.xlxumu
|
||||
version=1.0.0
|
||||
@@ -0,0 +1 @@
|
||||
com/xlxumu/farm/FarmingServiceApplication.class
|
||||
@@ -0,0 +1 @@
|
||||
/Users/ainongkeji/code/vue/xlxumu/backend-java/services/farming-service/src/main/java/com/xlxumu/farm/FarmingServiceApplication.java
|
||||
44
backend-java/services/user-center-service/pom.xml
Normal file
44
backend-java/services/user-center-service/pom.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.xlxumu</groupId>
|
||||
<artifactId>backend-java</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>user-center-service</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.33</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.xlxumu.user;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class UserCenterApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(UserCenterApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
server.port=8082
|
||||
|
||||
spring.datasource.url=jdbc:mysql://localhost:3306/xlxumu_user?useSSL=false&serverTimezone=UTC
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
|
||||
@@ -0,0 +1,10 @@
|
||||
server.port=8082
|
||||
|
||||
spring.datasource.url=jdbc:mysql://localhost:3306/xlxumu_user?useSSL=false&serverTimezone=UTC
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
com/xlxumu/user/UserCenterApplication.class
|
||||
@@ -0,0 +1 @@
|
||||
/Users/ainongkeji/code/vue/xlxumu/backend-java/services/user-center-service/src/main/java/com/xlxumu/user/UserCenterApplication.java
|
||||
37
backend/api/.env
Normal file
37
backend/api/.env
Normal file
@@ -0,0 +1,37 @@
|
||||
# 服务器配置
|
||||
PORT=8889
|
||||
NODE_ENV=development
|
||||
|
||||
# 数据库配置
|
||||
DB_HOST=nj-cdb-3pwh2kz1.sql.tencentcdb.com
|
||||
DB_PORT=20784
|
||||
DB_USER=xymg
|
||||
DB_PASSWORD=aiot741$xymg
|
||||
DB_NAME=xumgdata
|
||||
DB_CHARSET=utf8mb4
|
||||
|
||||
# Redis配置 (待配置)
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
|
||||
# JWT配置
|
||||
JWT_SECRET=xlxumu_jwt_secret_key_2024
|
||||
JWT_EXPIRES_IN=24h
|
||||
|
||||
# 腾讯云对象存储配置 (待配置)
|
||||
COS_SECRET_ID=
|
||||
COS_SECRET_KEY=
|
||||
COS_BUCKET=
|
||||
COS_REGION=
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL=info
|
||||
LOG_FILE=./logs/app.log
|
||||
|
||||
# 安全配置
|
||||
RATE_LIMIT_MAX=100
|
||||
RATE_LIMIT_WINDOW=15
|
||||
|
||||
# WebSocket配置
|
||||
WS_PORT=8001
|
||||
282
backend/api/package-lock.json
generated
282
backend/api/package-lock.json
generated
@@ -8,11 +8,14 @@
|
||||
"name": "xlxumu-api",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"bcrypt": "^6.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^4.21.2",
|
||||
"express-rate-limit": "^8.0.1",
|
||||
"helmet": "^8.1.0"
|
||||
"helmet": "^8.1.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"mysql2": "^3.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
@@ -32,6 +35,29 @@
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||
},
|
||||
"node_modules/aws-ssl-profiles": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
|
||||
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bcrypt": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/bcrypt/-/bcrypt-6.0.0.tgz",
|
||||
"integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^8.3.0",
|
||||
"node-gyp-build": "^4.8.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||
@@ -55,6 +81,12 @@
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-equal-constant-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
@@ -142,6 +174,15 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz",
|
||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@@ -183,6 +224,15 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@@ -339,6 +389,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/generate-function": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/generate-function/-/generate-function-2.3.1.tgz",
|
||||
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-property": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
@@ -462,6 +521,133 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/is-property": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/is-property/-/is-property-1.0.2.tgz",
|
||||
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsonwebtoken": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
||||
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jws": "^3.2.2",
|
||||
"lodash.includes": "^4.3.0",
|
||||
"lodash.isboolean": "^3.0.3",
|
||||
"lodash.isinteger": "^4.0.4",
|
||||
"lodash.isnumber": "^3.0.3",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"lodash.once": "^4.0.0",
|
||||
"ms": "^2.1.1",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmmirror.com/jwa/-/jwa-1.4.2.tgz",
|
||||
"integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-equal-constant-time": "^1.0.1",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jws": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/jws/-/jws-3.2.2.tgz",
|
||||
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jwa": "^1.4.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isboolean": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isinteger": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isnumber": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmmirror.com/long/-/long-5.3.2.tgz",
|
||||
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "7.18.3",
|
||||
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-7.18.3.tgz",
|
||||
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/lru.min": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/lru.min/-/lru.min-1.1.2.tgz",
|
||||
"integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"bun": ">=1.0.0",
|
||||
"deno": ">=1.30.0",
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wellwelwel"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@@ -529,6 +715,54 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/mysql2": {
|
||||
"version": "3.14.4",
|
||||
"resolved": "https://registry.npmmirror.com/mysql2/-/mysql2-3.14.4.tgz",
|
||||
"integrity": "sha512-Cs/jx3WZPNrYHVz+Iunp9ziahaG5uFMvD2R8Zlmc194AqXNxt9HBNu7ZsPYrUtmJsF0egETCWIdMIYAwOGjL1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"aws-ssl-profiles": "^1.1.1",
|
||||
"denque": "^2.1.0",
|
||||
"generate-function": "^2.3.1",
|
||||
"iconv-lite": "^0.7.0",
|
||||
"long": "^5.2.1",
|
||||
"lru.min": "^1.0.0",
|
||||
"named-placeholders": "^1.1.3",
|
||||
"seq-queue": "^0.0.5",
|
||||
"sqlstring": "^2.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mysql2/node_modules/iconv-lite": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.0.tgz",
|
||||
"integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/named-placeholders": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/named-placeholders/-/named-placeholders-1.1.3.tgz",
|
||||
"integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lru-cache": "^7.14.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
@@ -537,6 +771,26 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-8.5.0.tgz",
|
||||
"integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18 || ^20 || >= 21"
|
||||
}
|
||||
},
|
||||
"node_modules/node-gyp-build": {
|
||||
"version": "4.8.4",
|
||||
"resolved": "https://registry.npmmirror.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
|
||||
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"node-gyp-build": "bin.js",
|
||||
"node-gyp-build-optional": "optional.js",
|
||||
"node-gyp-build-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -652,6 +906,18 @@
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz",
|
||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.19.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
|
||||
@@ -688,6 +954,11 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/seq-queue": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/seq-queue/-/seq-queue-0.0.5.tgz",
|
||||
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.16.2",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
||||
@@ -775,6 +1046,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/sqlstring": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmmirror.com/sqlstring/-/sqlstring-2.3.3.tgz",
|
||||
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
|
||||
@@ -8,10 +8,13 @@
|
||||
"dev": "nodemon server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcrypt": "^6.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^4.21.2",
|
||||
"express-rate-limit": "^8.0.1",
|
||||
"helmet": "^8.1.0"
|
||||
"helmet": "^8.1.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"mysql2": "^3.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
554
backend/api/routes/auth.js
Normal file
554
backend/api/routes/auth.js
Normal file
@@ -0,0 +1,554 @@
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcrypt');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const router = express.Router();
|
||||
|
||||
// 导入数据库连接(假设从主服务器文件导入)
|
||||
// 这里暂时用模拟数据,待数据库连接修复后更新
|
||||
let pool = null;
|
||||
|
||||
// 设置数据库连接池(将从主服务器导入)
|
||||
function setPool(dbPool) {
|
||||
pool = dbPool;
|
||||
}
|
||||
|
||||
// JWT中间件验证
|
||||
const authenticateToken = (req, res, next) => {
|
||||
const authHeader = req.headers['authorization'];
|
||||
const token = authHeader && authHeader.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '访问令牌缺失',
|
||||
code: 'TOKEN_MISSING'
|
||||
});
|
||||
}
|
||||
|
||||
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
|
||||
if (err) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '访问令牌无效或已过期',
|
||||
code: 'TOKEN_INVALID'
|
||||
});
|
||||
}
|
||||
req.user = user;
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
// 权限检查中间件
|
||||
const checkPermission = (requiredPermission) => {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 查询用户权限
|
||||
const [permissions] = await pool.execute(`
|
||||
SELECT p.name as permission_name
|
||||
FROM users u
|
||||
JOIN user_roles ur ON u.id = ur.user_id
|
||||
JOIN role_permissions rp ON ur.role_id = rp.role_id
|
||||
JOIN permissions p ON rp.permission_id = p.id
|
||||
WHERE u.id = ?
|
||||
`, [req.user.userId]);
|
||||
|
||||
const userPermissions = permissions.map(p => p.permission_name);
|
||||
|
||||
if (!userPermissions.includes(requiredPermission)) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足',
|
||||
code: 'INSUFFICIENT_PERMISSION',
|
||||
required: requiredPermission
|
||||
});
|
||||
}
|
||||
|
||||
req.userPermissions = userPermissions;
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('权限检查错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '权限检查失败',
|
||||
code: 'PERMISSION_CHECK_ERROR'
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// 用户注册
|
||||
router.post('/register', async (req, res) => {
|
||||
try {
|
||||
const { username, email, phone, password, real_name, user_type } = req.body;
|
||||
|
||||
// 输入验证
|
||||
if (!username || !password || !user_type) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '用户名、密码和用户类型为必填项',
|
||||
code: 'MISSING_REQUIRED_FIELDS'
|
||||
});
|
||||
}
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用,请稍后重试',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在
|
||||
const [existingUsers] = await pool.execute(
|
||||
'SELECT id FROM users WHERE username = ? OR email = ? OR phone = ?',
|
||||
[username, email || null, phone || null]
|
||||
);
|
||||
|
||||
if (existingUsers.length > 0) {
|
||||
return res.status(409).json({
|
||||
success: false,
|
||||
message: '用户名、邮箱或手机号已存在',
|
||||
code: 'USER_EXISTS'
|
||||
});
|
||||
}
|
||||
|
||||
// 密码加密
|
||||
const saltRounds = 10;
|
||||
const password_hash = await bcrypt.hash(password, saltRounds);
|
||||
|
||||
// 插入新用户
|
||||
const [result] = await pool.execute(
|
||||
'INSERT INTO users (username, email, phone, password_hash, real_name, user_type) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[username, email || null, phone || null, password_hash, real_name || null, user_type]
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '用户注册成功',
|
||||
data: {
|
||||
userId: result.insertId,
|
||||
username,
|
||||
user_type
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('用户注册错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '注册失败,请稍后重试',
|
||||
code: 'REGISTRATION_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 用户登录
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '用户名和密码为必填项',
|
||||
code: 'MISSING_CREDENTIALS'
|
||||
});
|
||||
}
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据(用于测试)
|
||||
if (username === 'admin' && password === 'admin123') {
|
||||
const token = jwt.sign(
|
||||
{ userId: 1, username: 'admin', user_type: 'admin' },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
|
||||
);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '登录成功(测试模式)',
|
||||
data: {
|
||||
token,
|
||||
user: {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
user_type: 'admin',
|
||||
real_name: '系统管理员'
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误',
|
||||
code: 'INVALID_CREDENTIALS'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 检查数据库连接是否可用
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,使用测试模式
|
||||
if (username === 'admin' && password === 'admin123') {
|
||||
const token = jwt.sign(
|
||||
{ userId: 1, username: 'admin', user_type: 'admin' },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
|
||||
);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '登录成功(测试模式 - 数据库不可用)',
|
||||
data: {
|
||||
token,
|
||||
user: {
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
user_type: 'admin',
|
||||
real_name: '系统管理员'
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误(测试模式)',
|
||||
code: 'INVALID_CREDENTIALS'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 查询用户
|
||||
const [users] = await pool.execute(
|
||||
'SELECT id, username, password_hash, user_type, real_name, status FROM users WHERE username = ?',
|
||||
[username]
|
||||
);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误',
|
||||
code: 'INVALID_CREDENTIALS'
|
||||
});
|
||||
}
|
||||
|
||||
const user = users[0];
|
||||
|
||||
// 检查用户状态
|
||||
if (user.status === 0) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '用户账号已被禁用',
|
||||
code: 'ACCOUNT_DISABLED'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
const isPasswordValid = await bcrypt.compare(password, user.password_hash);
|
||||
if (!isPasswordValid) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误',
|
||||
code: 'INVALID_CREDENTIALS'
|
||||
});
|
||||
}
|
||||
|
||||
// 生成JWT令牌
|
||||
const token = jwt.sign(
|
||||
{
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
user_type: user.user_type
|
||||
},
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
|
||||
);
|
||||
|
||||
// 更新最后登录时间
|
||||
await pool.execute(
|
||||
'UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[user.id]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '登录成功',
|
||||
data: {
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
user_type: user.user_type,
|
||||
real_name: user.real_name
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('用户登录错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '登录失败,请稍后重试',
|
||||
code: 'LOGIN_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取当前用户信息
|
||||
router.get('/profile', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
id: req.user.userId,
|
||||
username: req.user.username,
|
||||
user_type: req.user.user_type,
|
||||
real_name: '系统管理员',
|
||||
email: 'admin@xlxumu.com',
|
||||
status: 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 检查数据库连接
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
id: req.user.userId,
|
||||
username: req.user.username,
|
||||
user_type: req.user.user_type,
|
||||
real_name: '系统管理员',
|
||||
email: 'admin@xlxumu.com',
|
||||
status: 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const [users] = await pool.execute(
|
||||
'SELECT id, username, email, phone, real_name, user_type, status, last_login, created_at FROM users WHERE id = ?',
|
||||
[req.user.userId]
|
||||
);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在',
|
||||
code: 'USER_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: users[0]
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取用户信息错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取用户信息失败',
|
||||
code: 'PROFILE_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 更新用户信息
|
||||
router.put('/profile', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { real_name, email, phone } = req.body;
|
||||
const userId = req.user.userId;
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查邮箱和手机号是否被其他用户使用
|
||||
if (email || phone) {
|
||||
const [existingUsers] = await pool.execute(
|
||||
'SELECT id FROM users WHERE (email = ? OR phone = ?) AND id != ?',
|
||||
[email || null, phone || null, userId]
|
||||
);
|
||||
|
||||
if (existingUsers.length > 0) {
|
||||
return res.status(409).json({
|
||||
success: false,
|
||||
message: '邮箱或手机号已被其他用户使用',
|
||||
code: 'CONTACT_EXISTS'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
await pool.execute(
|
||||
'UPDATE users SET real_name = ?, email = ?, phone = ? WHERE id = ?',
|
||||
[real_name || null, email || null, phone || null, userId]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '用户信息更新成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新用户信息错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新用户信息失败',
|
||||
code: 'UPDATE_PROFILE_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 修改密码
|
||||
router.post('/change-password', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { current_password, new_password } = req.body;
|
||||
const userId = req.user.userId;
|
||||
|
||||
if (!current_password || !new_password) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '当前密码和新密码为必填项',
|
||||
code: 'MISSING_PASSWORDS'
|
||||
});
|
||||
}
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取当前密码哈希
|
||||
const [users] = await pool.execute(
|
||||
'SELECT password_hash FROM users WHERE id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在',
|
||||
code: 'USER_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证当前密码
|
||||
const isCurrentPasswordValid = await bcrypt.compare(current_password, users[0].password_hash);
|
||||
if (!isCurrentPasswordValid) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '当前密码错误',
|
||||
code: 'INVALID_CURRENT_PASSWORD'
|
||||
});
|
||||
}
|
||||
|
||||
// 加密新密码
|
||||
const saltRounds = 10;
|
||||
const new_password_hash = await bcrypt.hash(new_password, saltRounds);
|
||||
|
||||
// 更新密码
|
||||
await pool.execute(
|
||||
'UPDATE users SET password_hash = ? WHERE id = ?',
|
||||
[new_password_hash, userId]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '密码修改成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('修改密码错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '修改密码失败',
|
||||
code: 'CHANGE_PASSWORD_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取用户权限
|
||||
router.get('/permissions', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟权限数据
|
||||
const mockPermissions = ['user_manage', 'cattle_manage', 'data_view', 'system_config'];
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
permissions: mockPermissions,
|
||||
roles: ['admin']
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 查询用户角色和权限
|
||||
const [results] = await pool.execute(`
|
||||
SELECT r.name as role_name, p.name as permission_name, p.module
|
||||
FROM users u
|
||||
JOIN user_roles ur ON u.id = ur.user_id
|
||||
JOIN roles r ON ur.role_id = r.id
|
||||
LEFT JOIN role_permissions rp ON r.id = rp.role_id
|
||||
LEFT JOIN permissions p ON rp.permission_id = p.id
|
||||
WHERE u.id = ?
|
||||
`, [req.user.userId]);
|
||||
|
||||
const roles = [...new Set(results.map(r => r.role_name))];
|
||||
const permissions = [...new Set(results.filter(r => r.permission_name).map(r => r.permission_name))];
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
roles,
|
||||
permissions
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取权限错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取权限失败',
|
||||
code: 'PERMISSIONS_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 登出(主要用于前端清除token,后端不需要处理)
|
||||
router.post('/logout', authenticateToken, (req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
message: '登出成功'
|
||||
});
|
||||
});
|
||||
|
||||
// 导出模块
|
||||
module.exports = {
|
||||
router,
|
||||
authenticateToken,
|
||||
checkPermission,
|
||||
setPool
|
||||
};
|
||||
774
backend/api/routes/cattle.js
Normal file
774
backend/api/routes/cattle.js
Normal file
@@ -0,0 +1,774 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
// 中间件将在服务器启动时设置
|
||||
let authenticateToken = (req, res, next) => {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '认证中间件未初始化',
|
||||
code: 'AUTH_NOT_INITIALIZED'
|
||||
});
|
||||
};
|
||||
|
||||
let checkPermission = (permission) => {
|
||||
return (req, res, next) => {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '权限中间件未初始化',
|
||||
code: 'PERMISSION_NOT_INITIALIZED'
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
let pool = null;
|
||||
|
||||
// 设置中间件和数据库连接(从主服务器导入)
|
||||
function setMiddleware(auth, permission, dbPool) {
|
||||
authenticateToken = auth;
|
||||
checkPermission = permission;
|
||||
pool = dbPool;
|
||||
}
|
||||
|
||||
// 获取牛只列表
|
||||
router.get('/', authenticateToken, checkPermission('cattle_manage'), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
owner_id,
|
||||
breed,
|
||||
status,
|
||||
health_status,
|
||||
search
|
||||
} = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockCattle = [
|
||||
{
|
||||
id: 1,
|
||||
ear_tag: 'XL001',
|
||||
name: '小花',
|
||||
breed: '西门塔尔牛',
|
||||
gender: 'female',
|
||||
birth_date: '2022-03-15',
|
||||
color: '黄白花',
|
||||
weight: 450.50,
|
||||
health_status: 'healthy',
|
||||
status: 'active',
|
||||
farm_location: '锡林浩特市第一牧场',
|
||||
created_at: '2024-01-01 00:00:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
ear_tag: 'XL002',
|
||||
name: '壮壮',
|
||||
breed: '安格斯牛',
|
||||
gender: 'male',
|
||||
birth_date: '2021-08-20',
|
||||
color: '黑色',
|
||||
weight: 580.75,
|
||||
health_status: 'healthy',
|
||||
status: 'active',
|
||||
farm_location: '锡林浩特市第一牧场',
|
||||
created_at: '2024-01-01 01:00:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
ear_tag: 'XL003',
|
||||
name: '美美',
|
||||
breed: '夏洛莱牛',
|
||||
gender: 'female',
|
||||
birth_date: '2022-05-10',
|
||||
color: '白色',
|
||||
weight: 420.30,
|
||||
health_status: 'healthy',
|
||||
status: 'active',
|
||||
farm_location: '东乌旗牧场A',
|
||||
created_at: '2024-01-01 02:00:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
cattle: mockCattle,
|
||||
pagination: {
|
||||
total: mockCattle.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockCattle.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 检查数据库连接
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
const mockCattle = [
|
||||
{
|
||||
id: 1,
|
||||
ear_tag: 'XL001',
|
||||
name: '小花',
|
||||
breed: '西门塔尔牛',
|
||||
gender: 'female',
|
||||
birth_date: '2022-03-15',
|
||||
color: '黄白花',
|
||||
weight: 450.50,
|
||||
health_status: 'healthy',
|
||||
status: 'active',
|
||||
farm_location: '锡林浩特市第一牧场',
|
||||
created_at: '2024-01-01 00:00:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,返回模拟数据',
|
||||
data: {
|
||||
cattle: mockCattle,
|
||||
pagination: {
|
||||
total: mockCattle.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockCattle.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 构建查询条件
|
||||
let whereClause = '1=1';
|
||||
let queryParams = [];
|
||||
|
||||
if (owner_id) {
|
||||
whereClause += ' AND owner_id = ?';
|
||||
queryParams.push(owner_id);
|
||||
}
|
||||
|
||||
if (breed) {
|
||||
whereClause += ' AND breed = ?';
|
||||
queryParams.push(breed);
|
||||
}
|
||||
|
||||
if (status) {
|
||||
whereClause += ' AND status = ?';
|
||||
queryParams.push(status);
|
||||
}
|
||||
|
||||
if (health_status) {
|
||||
whereClause += ' AND health_status = ?';
|
||||
queryParams.push(health_status);
|
||||
}
|
||||
|
||||
if (search) {
|
||||
whereClause += ' AND (ear_tag LIKE ? OR name LIKE ? OR breed LIKE ?)';
|
||||
const searchTerm = `%${search}%`;
|
||||
queryParams.push(searchTerm, searchTerm, searchTerm);
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
const [countResult] = await pool.execute(
|
||||
`SELECT COUNT(*) as total FROM cattle WHERE ${whereClause}`,
|
||||
queryParams
|
||||
);
|
||||
const total = countResult[0].total;
|
||||
|
||||
// 获取牛只列表
|
||||
const [cattle] = await pool.execute(
|
||||
`SELECT c.*, u.real_name as owner_name
|
||||
FROM cattle c
|
||||
LEFT JOIN users u ON c.owner_id = u.id
|
||||
WHERE ${whereClause}
|
||||
ORDER BY c.created_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...queryParams, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
cattle,
|
||||
pagination: {
|
||||
total,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取牛只列表错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取牛只列表失败',
|
||||
code: 'GET_CATTLE_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取牛只详情
|
||||
router.get('/:id', authenticateToken, checkPermission('cattle_manage'), async (req, res) => {
|
||||
try {
|
||||
const cattleId = req.params.id;
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查数据库连接
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取牛只基本信息
|
||||
const [cattle] = await pool.execute(
|
||||
`SELECT c.*, u.real_name as owner_name, u.phone as owner_phone
|
||||
FROM cattle c
|
||||
LEFT JOIN users u ON c.owner_id = u.id
|
||||
WHERE c.id = ?`,
|
||||
[cattleId]
|
||||
);
|
||||
|
||||
if (cattle.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '牛只不存在',
|
||||
code: 'CATTLE_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取最近的饲养记录
|
||||
const [feedingRecords] = await pool.execute(
|
||||
`SELECT * FROM feeding_records
|
||||
WHERE cattle_id = ?
|
||||
ORDER BY record_date DESC
|
||||
LIMIT 10`,
|
||||
[cattleId]
|
||||
);
|
||||
|
||||
// 获取繁殖记录
|
||||
const [breedingRecords] = await pool.execute(
|
||||
`SELECT * FROM breeding_records
|
||||
WHERE cattle_id = ?
|
||||
ORDER BY breeding_date DESC
|
||||
LIMIT 5`,
|
||||
[cattleId]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
cattle: cattle[0],
|
||||
feeding_records: feedingRecords,
|
||||
breeding_records: breedingRecords
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取牛只详情错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取牛只详情失败',
|
||||
code: 'GET_CATTLE_DETAIL_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 创建牛只档案
|
||||
router.post('/', authenticateToken, checkPermission('cattle_manage'), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
ear_tag,
|
||||
name,
|
||||
breed,
|
||||
gender,
|
||||
birth_date,
|
||||
color,
|
||||
weight,
|
||||
owner_id,
|
||||
farm_location
|
||||
} = req.body;
|
||||
|
||||
// 输入验证
|
||||
if (!ear_tag || !breed || !gender) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '耳标号、品种和性别为必填项',
|
||||
code: 'MISSING_REQUIRED_FIELDS'
|
||||
});
|
||||
}
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查数据库连接
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查耳标是否已存在
|
||||
const [existingCattle] = await pool.execute(
|
||||
'SELECT id FROM cattle WHERE ear_tag = ?',
|
||||
[ear_tag]
|
||||
);
|
||||
|
||||
if (existingCattle.length > 0) {
|
||||
return res.status(409).json({
|
||||
success: false,
|
||||
message: '耳标号已存在',
|
||||
code: 'EAR_TAG_EXISTS'
|
||||
});
|
||||
}
|
||||
|
||||
// 插入新牛只
|
||||
const [result] = await pool.execute(
|
||||
`INSERT INTO cattle (ear_tag, name, breed, gender, birth_date, color, weight, owner_id, farm_location)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[ear_tag, name || null, breed, gender, birth_date || null, color || null, weight || null, owner_id || null, farm_location || null]
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '牛只档案创建成功',
|
||||
data: {
|
||||
cattle_id: result.insertId,
|
||||
ear_tag,
|
||||
name,
|
||||
breed
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建牛只档案错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建牛只档案失败',
|
||||
code: 'CREATE_CATTLE_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 更新牛只信息
|
||||
router.put('/:id', authenticateToken, checkPermission('cattle_manage'), async (req, res) => {
|
||||
try {
|
||||
const cattleId = req.params.id;
|
||||
const {
|
||||
name,
|
||||
color,
|
||||
weight,
|
||||
health_status,
|
||||
status,
|
||||
farm_location,
|
||||
owner_id
|
||||
} = req.body;
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查数据库连接
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查牛只是否存在
|
||||
const [cattle] = await pool.execute('SELECT id FROM cattle WHERE id = ?', [cattleId]);
|
||||
if (cattle.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '牛只不存在',
|
||||
code: 'CATTLE_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新牛只信息
|
||||
await pool.execute(
|
||||
`UPDATE cattle
|
||||
SET name = ?, color = ?, weight = ?, health_status = ?, status = ?, farm_location = ?, owner_id = ?
|
||||
WHERE id = ?`,
|
||||
[
|
||||
name || null,
|
||||
color || null,
|
||||
weight || null,
|
||||
health_status || 'healthy',
|
||||
status || 'active',
|
||||
farm_location || null,
|
||||
owner_id || null,
|
||||
cattleId
|
||||
]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '牛只信息更新成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新牛只信息错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新牛只信息失败',
|
||||
code: 'UPDATE_CATTLE_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 删除牛只档案
|
||||
router.delete('/:id', authenticateToken, checkPermission('cattle_manage'), async (req, res) => {
|
||||
try {
|
||||
const cattleId = req.params.id;
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查数据库连接
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查牛只是否存在
|
||||
const [cattle] = await pool.execute('SELECT id FROM cattle WHERE id = ?', [cattleId]);
|
||||
if (cattle.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '牛只不存在',
|
||||
code: 'CATTLE_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
// 删除牛只(级联删除相关记录)
|
||||
await pool.execute('DELETE FROM cattle WHERE id = ?', [cattleId]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '牛只档案删除成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('删除牛只档案错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除牛只档案失败',
|
||||
code: 'DELETE_CATTLE_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取饲养记录
|
||||
router.get('/:id/feeding-records', authenticateToken, checkPermission('cattle_manage'), async (req, res) => {
|
||||
try {
|
||||
const cattleId = req.params.id;
|
||||
const { page = 1, limit = 10, record_type } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查数据库连接
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 构建查询条件
|
||||
let whereClause = 'cattle_id = ?';
|
||||
let queryParams = [cattleId];
|
||||
|
||||
if (record_type) {
|
||||
whereClause += ' AND record_type = ?';
|
||||
queryParams.push(record_type);
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
const [countResult] = await pool.execute(
|
||||
`SELECT COUNT(*) as total FROM feeding_records WHERE ${whereClause}`,
|
||||
queryParams
|
||||
);
|
||||
const total = countResult[0].total;
|
||||
|
||||
// 获取饲养记录
|
||||
const [records] = await pool.execute(
|
||||
`SELECT fr.*, u.real_name as operator_name
|
||||
FROM feeding_records fr
|
||||
LEFT JOIN users u ON fr.operator_id = u.id
|
||||
WHERE ${whereClause}
|
||||
ORDER BY fr.record_date DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...queryParams, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
records,
|
||||
pagination: {
|
||||
total,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取饲养记录错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取饲养记录失败',
|
||||
code: 'GET_FEEDING_RECORDS_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 添加饲养记录
|
||||
router.post('/:id/feeding-records', authenticateToken, checkPermission('cattle_manage'), async (req, res) => {
|
||||
try {
|
||||
const cattleId = req.params.id;
|
||||
const {
|
||||
record_type,
|
||||
feed_type,
|
||||
feed_amount,
|
||||
vaccine_name,
|
||||
treatment_desc,
|
||||
medicine_name,
|
||||
dosage,
|
||||
veterinarian,
|
||||
cost,
|
||||
record_date,
|
||||
notes
|
||||
} = req.body;
|
||||
|
||||
// 输入验证
|
||||
if (!record_type || !record_date) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '记录类型和记录日期为必填项',
|
||||
code: 'MISSING_REQUIRED_FIELDS'
|
||||
});
|
||||
}
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查数据库连接
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查牛只是否存在
|
||||
const [cattle] = await pool.execute('SELECT id FROM cattle WHERE id = ?', [cattleId]);
|
||||
if (cattle.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '牛只不存在',
|
||||
code: 'CATTLE_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
// 插入饲养记录
|
||||
const [result] = await pool.execute(
|
||||
`INSERT INTO feeding_records
|
||||
(cattle_id, record_type, feed_type, feed_amount, vaccine_name, treatment_desc,
|
||||
medicine_name, dosage, veterinarian, cost, record_date, notes, operator_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
cattleId,
|
||||
record_type,
|
||||
feed_type || null,
|
||||
feed_amount || null,
|
||||
vaccine_name || null,
|
||||
treatment_desc || null,
|
||||
medicine_name || null,
|
||||
dosage || null,
|
||||
veterinarian || null,
|
||||
cost || null,
|
||||
record_date,
|
||||
notes || null,
|
||||
req.user.userId
|
||||
]
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '饲养记录添加成功',
|
||||
data: {
|
||||
record_id: result.insertId,
|
||||
record_type,
|
||||
record_date
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('添加饲养记录错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '添加饲养记录失败',
|
||||
code: 'CREATE_FEEDING_RECORD_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取牛只统计信息
|
||||
router.get('/stats/overview', authenticateToken, checkPermission('data_view'), async (req, res) => {
|
||||
try {
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockStats = {
|
||||
total_cattle: 156,
|
||||
healthy_cattle: 148,
|
||||
sick_cattle: 8,
|
||||
by_breed: [
|
||||
{ breed: '西门塔尔牛', count: 68 },
|
||||
{ breed: '安格斯牛', count: 45 },
|
||||
{ breed: '夏洛莱牛', count: 43 }
|
||||
],
|
||||
by_status: [
|
||||
{ status: 'active', count: 142 },
|
||||
{ status: 'sold', count: 12 },
|
||||
{ status: 'quarantine', count: 2 }
|
||||
]
|
||||
};
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: mockStats
|
||||
});
|
||||
}
|
||||
|
||||
// 检查数据库连接
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
const mockStats = {
|
||||
total_cattle: 156,
|
||||
healthy_cattle: 148,
|
||||
sick_cattle: 8,
|
||||
by_breed: [
|
||||
{ breed: '西门塔尔牛', count: 68 },
|
||||
{ breed: '安格斯牛', count: 45 },
|
||||
{ breed: '夏洛莱牛', count: 43 }
|
||||
],
|
||||
by_status: [
|
||||
{ status: 'active', count: 142 },
|
||||
{ status: 'sold', count: 12 },
|
||||
{ status: 'quarantine', count: 2 }
|
||||
]
|
||||
};
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,返回模拟数据',
|
||||
data: mockStats
|
||||
});
|
||||
}
|
||||
|
||||
// 获取总体统计
|
||||
const [totalResult] = await pool.execute('SELECT COUNT(*) as total FROM cattle');
|
||||
const [healthyResult] = await pool.execute('SELECT COUNT(*) as healthy FROM cattle WHERE health_status = "healthy"');
|
||||
const [sickResult] = await pool.execute('SELECT COUNT(*) as sick FROM cattle WHERE health_status IN ("sick", "quarantine")');
|
||||
|
||||
// 按品种统计
|
||||
const [breedStats] = await pool.execute(
|
||||
'SELECT breed, COUNT(*) as count FROM cattle GROUP BY breed ORDER BY count DESC LIMIT 10'
|
||||
);
|
||||
|
||||
// 按状态统计
|
||||
const [statusStats] = await pool.execute(
|
||||
'SELECT status, COUNT(*) as count FROM cattle GROUP BY status'
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
total_cattle: totalResult[0].total,
|
||||
healthy_cattle: healthyResult[0].healthy,
|
||||
sick_cattle: sickResult[0].sick,
|
||||
by_breed: breedStats,
|
||||
by_status: statusStats
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取牛只统计错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取牛只统计失败',
|
||||
code: 'GET_CATTLE_STATS_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
setMiddleware
|
||||
};
|
||||
919
backend/api/routes/finance.js
Normal file
919
backend/api/routes/finance.js
Normal file
@@ -0,0 +1,919 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
// 中间件将在服务器启动时设置
|
||||
let authenticateToken = (req, res, next) => {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '认证中间件未初始化',
|
||||
code: 'AUTH_NOT_INITIALIZED'
|
||||
});
|
||||
};
|
||||
|
||||
let checkPermission = (permission) => {
|
||||
return (req, res, next) => {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '权限中间件未初始化',
|
||||
code: 'PERMISSION_NOT_INITIALIZED'
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
let pool = null;
|
||||
|
||||
// 设置中间件和数据库连接(从主服务器导入)
|
||||
function setMiddleware(auth, permission, dbPool) {
|
||||
authenticateToken = auth;
|
||||
checkPermission = permission;
|
||||
pool = dbPool;
|
||||
}
|
||||
|
||||
// ======================================
|
||||
// 贷款管理相关接口
|
||||
// ======================================
|
||||
|
||||
// 获取贷款申请列表
|
||||
router.get('/loans', authenticateToken, checkPermission('loan_manage'), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
status,
|
||||
loan_type,
|
||||
applicant_id,
|
||||
search
|
||||
} = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockLoans = [
|
||||
{
|
||||
id: 1,
|
||||
applicant_id: 2,
|
||||
applicant_name: '张三',
|
||||
loan_type: 'cattle',
|
||||
loan_amount: 500000.00,
|
||||
interest_rate: 0.0450,
|
||||
term_months: 24,
|
||||
status: 'approved',
|
||||
purpose: '购买西门塔尔牛30头,用于扩大养殖规模',
|
||||
approved_amount: 450000.00,
|
||||
approved_date: '2024-01-15 10:30:00',
|
||||
disbursement_date: '2024-01-20 14:00:00',
|
||||
created_at: '2024-01-10 09:00:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
applicant_id: 3,
|
||||
applicant_name: '李四',
|
||||
loan_type: 'equipment',
|
||||
loan_amount: 300000.00,
|
||||
interest_rate: 0.0520,
|
||||
term_months: 36,
|
||||
status: 'under_review',
|
||||
purpose: '购买饲料加工设备和自动饮水系统',
|
||||
approved_amount: null,
|
||||
approved_date: null,
|
||||
disbursement_date: null,
|
||||
created_at: '2024-01-18 16:45:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
applicant_id: 4,
|
||||
applicant_name: '王五',
|
||||
loan_type: 'operating',
|
||||
loan_amount: 200000.00,
|
||||
interest_rate: 0.0480,
|
||||
term_months: 12,
|
||||
status: 'disbursed',
|
||||
purpose: '购买饲料和兽药,维持日常运营',
|
||||
approved_amount: 200000.00,
|
||||
approved_date: '2024-01-12 11:20:00',
|
||||
disbursement_date: '2024-01-16 09:30:00',
|
||||
created_at: '2024-01-08 14:15:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
loans: mockLoans,
|
||||
pagination: {
|
||||
total: mockLoans.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockLoans.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际查询逻辑
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
const mockLoans = [
|
||||
{
|
||||
id: 1,
|
||||
applicant_name: '张三',
|
||||
loan_type: 'cattle',
|
||||
loan_amount: 500000.00,
|
||||
status: 'approved',
|
||||
purpose: '购买西门塔尔牛30头',
|
||||
created_at: '2024-01-10 09:00:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,返回模拟数据',
|
||||
data: {
|
||||
loans: mockLoans,
|
||||
pagination: {
|
||||
total: mockLoans.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockLoans.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 构建查询条件
|
||||
let whereClause = '1=1';
|
||||
let queryParams = [];
|
||||
|
||||
if (status) {
|
||||
whereClause += ' AND la.status = ?';
|
||||
queryParams.push(status);
|
||||
}
|
||||
|
||||
if (loan_type) {
|
||||
whereClause += ' AND la.loan_type = ?';
|
||||
queryParams.push(loan_type);
|
||||
}
|
||||
|
||||
if (applicant_id) {
|
||||
whereClause += ' AND la.applicant_id = ?';
|
||||
queryParams.push(applicant_id);
|
||||
}
|
||||
|
||||
if (search) {
|
||||
whereClause += ' AND (u.real_name LIKE ? OR la.purpose LIKE ?)';
|
||||
const searchTerm = `%${search}%`;
|
||||
queryParams.push(searchTerm, searchTerm);
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
const [countResult] = await pool.execute(
|
||||
`SELECT COUNT(*) as total
|
||||
FROM loan_applications la
|
||||
LEFT JOIN users u ON la.applicant_id = u.id
|
||||
WHERE ${whereClause}`,
|
||||
queryParams
|
||||
);
|
||||
const total = countResult[0].total;
|
||||
|
||||
// 获取贷款申请列表
|
||||
const [loans] = await pool.execute(
|
||||
`SELECT la.*, u.real_name as applicant_name, u.phone as applicant_phone,
|
||||
rv.real_name as reviewer_name
|
||||
FROM loan_applications la
|
||||
LEFT JOIN users u ON la.applicant_id = u.id
|
||||
LEFT JOIN users rv ON la.reviewer_id = rv.id
|
||||
WHERE ${whereClause}
|
||||
ORDER BY la.created_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...queryParams, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
loans,
|
||||
pagination: {
|
||||
total,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取贷款申请列表错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取贷款申请列表失败',
|
||||
code: 'GET_LOANS_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取贷款申请详情
|
||||
router.get('/loans/:id', authenticateToken, checkPermission('loan_manage'), async (req, res) => {
|
||||
try {
|
||||
const loanId = req.params.id;
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取贷款详情
|
||||
const [loans] = await pool.execute(
|
||||
`SELECT la.*, u.real_name as applicant_name, u.phone as applicant_phone, u.email as applicant_email,
|
||||
rv.real_name as reviewer_name
|
||||
FROM loan_applications la
|
||||
LEFT JOIN users u ON la.applicant_id = u.id
|
||||
LEFT JOIN users rv ON la.reviewer_id = rv.id
|
||||
WHERE la.id = ?`,
|
||||
[loanId]
|
||||
);
|
||||
|
||||
if (loans.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '贷款申请不存在',
|
||||
code: 'LOAN_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
const loan = loans[0];
|
||||
|
||||
// 如果有质押牛只,获取牛只信息
|
||||
let cattleInfo = [];
|
||||
if (loan.cattle_ids) {
|
||||
try {
|
||||
const cattleIds = JSON.parse(loan.cattle_ids);
|
||||
if (cattleIds && cattleIds.length > 0) {
|
||||
const [cattle] = await pool.execute(
|
||||
`SELECT id, ear_tag, name, breed, weight, health_status
|
||||
FROM cattle
|
||||
WHERE id IN (${cattleIds.map(() => '?').join(',')})`,
|
||||
cattleIds
|
||||
);
|
||||
cattleInfo = cattle;
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.warn('解析质押牛只ID失败:', parseError);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
loan,
|
||||
cattle_collateral: cattleInfo
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取贷款详情错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取贷款详情失败',
|
||||
code: 'GET_LOAN_DETAIL_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 创建贷款申请
|
||||
router.post('/loans', authenticateToken, checkPermission('loan_manage'), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
applicant_id,
|
||||
loan_type,
|
||||
cattle_ids,
|
||||
loan_amount,
|
||||
interest_rate,
|
||||
term_months,
|
||||
purpose,
|
||||
repayment_method,
|
||||
guarantee_type
|
||||
} = req.body;
|
||||
|
||||
// 输入验证
|
||||
if (!applicant_id || !loan_type || !loan_amount || !purpose) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '申请人、贷款类型、贷款金额和用途为必填项',
|
||||
code: 'MISSING_REQUIRED_FIELDS'
|
||||
});
|
||||
}
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证申请人是否存在
|
||||
const [users] = await pool.execute('SELECT id FROM users WHERE id = ?', [applicant_id]);
|
||||
if (users.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '申请人不存在',
|
||||
code: 'APPLICANT_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
// 插入贷款申请
|
||||
const [result] = await pool.execute(
|
||||
`INSERT INTO loan_applications
|
||||
(applicant_id, loan_type, cattle_ids, loan_amount, interest_rate, term_months,
|
||||
purpose, repayment_method, guarantee_type, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'submitted')`,
|
||||
[
|
||||
applicant_id,
|
||||
loan_type,
|
||||
cattle_ids ? JSON.stringify(cattle_ids) : null,
|
||||
loan_amount,
|
||||
interest_rate || null,
|
||||
term_months || null,
|
||||
purpose,
|
||||
repayment_method || null,
|
||||
guarantee_type || null
|
||||
]
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '贷款申请创建成功',
|
||||
data: {
|
||||
loan_id: result.insertId,
|
||||
applicant_id,
|
||||
loan_amount,
|
||||
status: 'submitted'
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建贷款申请错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建贷款申请失败',
|
||||
code: 'CREATE_LOAN_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 审批贷款申请
|
||||
router.put('/loans/:id/review', authenticateToken, checkPermission('loan_manage'), async (req, res) => {
|
||||
try {
|
||||
const loanId = req.params.id;
|
||||
const {
|
||||
status,
|
||||
approved_amount,
|
||||
review_notes
|
||||
} = req.body;
|
||||
|
||||
// 输入验证
|
||||
if (!status || !['approved', 'rejected'].includes(status)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '审批状态必须是 approved 或 rejected',
|
||||
code: 'INVALID_STATUS'
|
||||
});
|
||||
}
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查贷款申请是否存在
|
||||
const [loans] = await pool.execute('SELECT id, status FROM loan_applications WHERE id = ?', [loanId]);
|
||||
if (loans.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '贷款申请不存在',
|
||||
code: 'LOAN_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查当前状态是否允许审批
|
||||
if (!['submitted', 'under_review'].includes(loans[0].status)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '当前状态不允许审批',
|
||||
code: 'INVALID_CURRENT_STATUS'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新审批结果
|
||||
await pool.execute(
|
||||
`UPDATE loan_applications
|
||||
SET status = ?, approved_amount = ?, review_notes = ?, reviewer_id = ?, approved_date = CURRENT_TIMESTAMP
|
||||
WHERE id = ?`,
|
||||
[status, approved_amount || null, review_notes || null, req.user.userId, loanId]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `贷款申请${status === 'approved' ? '批准' : '拒绝'}成功`,
|
||||
data: {
|
||||
loan_id: loanId,
|
||||
status,
|
||||
approved_amount: approved_amount || null
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('审批贷款申请错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '审批贷款申请失败',
|
||||
code: 'REVIEW_LOAN_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ======================================
|
||||
// 保险管理相关接口
|
||||
// ======================================
|
||||
|
||||
// 获取保险申请列表
|
||||
router.get('/insurance', authenticateToken, checkPermission('insurance_manage'), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
status,
|
||||
insurance_type,
|
||||
applicant_id,
|
||||
search
|
||||
} = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockInsurance = [
|
||||
{
|
||||
id: 1,
|
||||
applicant_id: 2,
|
||||
applicant_name: '张三',
|
||||
insurance_type: 'cattle_death',
|
||||
policy_number: 'INS202401001',
|
||||
insured_amount: 300000.00,
|
||||
premium: 12000.00,
|
||||
start_date: '2024-02-01',
|
||||
end_date: '2025-01-31',
|
||||
status: 'active',
|
||||
created_at: '2024-01-20 10:00:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
applicant_id: 3,
|
||||
applicant_name: '李四',
|
||||
insurance_type: 'cattle_health',
|
||||
policy_number: 'INS202401002',
|
||||
insured_amount: 250000.00,
|
||||
premium: 8750.00,
|
||||
start_date: '2024-02-15',
|
||||
end_date: '2025-02-14',
|
||||
status: 'underwriting',
|
||||
created_at: '2024-01-25 14:30:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
applicant_id: 4,
|
||||
applicant_name: '王五',
|
||||
insurance_type: 'cattle_theft',
|
||||
policy_number: null,
|
||||
insured_amount: 180000.00,
|
||||
premium: 5400.00,
|
||||
start_date: null,
|
||||
end_date: null,
|
||||
status: 'applied',
|
||||
created_at: '2024-01-28 09:15:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
insurance: mockInsurance,
|
||||
pagination: {
|
||||
total: mockInsurance.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockInsurance.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
const mockInsurance = [
|
||||
{
|
||||
id: 1,
|
||||
applicant_name: '张三',
|
||||
insurance_type: 'cattle_death',
|
||||
insured_amount: 300000.00,
|
||||
status: 'active',
|
||||
created_at: '2024-01-20 10:00:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,返回模拟数据',
|
||||
data: {
|
||||
insurance: mockInsurance,
|
||||
pagination: {
|
||||
total: mockInsurance.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockInsurance.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 实际数据库查询逻辑
|
||||
let whereClause = '1=1';
|
||||
let queryParams = [];
|
||||
|
||||
if (status) {
|
||||
whereClause += ' AND ia.status = ?';
|
||||
queryParams.push(status);
|
||||
}
|
||||
|
||||
if (insurance_type) {
|
||||
whereClause += ' AND ia.insurance_type = ?';
|
||||
queryParams.push(insurance_type);
|
||||
}
|
||||
|
||||
if (applicant_id) {
|
||||
whereClause += ' AND ia.applicant_id = ?';
|
||||
queryParams.push(applicant_id);
|
||||
}
|
||||
|
||||
if (search) {
|
||||
whereClause += ' AND (u.real_name LIKE ? OR ia.policy_number LIKE ?)';
|
||||
const searchTerm = `%${search}%`;
|
||||
queryParams.push(searchTerm, searchTerm);
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
const [countResult] = await pool.execute(
|
||||
`SELECT COUNT(*) as total
|
||||
FROM insurance_applications ia
|
||||
LEFT JOIN users u ON ia.applicant_id = u.id
|
||||
WHERE ${whereClause}`,
|
||||
queryParams
|
||||
);
|
||||
const total = countResult[0].total;
|
||||
|
||||
// 获取保险申请列表
|
||||
const [insurance] = await pool.execute(
|
||||
`SELECT ia.*, u.real_name as applicant_name, u.phone as applicant_phone,
|
||||
uw.real_name as underwriter_name
|
||||
FROM insurance_applications ia
|
||||
LEFT JOIN users u ON ia.applicant_id = u.id
|
||||
LEFT JOIN users uw ON ia.underwriter_id = uw.id
|
||||
WHERE ${whereClause}
|
||||
ORDER BY ia.created_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...queryParams, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
insurance,
|
||||
pagination: {
|
||||
total,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取保险申请列表错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取保险申请列表失败',
|
||||
code: 'GET_INSURANCE_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取理赔申请列表
|
||||
router.get('/claims', authenticateToken, checkPermission('insurance_manage'), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
status,
|
||||
incident_type,
|
||||
insurance_id
|
||||
} = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockClaims = [
|
||||
{
|
||||
id: 1,
|
||||
insurance_id: 1,
|
||||
policy_number: 'INS202401001',
|
||||
applicant_name: '张三',
|
||||
claim_amount: 50000.00,
|
||||
incident_date: '2024-01-30',
|
||||
incident_type: 'illness',
|
||||
description: '牛只突发疾病,经兽医诊断为传染性疾病',
|
||||
status: 'under_review',
|
||||
submitted_at: '2024-02-01 09:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
insurance_id: 1,
|
||||
policy_number: 'INS202401001',
|
||||
applicant_name: '张三',
|
||||
claim_amount: 25000.00,
|
||||
incident_date: '2024-02-10',
|
||||
incident_type: 'accident',
|
||||
description: '牛只在放牧过程中意外受伤',
|
||||
status: 'approved',
|
||||
approved_amount: 22000.00,
|
||||
submitted_at: '2024-02-11 14:20:00',
|
||||
approved_at: '2024-02-15 10:45:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
claims: mockClaims,
|
||||
pagination: {
|
||||
total: mockClaims.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockClaims.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
const mockClaims = [
|
||||
{
|
||||
id: 1,
|
||||
applicant_name: '张三',
|
||||
claim_amount: 50000.00,
|
||||
incident_type: 'illness',
|
||||
status: 'under_review'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,返回模拟数据',
|
||||
data: {
|
||||
claims: mockClaims,
|
||||
pagination: {
|
||||
total: mockClaims.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockClaims.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 实际数据库查询
|
||||
let whereClause = '1=1';
|
||||
let queryParams = [];
|
||||
|
||||
if (status) {
|
||||
whereClause += ' AND c.status = ?';
|
||||
queryParams.push(status);
|
||||
}
|
||||
|
||||
if (incident_type) {
|
||||
whereClause += ' AND c.incident_type = ?';
|
||||
queryParams.push(incident_type);
|
||||
}
|
||||
|
||||
if (insurance_id) {
|
||||
whereClause += ' AND c.insurance_id = ?';
|
||||
queryParams.push(insurance_id);
|
||||
}
|
||||
|
||||
const [countResult] = await pool.execute(
|
||||
`SELECT COUNT(*) as total
|
||||
FROM claims c
|
||||
LEFT JOIN insurance_applications ia ON c.insurance_id = ia.id
|
||||
LEFT JOIN users u ON c.applicant_id = u.id
|
||||
WHERE ${whereClause}`,
|
||||
queryParams
|
||||
);
|
||||
const total = countResult[0].total;
|
||||
|
||||
const [claims] = await pool.execute(
|
||||
`SELECT c.*, ia.policy_number, u.real_name as applicant_name,
|
||||
rv.real_name as reviewer_name
|
||||
FROM claims c
|
||||
LEFT JOIN insurance_applications ia ON c.insurance_id = ia.id
|
||||
LEFT JOIN users u ON c.applicant_id = u.id
|
||||
LEFT JOIN users rv ON c.reviewer_id = rv.id
|
||||
WHERE ${whereClause}
|
||||
ORDER BY c.submitted_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...queryParams, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
claims,
|
||||
pagination: {
|
||||
total,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取理赔申请列表错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取理赔申请列表失败',
|
||||
code: 'GET_CLAIMS_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取金融服务统计信息
|
||||
router.get('/stats/overview', authenticateToken, checkPermission('data_view'), async (req, res) => {
|
||||
try {
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockStats = {
|
||||
loans: {
|
||||
total_applications: 156,
|
||||
approved_loans: 89,
|
||||
pending_review: 23,
|
||||
total_amount: 45600000.00,
|
||||
approved_amount: 32800000.00
|
||||
},
|
||||
insurance: {
|
||||
total_policies: 234,
|
||||
active_policies: 198,
|
||||
total_coverage: 78500000.00,
|
||||
total_claims: 45,
|
||||
paid_claims: 32,
|
||||
pending_claims: 8
|
||||
},
|
||||
risk_analysis: {
|
||||
default_rate: 0.025,
|
||||
claim_rate: 0.165,
|
||||
average_loan_amount: 368539.32,
|
||||
average_premium: 15420.50
|
||||
}
|
||||
};
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: mockStats
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
const mockStats = {
|
||||
loans: {
|
||||
total_applications: 156,
|
||||
approved_loans: 89,
|
||||
pending_review: 23,
|
||||
total_amount: 45600000.00,
|
||||
approved_amount: 32800000.00
|
||||
},
|
||||
insurance: {
|
||||
total_policies: 234,
|
||||
active_policies: 198,
|
||||
total_coverage: 78500000.00
|
||||
}
|
||||
};
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,返回模拟数据',
|
||||
data: mockStats
|
||||
});
|
||||
}
|
||||
|
||||
// 贷款统计
|
||||
const [loanStats] = await pool.execute(`
|
||||
SELECT
|
||||
COUNT(*) as total_applications,
|
||||
COUNT(CASE WHEN status = 'approved' THEN 1 END) as approved_loans,
|
||||
COUNT(CASE WHEN status IN ('submitted', 'under_review') THEN 1 END) as pending_review,
|
||||
SUM(loan_amount) as total_amount,
|
||||
SUM(CASE WHEN status = 'approved' THEN approved_amount ELSE 0 END) as approved_amount
|
||||
FROM loan_applications
|
||||
`);
|
||||
|
||||
// 保险统计
|
||||
const [insuranceStats] = await pool.execute(`
|
||||
SELECT
|
||||
COUNT(*) as total_policies,
|
||||
COUNT(CASE WHEN status = 'active' THEN 1 END) as active_policies,
|
||||
SUM(insured_amount) as total_coverage
|
||||
FROM insurance_applications
|
||||
`);
|
||||
|
||||
// 理赔统计
|
||||
const [claimStats] = await pool.execute(`
|
||||
SELECT
|
||||
COUNT(*) as total_claims,
|
||||
COUNT(CASE WHEN status = 'paid' THEN 1 END) as paid_claims,
|
||||
COUNT(CASE WHEN status IN ('submitted', 'under_review') THEN 1 END) as pending_claims
|
||||
FROM claims
|
||||
`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
loans: loanStats[0],
|
||||
insurance: {
|
||||
...insuranceStats[0],
|
||||
...claimStats[0]
|
||||
},
|
||||
risk_analysis: {
|
||||
default_rate: 0.025, // 这里可以添加更复杂的计算逻辑
|
||||
claim_rate: claimStats[0].total_claims / (insuranceStats[0].total_policies || 1),
|
||||
average_loan_amount: loanStats[0].total_amount / (loanStats[0].total_applications || 1),
|
||||
average_premium: 15420.50 // 可以从数据库计算
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取金融服务统计错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取金融服务统计失败',
|
||||
code: 'GET_FINANCE_STATS_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
setMiddleware
|
||||
};
|
||||
657
backend/api/routes/government.js
Normal file
657
backend/api/routes/government.js
Normal file
@@ -0,0 +1,657 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
// 中间件将在服务器启动时设置
|
||||
let authenticateToken = (req, res, next) => {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '认证中间件未初始化',
|
||||
code: 'AUTH_NOT_INITIALIZED'
|
||||
});
|
||||
};
|
||||
|
||||
let checkPermission = (permission) => {
|
||||
return (req, res, next) => {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '权限中间件未初始化',
|
||||
code: 'PERMISSION_NOT_INITIALIZED'
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
let pool = null;
|
||||
|
||||
// 设置中间件和数据库连接(从主服务器导入)
|
||||
function setMiddleware(auth, permission, dbPool) {
|
||||
authenticateToken = auth;
|
||||
checkPermission = permission;
|
||||
pool = dbPool;
|
||||
}
|
||||
|
||||
// ======================================
|
||||
// 养殖监管相关接口
|
||||
// ======================================
|
||||
|
||||
// 获取牧场监管信息
|
||||
router.get('/farms/supervision', authenticateToken, checkPermission('government_supervision'), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
region,
|
||||
compliance_status,
|
||||
inspection_status,
|
||||
search
|
||||
} = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockFarms = [
|
||||
{
|
||||
id: 1,
|
||||
farm_name: '锡林浩特市第一牧场',
|
||||
owner_name: '张三',
|
||||
owner_phone: '13900000002',
|
||||
region: '锡林浩特市',
|
||||
registration_number: 'REG2024001',
|
||||
cattle_count: 240,
|
||||
farm_area: 150.5,
|
||||
compliance_status: 'compliant',
|
||||
last_inspection_date: '2024-01-15',
|
||||
next_inspection_date: '2024-04-15',
|
||||
inspector_name: '政府检查员A',
|
||||
compliance_score: 95,
|
||||
violation_count: 0,
|
||||
environmental_rating: 'A',
|
||||
safety_rating: 'A',
|
||||
notes: '管理规范,设施完善',
|
||||
created_at: '2023-06-15 10:00:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
farm_name: '东乌旗牧场A',
|
||||
owner_name: '李四',
|
||||
owner_phone: '13900000003',
|
||||
region: '东乌旗',
|
||||
registration_number: 'REG2024002',
|
||||
cattle_count: 180,
|
||||
farm_area: 120.3,
|
||||
compliance_status: 'warning',
|
||||
last_inspection_date: '2024-01-10',
|
||||
next_inspection_date: '2024-03-10',
|
||||
inspector_name: '政府检查员B',
|
||||
compliance_score: 78,
|
||||
violation_count: 2,
|
||||
environmental_rating: 'B',
|
||||
safety_rating: 'A',
|
||||
notes: '存在轻微环境问题,需要改进',
|
||||
created_at: '2023-08-20 14:30:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
farm_name: '西乌旗生态牧场',
|
||||
owner_name: '王五',
|
||||
owner_phone: '13900000004',
|
||||
region: '西乌旗',
|
||||
registration_number: 'REG2024003',
|
||||
cattle_count: 320,
|
||||
farm_area: 200.8,
|
||||
compliance_status: 'excellent',
|
||||
last_inspection_date: '2024-01-20',
|
||||
next_inspection_date: '2024-07-20',
|
||||
inspector_name: '政府检查员C',
|
||||
compliance_score: 98,
|
||||
violation_count: 0,
|
||||
environmental_rating: 'A+',
|
||||
safety_rating: 'A+',
|
||||
notes: '示范牧场,各项指标优秀',
|
||||
created_at: '2023-05-10 09:15:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
farms: mockFarms,
|
||||
pagination: {
|
||||
total: mockFarms.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockFarms.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际查询逻辑...
|
||||
res.json({
|
||||
success: true,
|
||||
message: '政府监管功能开发中',
|
||||
data: { farms: [], pagination: { total: 0, page: parseInt(page), limit: parseInt(limit), pages: 0 } }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取牧场监管信息失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取牧场监管信息失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取检查记录
|
||||
router.get('/inspections', authenticateToken, checkPermission('government_supervision'), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
farm_id,
|
||||
inspector_id,
|
||||
inspection_type,
|
||||
result,
|
||||
start_date,
|
||||
end_date
|
||||
} = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockInspections = [
|
||||
{
|
||||
id: 1,
|
||||
farm_id: 1,
|
||||
farm_name: '锡林浩特市第一牧场',
|
||||
inspector_id: 10,
|
||||
inspector_name: '政府检查员A',
|
||||
inspection_type: 'routine',
|
||||
inspection_date: '2024-01-15',
|
||||
result: 'passed',
|
||||
score: 95,
|
||||
violations: [],
|
||||
improvements: [
|
||||
'建议加强饲料储存管理',
|
||||
'完善消毒记录台账'
|
||||
],
|
||||
next_inspection_date: '2024-04-15',
|
||||
report_url: '/uploads/inspection_reports/INS001.pdf',
|
||||
notes: '整体情况良好,管理规范',
|
||||
created_at: '2024-01-15 14:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
farm_id: 2,
|
||||
farm_name: '东乌旗牧场A',
|
||||
inspector_id: 11,
|
||||
inspector_name: '政府检查员B',
|
||||
inspection_type: 'follow_up',
|
||||
inspection_date: '2024-01-10',
|
||||
result: 'conditional_pass',
|
||||
score: 78,
|
||||
violations: [
|
||||
{
|
||||
type: 'environmental',
|
||||
description: '粪污处理不够及时',
|
||||
severity: 'minor',
|
||||
deadline: '2024-02-10'
|
||||
},
|
||||
{
|
||||
type: 'safety',
|
||||
description: '部分围栏需要维修',
|
||||
severity: 'minor',
|
||||
deadline: '2024-01-25'
|
||||
}
|
||||
],
|
||||
improvements: [
|
||||
'加强粪污处理设施维护',
|
||||
'定期检查围栏安全性',
|
||||
'建立更完善的清洁制度'
|
||||
],
|
||||
next_inspection_date: '2024-03-10',
|
||||
report_url: '/uploads/inspection_reports/INS002.pdf',
|
||||
notes: '存在轻微问题,需要限期整改',
|
||||
created_at: '2024-01-10 16:45:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
inspections: mockInspections,
|
||||
pagination: {
|
||||
total: mockInspections.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockInspections.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际查询逻辑...
|
||||
res.json({
|
||||
success: true,
|
||||
message: '检查记录功能开发中',
|
||||
data: { inspections: [], pagination: { total: 0, page: parseInt(page), limit: parseInt(limit), pages: 0 } }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取检查记录失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取检查记录失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 创建检查记录
|
||||
router.post('/inspections', authenticateToken, checkPermission('government_inspection'), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
farm_id,
|
||||
inspection_type,
|
||||
inspection_date,
|
||||
score,
|
||||
result,
|
||||
violations,
|
||||
improvements,
|
||||
next_inspection_date,
|
||||
notes
|
||||
} = req.body;
|
||||
|
||||
// 验证必需字段
|
||||
if (!farm_id || !inspection_type || !inspection_date || !result) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '缺少必需的字段'
|
||||
});
|
||||
}
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟响应
|
||||
const mockInspection = {
|
||||
id: Math.floor(Math.random() * 1000) + 100,
|
||||
farm_id,
|
||||
inspector_id: req.user.id,
|
||||
inspector_name: req.user.real_name || '检查员',
|
||||
inspection_type,
|
||||
inspection_date,
|
||||
result,
|
||||
score,
|
||||
violations: violations || [],
|
||||
improvements: improvements || [],
|
||||
next_inspection_date,
|
||||
notes,
|
||||
created_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
return res.status(201).json({
|
||||
success: true,
|
||||
message: '检查记录创建成功(模拟数据)',
|
||||
data: mockInspection
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际创建逻辑...
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '检查记录创建功能开发中'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建检查记录失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建检查记录失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ======================================
|
||||
// 质量追溯相关接口
|
||||
// ======================================
|
||||
|
||||
// 获取产品追溯信息
|
||||
router.get('/traceability/:product_id', authenticateToken, checkPermission('quality_trace'), async (req, res) => {
|
||||
try {
|
||||
const { product_id } = req.params;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockTraceability = {
|
||||
product_id,
|
||||
product_name: '优质牛肉',
|
||||
batch_number: 'BATCH2024001',
|
||||
production_date: '2024-01-20',
|
||||
expiry_date: '2024-01-27',
|
||||
origin_info: {
|
||||
farm_id: 1,
|
||||
farm_name: '锡林浩特市第一牧场',
|
||||
farmer_name: '张三',
|
||||
region: '锡林浩特市',
|
||||
coordinates: { lat: 43.946, lng: 116.093 }
|
||||
},
|
||||
cattle_info: {
|
||||
cattle_id: 1,
|
||||
tag_number: 'C001',
|
||||
breed: '西门塔尔牛',
|
||||
birth_date: '2022-01-15',
|
||||
slaughter_date: '2024-01-18',
|
||||
weight: 450,
|
||||
health_records: [
|
||||
{
|
||||
date: '2023-06-15',
|
||||
type: 'vaccination',
|
||||
description: '口蹄疫疫苗接种',
|
||||
veterinarian: '兽医A'
|
||||
},
|
||||
{
|
||||
date: '2023-12-10',
|
||||
type: 'health_check',
|
||||
description: '定期健康检查',
|
||||
result: '健康状况良好',
|
||||
veterinarian: '兽医B'
|
||||
}
|
||||
]
|
||||
},
|
||||
processing_info: {
|
||||
slaughterhouse: '锡林郭勒肉类加工厂',
|
||||
slaughter_date: '2024-01-18',
|
||||
processing_date: '2024-01-19',
|
||||
packaging_date: '2024-01-20',
|
||||
inspector: '质检员A',
|
||||
quality_grade: 'A级',
|
||||
certificates: [
|
||||
'动物检疫合格证',
|
||||
'肉品品质检验合格证',
|
||||
'食品安全检测报告'
|
||||
]
|
||||
},
|
||||
transportation_info: {
|
||||
transport_company: '冷链物流A',
|
||||
departure_time: '2024-01-20 08:00:00',
|
||||
arrival_time: '2024-01-20 14:30:00',
|
||||
temperature_records: [
|
||||
{ time: '08:00', temperature: -2 },
|
||||
{ time: '10:00', temperature: -1.8 },
|
||||
{ time: '12:00', temperature: -2.1 },
|
||||
{ time: '14:00', temperature: -1.9 }
|
||||
],
|
||||
driver: '司机A',
|
||||
vehicle_number: '蒙H12345'
|
||||
},
|
||||
retail_info: {
|
||||
retailer: '锡林浩特超市A',
|
||||
receipt_date: '2024-01-20 15:00:00',
|
||||
sale_date: '2024-01-22 10:30:00',
|
||||
price: 68.00,
|
||||
customer_info: '已匿名化'
|
||||
},
|
||||
quality_reports: [
|
||||
{
|
||||
test_date: '2024-01-19',
|
||||
test_type: '微生物检测',
|
||||
result: '合格',
|
||||
lab: '第三方检测机构A'
|
||||
},
|
||||
{
|
||||
test_date: '2024-01-19',
|
||||
test_type: '重金属检测',
|
||||
result: '合格',
|
||||
lab: '第三方检测机构A'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: mockTraceability
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际查询逻辑...
|
||||
res.json({
|
||||
success: true,
|
||||
message: '产品追溯功能开发中',
|
||||
data: { product_id, message: '开发中' }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取产品追溯信息失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取产品追溯信息失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ======================================
|
||||
// 政策法规相关接口
|
||||
// ======================================
|
||||
|
||||
// 获取政策法规列表
|
||||
router.get('/policies', authenticateToken, checkPermission('policy_view'), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
category,
|
||||
status,
|
||||
search
|
||||
} = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockPolicies = [
|
||||
{
|
||||
id: 1,
|
||||
title: '锡林郭勒盟畜牧业发展扶持政策',
|
||||
category: 'support_policy',
|
||||
content_summary: '为促进畜牧业健康发展,对符合条件的养殖户给予资金补贴和技术支持',
|
||||
publish_date: '2024-01-01',
|
||||
effective_date: '2024-01-01',
|
||||
expiry_date: '2024-12-31',
|
||||
status: 'active',
|
||||
authority: '锡林郭勒盟农牧局',
|
||||
document_url: '/uploads/policies/policy001.pdf',
|
||||
created_at: '2024-01-01 10:00:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '动物疫病防控管理办法',
|
||||
category: 'regulation',
|
||||
content_summary: '规范动物疫病防控工作,确保畜牧业生产安全和公共卫生安全',
|
||||
publish_date: '2023-12-15',
|
||||
effective_date: '2024-01-01',
|
||||
expiry_date: null,
|
||||
status: 'active',
|
||||
authority: '锡林郭勒盟兽医局',
|
||||
document_url: '/uploads/policies/policy002.pdf',
|
||||
created_at: '2023-12-15 14:30:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '草原生态保护补助奖励政策',
|
||||
category: 'subsidy',
|
||||
content_summary: '对实施草原禁牧、草畜平衡的牧户给予生态保护补助奖励',
|
||||
publish_date: '2024-01-10',
|
||||
effective_date: '2024-01-15',
|
||||
expiry_date: '2024-12-31',
|
||||
status: 'active',
|
||||
authority: '锡林郭勒盟林草局',
|
||||
document_url: '/uploads/policies/policy003.pdf',
|
||||
created_at: '2024-01-10 09:15:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
policies: mockPolicies,
|
||||
pagination: {
|
||||
total: mockPolicies.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockPolicies.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际查询逻辑...
|
||||
res.json({
|
||||
success: true,
|
||||
message: '政策法规功能开发中',
|
||||
data: { policies: [], pagination: { total: 0, page: parseInt(page), limit: parseInt(limit), pages: 0 } }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取政策法规列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取政策法规列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ======================================
|
||||
// 统计报告相关接口
|
||||
// ======================================
|
||||
|
||||
// 获取监管统计数据
|
||||
router.get('/statistics', authenticateToken, checkPermission('government_statistics'), async (req, res) => {
|
||||
try {
|
||||
const { period = 'month', region } = req.query;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockStats = {
|
||||
overview: {
|
||||
total_farms: 156,
|
||||
total_cattle: 12850,
|
||||
compliant_farms: 142,
|
||||
warning_farms: 11,
|
||||
violation_farms: 3,
|
||||
compliance_rate: 91.0
|
||||
},
|
||||
regional_distribution: {
|
||||
'锡林浩特市': { farms: 45, cattle: 4200, compliance_rate: 93.3 },
|
||||
'东乌旗': { farms: 38, cattle: 3100, compliance_rate: 89.5 },
|
||||
'西乌旗': { farms: 42, cattle: 3800, compliance_rate: 92.9 },
|
||||
'阿巴嘎旗': { farms: 31, cattle: 1750, compliance_rate: 87.1 }
|
||||
},
|
||||
inspection_summary: {
|
||||
total_inspections: 89,
|
||||
passed: 76,
|
||||
conditional_pass: 8,
|
||||
failed: 5,
|
||||
pending: 0
|
||||
},
|
||||
violation_categories: {
|
||||
environmental: 15,
|
||||
safety: 8,
|
||||
health: 5,
|
||||
documentation: 12
|
||||
},
|
||||
monthly_trend: [
|
||||
{ month: '2023-11', compliance_rate: 88.5, inspections: 28 },
|
||||
{ month: '2023-12', compliance_rate: 89.2, inspections: 32 },
|
||||
{ month: '2024-01', compliance_rate: 91.0, inspections: 29 }
|
||||
]
|
||||
};
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: mockStats
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际统计查询逻辑...
|
||||
res.json({
|
||||
success: true,
|
||||
message: '监管统计功能开发中',
|
||||
data: { overview: { total_farms: 0, total_cattle: 0 } }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取监管统计数据失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取监管统计数据失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 生成监管报告
|
||||
router.post('/reports', authenticateToken, checkPermission('government_report'), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
report_type,
|
||||
period,
|
||||
region,
|
||||
start_date,
|
||||
end_date,
|
||||
format = 'pdf'
|
||||
} = req.body;
|
||||
|
||||
// 验证必需字段
|
||||
if (!report_type || !period) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '缺少必需的字段'
|
||||
});
|
||||
}
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟响应
|
||||
const mockReport = {
|
||||
id: Math.floor(Math.random() * 1000) + 100,
|
||||
report_type,
|
||||
period,
|
||||
region,
|
||||
start_date,
|
||||
end_date,
|
||||
format,
|
||||
status: 'generating',
|
||||
created_by: req.user.id,
|
||||
created_at: new Date().toISOString(),
|
||||
download_url: null,
|
||||
estimated_completion: new Date(Date.now() + 5 * 60 * 1000).toISOString() // 5分钟后
|
||||
};
|
||||
|
||||
return res.status(201).json({
|
||||
success: true,
|
||||
message: '报告生成任务已创建(模拟数据)',
|
||||
data: mockReport
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际报告生成逻辑...
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '报告生成功能开发中'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('生成监管报告失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '生成监管报告失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 导出模块
|
||||
module.exports = {
|
||||
router,
|
||||
setMiddleware
|
||||
};
|
||||
874
backend/api/routes/mall.js
Normal file
874
backend/api/routes/mall.js
Normal file
@@ -0,0 +1,874 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
// 中间件将在服务器启动时设置
|
||||
let authenticateToken = (req, res, next) => {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '认证中间件未初始化',
|
||||
code: 'AUTH_NOT_INITIALIZED'
|
||||
});
|
||||
};
|
||||
|
||||
let checkPermission = (permission) => {
|
||||
return (req, res, next) => {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '权限中间件未初始化',
|
||||
code: 'PERMISSION_NOT_INITIALIZED'
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
let pool = null;
|
||||
|
||||
// 设置中间件和数据库连接(从主服务器导入)
|
||||
function setMiddleware(auth, permission, dbPool) {
|
||||
authenticateToken = auth;
|
||||
checkPermission = permission;
|
||||
pool = dbPool;
|
||||
}
|
||||
|
||||
// ======================================
|
||||
// 商品管理相关接口
|
||||
// ======================================
|
||||
|
||||
// 获取商品列表
|
||||
router.get('/products', async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
category,
|
||||
status,
|
||||
seller_id,
|
||||
price_min,
|
||||
price_max,
|
||||
search,
|
||||
sort_by = 'created_at',
|
||||
sort_order = 'desc'
|
||||
} = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockProducts = [
|
||||
{
|
||||
id: 1,
|
||||
name: '优质牛肉礼盒装',
|
||||
category: 'beef',
|
||||
description: '来自锡林浩特优质牧场的新鲜牛肉,肉质鲜美,营养丰富',
|
||||
price: 268.00,
|
||||
original_price: 298.00,
|
||||
stock: 45,
|
||||
sales_count: 128,
|
||||
status: 'active',
|
||||
seller_id: 2,
|
||||
seller_name: '张三牧场直营店',
|
||||
images: [
|
||||
'/uploads/products/beef_box_1.jpg',
|
||||
'/uploads/products/beef_box_2.jpg'
|
||||
],
|
||||
specifications: {
|
||||
weight: '2kg',
|
||||
packaging: '礼盒装',
|
||||
storage: '冷冻保存',
|
||||
shelf_life: '30天'
|
||||
},
|
||||
origin: '锡林浩特市第一牧场',
|
||||
rating: 4.8,
|
||||
review_count: 56,
|
||||
created_at: '2024-01-15 10:30:00',
|
||||
updated_at: '2024-01-20 14:15:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '有机牛奶',
|
||||
category: 'dairy',
|
||||
description: '纯天然有机牛奶,无添加剂,营养价值高',
|
||||
price: 35.00,
|
||||
original_price: 35.00,
|
||||
stock: 120,
|
||||
sales_count: 89,
|
||||
status: 'active',
|
||||
seller_id: 3,
|
||||
seller_name: '草原乳业',
|
||||
images: [
|
||||
'/uploads/products/milk_1.jpg'
|
||||
],
|
||||
specifications: {
|
||||
volume: '1L',
|
||||
packaging: '利乐包装',
|
||||
storage: '冷藏保存',
|
||||
shelf_life: '7天'
|
||||
},
|
||||
origin: '东乌旗生态牧场',
|
||||
rating: 4.6,
|
||||
review_count: 32,
|
||||
created_at: '2024-01-18 09:45:00',
|
||||
updated_at: '2024-01-22 16:30:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '牛肉干',
|
||||
category: 'snacks',
|
||||
description: '传统工艺制作的牛肉干,口感醇香,营养丰富',
|
||||
price: 68.00,
|
||||
original_price: 78.00,
|
||||
stock: 0,
|
||||
sales_count: 245,
|
||||
status: 'out_of_stock',
|
||||
seller_id: 4,
|
||||
seller_name: '草原食品厂',
|
||||
images: [
|
||||
'/uploads/products/jerky_1.jpg',
|
||||
'/uploads/products/jerky_2.jpg'
|
||||
],
|
||||
specifications: {
|
||||
weight: '500g',
|
||||
packaging: '真空包装',
|
||||
storage: '常温保存',
|
||||
shelf_life: '180天'
|
||||
},
|
||||
origin: '西乌旗牧场',
|
||||
rating: 4.9,
|
||||
review_count: 78,
|
||||
created_at: '2024-01-10 14:20:00',
|
||||
updated_at: '2024-01-25 11:40:00'
|
||||
}
|
||||
];
|
||||
|
||||
// 根据查询条件过滤
|
||||
let filteredProducts = mockProducts;
|
||||
|
||||
if (category) {
|
||||
filteredProducts = filteredProducts.filter(p => p.category === category);
|
||||
}
|
||||
|
||||
if (status) {
|
||||
filteredProducts = filteredProducts.filter(p => p.status === status);
|
||||
}
|
||||
|
||||
if (search) {
|
||||
const searchLower = search.toLowerCase();
|
||||
filteredProducts = filteredProducts.filter(p =>
|
||||
p.name.toLowerCase().includes(searchLower) ||
|
||||
p.description.toLowerCase().includes(searchLower)
|
||||
);
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
products: filteredProducts.slice(offset, offset + parseInt(limit)),
|
||||
pagination: {
|
||||
total: filteredProducts.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(filteredProducts.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际查询逻辑
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
const mockProducts = [
|
||||
{
|
||||
id: 1,
|
||||
name: '优质牛肉礼盒装',
|
||||
category: 'beef',
|
||||
price: 268.00,
|
||||
stock: 45,
|
||||
status: 'active',
|
||||
seller_name: '张三牧场直营店',
|
||||
created_at: '2024-01-15 10:30:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,返回模拟数据',
|
||||
data: {
|
||||
products: mockProducts,
|
||||
pagination: {
|
||||
total: mockProducts.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockProducts.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '商品列表功能开发中',
|
||||
data: { products: [], pagination: { total: 0, page: parseInt(page), limit: parseInt(limit), pages: 0 } }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取商品列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取商品列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取商品详情
|
||||
router.get('/products/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockProduct = {
|
||||
id: parseInt(id),
|
||||
name: '优质牛肉礼盒装',
|
||||
category: 'beef',
|
||||
description: '来自锡林浩特优质牧场的新鲜牛肉,采用传统草饲方式饲养,肉质鲜美,营养丰富,是您餐桌上的不二选择。',
|
||||
price: 268.00,
|
||||
original_price: 298.00,
|
||||
stock: 45,
|
||||
sales_count: 128,
|
||||
status: 'active',
|
||||
seller_id: 2,
|
||||
seller_name: '张三牧场直营店',
|
||||
seller_rating: 4.7,
|
||||
images: [
|
||||
'/uploads/products/beef_box_1.jpg',
|
||||
'/uploads/products/beef_box_2.jpg',
|
||||
'/uploads/products/beef_box_3.jpg'
|
||||
],
|
||||
specifications: {
|
||||
weight: '2kg',
|
||||
packaging: '礼盒装',
|
||||
storage: '冷冻保存',
|
||||
shelf_life: '30天',
|
||||
certification: ['有机认证', '质量安全认证']
|
||||
},
|
||||
origin_detail: {
|
||||
farm_name: '锡林浩特市第一牧场',
|
||||
farm_address: '锡林浩特市郊区',
|
||||
cattle_breed: '西门塔尔牛',
|
||||
feeding_method: '天然草饲',
|
||||
slaughter_date: '2024-01-12'
|
||||
},
|
||||
nutritional_info: {
|
||||
protein: '20.1g/100g',
|
||||
fat: '15.2g/100g',
|
||||
calories: '210kcal/100g',
|
||||
iron: '3.2mg/100g'
|
||||
},
|
||||
rating: 4.8,
|
||||
review_count: 56,
|
||||
reviews: [
|
||||
{
|
||||
id: 1,
|
||||
user_name: '李女士',
|
||||
rating: 5,
|
||||
content: '肉质非常好,很新鲜,包装也很精美,送人很有面子!',
|
||||
images: ['/uploads/reviews/review_1.jpg'],
|
||||
created_at: '2024-01-22 10:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
user_name: '王先生',
|
||||
rating: 5,
|
||||
content: '味道正宗,口感很好,下次还会再买的',
|
||||
images: [],
|
||||
created_at: '2024-01-20 15:45:00'
|
||||
}
|
||||
],
|
||||
shipping_info: {
|
||||
free_shipping_threshold: 200,
|
||||
shipping_fee: 0,
|
||||
estimated_delivery: '2-3个工作日',
|
||||
shipping_areas: ['锡林郭勒盟', '呼和浩特市', '包头市']
|
||||
},
|
||||
return_policy: {
|
||||
return_days: 7,
|
||||
return_conditions: '商品质量问题支持退换货',
|
||||
return_fee: '免费'
|
||||
},
|
||||
created_at: '2024-01-15 10:30:00',
|
||||
updated_at: '2024-01-20 14:15:00'
|
||||
};
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: mockProduct
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际查询逻辑
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,返回模拟数据',
|
||||
data: {
|
||||
id: parseInt(id),
|
||||
name: '优质牛肉礼盒装',
|
||||
price: 268.00,
|
||||
status: 'active'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '商品详情功能开发中',
|
||||
data: { id: parseInt(id), message: '开发中' }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取商品详情失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取商品详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 创建商品(商家)
|
||||
router.post('/products', authenticateToken, checkPermission('product_create'), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
name,
|
||||
category,
|
||||
description,
|
||||
price,
|
||||
original_price,
|
||||
stock,
|
||||
images,
|
||||
specifications,
|
||||
origin
|
||||
} = req.body;
|
||||
|
||||
// 验证必需字段
|
||||
if (!name || !category || !price || !stock) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '缺少必需的字段'
|
||||
});
|
||||
}
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟响应
|
||||
const mockProduct = {
|
||||
id: Math.floor(Math.random() * 1000) + 100,
|
||||
name,
|
||||
category,
|
||||
description,
|
||||
price,
|
||||
original_price: original_price || price,
|
||||
stock,
|
||||
sales_count: 0,
|
||||
status: 'pending_review',
|
||||
seller_id: req.user?.id || 1,
|
||||
seller_name: req.user?.real_name || '商家',
|
||||
images: images || [],
|
||||
specifications: specifications || {},
|
||||
origin,
|
||||
rating: 0,
|
||||
review_count: 0,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
return res.status(201).json({
|
||||
success: true,
|
||||
message: '商品创建成功,等待审核(模拟数据)',
|
||||
data: mockProduct
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际创建逻辑
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
return res.status(201).json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,模拟创建成功',
|
||||
data: {
|
||||
id: Math.floor(Math.random() * 1000) + 100,
|
||||
name,
|
||||
status: 'pending_review',
|
||||
created_at: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '商品创建功能开发中'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建商品失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建商品失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ======================================
|
||||
// 订单管理相关接口
|
||||
// ======================================
|
||||
|
||||
// 获取订单列表
|
||||
router.get('/orders', authenticateToken, checkPermission('order_view'), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
status,
|
||||
user_id,
|
||||
start_date,
|
||||
end_date,
|
||||
search
|
||||
} = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockOrders = [
|
||||
{
|
||||
id: 1,
|
||||
order_number: 'ORD202401001',
|
||||
user_id: 5,
|
||||
user_name: '赵六',
|
||||
total_amount: 536.00,
|
||||
discount_amount: 30.00,
|
||||
shipping_fee: 0.00,
|
||||
final_amount: 506.00,
|
||||
status: 'delivered',
|
||||
payment_status: 'paid',
|
||||
payment_method: 'wechat_pay',
|
||||
shipping_address: '呼和浩特市新城区xxx街道xxx号',
|
||||
shipping_phone: '13900000005',
|
||||
tracking_number: 'SF1234567890',
|
||||
items: [
|
||||
{
|
||||
product_id: 1,
|
||||
product_name: '优质牛肉礼盒装',
|
||||
quantity: 2,
|
||||
unit_price: 268.00,
|
||||
total_price: 536.00
|
||||
}
|
||||
],
|
||||
order_date: '2024-01-20 10:30:00',
|
||||
payment_date: '2024-01-20 10:35:00',
|
||||
shipping_date: '2024-01-21 08:00:00',
|
||||
delivery_date: '2024-01-23 15:30:00',
|
||||
created_at: '2024-01-20 10:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
order_number: 'ORD202401002',
|
||||
user_id: 6,
|
||||
user_name: '钱七',
|
||||
total_amount: 210.00,
|
||||
discount_amount: 0.00,
|
||||
shipping_fee: 15.00,
|
||||
final_amount: 225.00,
|
||||
status: 'shipping',
|
||||
payment_status: 'paid',
|
||||
payment_method: 'alipay',
|
||||
shipping_address: '包头市昆都仑区xxx路xxx号',
|
||||
shipping_phone: '13900000006',
|
||||
tracking_number: 'YTO0987654321',
|
||||
items: [
|
||||
{
|
||||
product_id: 2,
|
||||
product_name: '有机牛奶',
|
||||
quantity: 6,
|
||||
unit_price: 35.00,
|
||||
total_price: 210.00
|
||||
}
|
||||
],
|
||||
order_date: '2024-01-22 14:20:00',
|
||||
payment_date: '2024-01-22 14:25:00',
|
||||
shipping_date: '2024-01-23 09:15:00',
|
||||
delivery_date: null,
|
||||
created_at: '2024-01-22 14:20:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
orders: mockOrders,
|
||||
pagination: {
|
||||
total: mockOrders.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockOrders.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际查询逻辑
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
const mockOrders = [
|
||||
{
|
||||
id: 1,
|
||||
order_number: 'ORD202401001',
|
||||
user_name: '赵六',
|
||||
total_amount: 506.00,
|
||||
status: 'delivered',
|
||||
created_at: '2024-01-20 10:30:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,返回模拟数据',
|
||||
data: {
|
||||
orders: mockOrders,
|
||||
pagination: {
|
||||
total: mockOrders.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockOrders.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '订单列表功能开发中',
|
||||
data: { orders: [], pagination: { total: 0, page: parseInt(page), limit: parseInt(limit), pages: 0 } }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取订单列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取订单列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 创建订单
|
||||
router.post('/orders', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
items,
|
||||
shipping_address,
|
||||
shipping_phone,
|
||||
shipping_name,
|
||||
payment_method,
|
||||
coupon_code,
|
||||
notes
|
||||
} = req.body;
|
||||
|
||||
// 验证必需字段
|
||||
if (!items || !Array.isArray(items) || items.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '订单商品不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
if (!shipping_address || !shipping_phone || !shipping_name) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '收货信息不完整'
|
||||
});
|
||||
}
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟响应
|
||||
const mockOrder = {
|
||||
id: Math.floor(Math.random() * 1000) + 100,
|
||||
order_number: `ORD${new Date().getFullYear()}${String(Date.now()).slice(-8)}`,
|
||||
user_id: req.user?.id || 1,
|
||||
items,
|
||||
total_amount: items.reduce((sum, item) => sum + (item.quantity * item.unit_price), 0),
|
||||
discount_amount: 0,
|
||||
shipping_fee: 0,
|
||||
final_amount: items.reduce((sum, item) => sum + (item.quantity * item.unit_price), 0),
|
||||
status: 'pending_payment',
|
||||
payment_status: 'pending',
|
||||
payment_method,
|
||||
shipping_address,
|
||||
shipping_phone,
|
||||
shipping_name,
|
||||
notes,
|
||||
created_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
return res.status(201).json({
|
||||
success: true,
|
||||
message: '订单创建成功(模拟数据)',
|
||||
data: mockOrder
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际创建逻辑
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
return res.status(201).json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,模拟创建成功',
|
||||
data: {
|
||||
id: Math.floor(Math.random() * 1000) + 100,
|
||||
order_number: `ORD${new Date().getFullYear()}${String(Date.now()).slice(-8)}`,
|
||||
status: 'pending_payment',
|
||||
created_at: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '订单创建功能开发中'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建订单失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建订单失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ======================================
|
||||
// 商品评价相关接口
|
||||
// ======================================
|
||||
|
||||
// 获取商品评价列表
|
||||
router.get('/products/:product_id/reviews', async (req, res) => {
|
||||
try {
|
||||
const { product_id } = req.params;
|
||||
const { page = 1, limit = 10, rating } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockReviews = [
|
||||
{
|
||||
id: 1,
|
||||
product_id: parseInt(product_id),
|
||||
user_id: 5,
|
||||
user_name: '赵六',
|
||||
user_avatar: '/uploads/avatars/user5.jpg',
|
||||
order_id: 1,
|
||||
rating: 5,
|
||||
content: '肉质非常好,很新鲜,包装也很精美,送人很有面子!家人都很满意,下次还会购买的。',
|
||||
images: [
|
||||
'/uploads/reviews/review_1_1.jpg',
|
||||
'/uploads/reviews/review_1_2.jpg'
|
||||
],
|
||||
helpful_count: 12,
|
||||
reply: {
|
||||
content: '感谢您的好评,我们会继续努力提供优质的产品!',
|
||||
reply_date: '2024-01-23 09:15:00'
|
||||
},
|
||||
created_at: '2024-01-22 10:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
product_id: parseInt(product_id),
|
||||
user_id: 6,
|
||||
user_name: '钱七',
|
||||
user_avatar: '/uploads/avatars/user6.jpg',
|
||||
order_id: 2,
|
||||
rating: 4,
|
||||
content: '味道正宗,口感很好,就是价格稍微有点贵',
|
||||
images: [],
|
||||
helpful_count: 8,
|
||||
reply: null,
|
||||
created_at: '2024-01-20 15:45:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
reviews: mockReviews,
|
||||
pagination: {
|
||||
total: mockReviews.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockReviews.length / limit)
|
||||
},
|
||||
summary: {
|
||||
average_rating: 4.8,
|
||||
total_reviews: mockReviews.length,
|
||||
rating_distribution: {
|
||||
5: 56,
|
||||
4: 12,
|
||||
3: 3,
|
||||
2: 1,
|
||||
1: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际查询逻辑
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
const mockReviews = [
|
||||
{
|
||||
id: 1,
|
||||
user_name: '赵六',
|
||||
rating: 5,
|
||||
content: '肉质非常好,很新鲜',
|
||||
created_at: '2024-01-22 10:30:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,返回模拟数据',
|
||||
data: {
|
||||
reviews: mockReviews,
|
||||
pagination: {
|
||||
total: mockReviews.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockReviews.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '商品评价功能开发中',
|
||||
data: { reviews: [], pagination: { total: 0, page: parseInt(page), limit: parseInt(limit), pages: 0 } }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取商品评价失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取商品评价失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ======================================
|
||||
// 商城统计相关接口
|
||||
// ======================================
|
||||
|
||||
// 获取商城统计数据
|
||||
router.get('/statistics', authenticateToken, checkPermission('mall_statistics'), async (req, res) => {
|
||||
try {
|
||||
const { period = 'month' } = req.query;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockStats = {
|
||||
overview: {
|
||||
total_products: 156,
|
||||
active_products: 142,
|
||||
total_orders: 1284,
|
||||
total_revenue: 2580000.00,
|
||||
total_users: 8456,
|
||||
active_sellers: 28
|
||||
},
|
||||
sales_trend: [
|
||||
{ date: '2024-01-15', orders: 45, revenue: 28500.00 },
|
||||
{ date: '2024-01-16', orders: 52, revenue: 31200.00 },
|
||||
{ date: '2024-01-17', orders: 48, revenue: 29800.00 },
|
||||
{ date: '2024-01-18', orders: 61, revenue: 38200.00 },
|
||||
{ date: '2024-01-19', orders: 55, revenue: 33500.00 },
|
||||
{ date: '2024-01-20', orders: 68, revenue: 42600.00 },
|
||||
{ date: '2024-01-21', orders: 73, revenue: 45800.00 }
|
||||
],
|
||||
category_distribution: {
|
||||
beef: { count: 45, revenue: 1250000.00 },
|
||||
dairy: { count: 32, revenue: 680000.00 },
|
||||
snacks: { count: 28, revenue: 420000.00 },
|
||||
processed: { count: 35, revenue: 890000.00 },
|
||||
other: { count: 16, revenue: 340000.00 }
|
||||
},
|
||||
top_products: [
|
||||
{ id: 1, name: '优质牛肉礼盒装', sales: 245, revenue: 65660.00 },
|
||||
{ id: 3, name: '牛肉干', sales: 189, revenue: 12852.00 },
|
||||
{ id: 2, name: '有机牛奶', sales: 156, revenue: 5460.00 }
|
||||
],
|
||||
top_sellers: [
|
||||
{ id: 2, name: '张三牧场直营店', orders: 128, revenue: 185600.00 },
|
||||
{ id: 4, name: '草原食品厂', orders: 95, revenue: 142800.00 },
|
||||
{ id: 3, name: '草原乳业', orders: 76, revenue: 98500.00 }
|
||||
]
|
||||
};
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: mockStats
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际统计查询逻辑
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
const mockStats = {
|
||||
overview: {
|
||||
total_products: 156,
|
||||
total_orders: 1284,
|
||||
total_revenue: 2580000.00
|
||||
}
|
||||
};
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,返回模拟数据',
|
||||
data: mockStats
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '商城统计功能开发中',
|
||||
data: { overview: { total_products: 0, total_orders: 0 } }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取商城统计数据失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取商城统计数据失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 导出模块
|
||||
module.exports = {
|
||||
router,
|
||||
setMiddleware
|
||||
};
|
||||
748
backend/api/routes/trading.js
Normal file
748
backend/api/routes/trading.js
Normal file
@@ -0,0 +1,748 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
// 中间件将在服务器启动时设置
|
||||
let authenticateToken = (req, res, next) => {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '认证中间件未初始化',
|
||||
code: 'AUTH_NOT_INITIALIZED'
|
||||
});
|
||||
};
|
||||
|
||||
let checkPermission = (permission) => {
|
||||
return (req, res, next) => {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '权限中间件未初始化',
|
||||
code: 'PERMISSION_NOT_INITIALIZED'
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
let pool = null;
|
||||
|
||||
// 设置中间件和数据库连接(从主服务器导入)
|
||||
function setMiddleware(auth, permission, dbPool) {
|
||||
authenticateToken = auth;
|
||||
checkPermission = permission;
|
||||
pool = dbPool;
|
||||
}
|
||||
|
||||
// ======================================
|
||||
// 交易管理相关接口
|
||||
// ======================================
|
||||
|
||||
// 获取交易记录列表
|
||||
router.get('/transactions', authenticateToken, checkPermission('transaction_view'), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
status,
|
||||
transaction_type,
|
||||
buyer_id,
|
||||
seller_id,
|
||||
search,
|
||||
start_date,
|
||||
end_date
|
||||
} = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockTransactions = [
|
||||
{
|
||||
id: 1,
|
||||
transaction_type: 'cattle_sale',
|
||||
buyer_id: 3,
|
||||
seller_id: 2,
|
||||
buyer_name: '李四',
|
||||
seller_name: '张三',
|
||||
cattle_ids: '1,2,3',
|
||||
cattle_count: 3,
|
||||
unit_price: 15000.00,
|
||||
total_amount: 45000.00,
|
||||
status: 'completed',
|
||||
payment_method: 'bank_transfer',
|
||||
delivery_method: 'pickup',
|
||||
delivery_address: '锡林浩特市郊区牧场',
|
||||
delivery_date: '2024-01-25 09:00:00',
|
||||
notes: '优质西门塔尔牛,健康状况良好',
|
||||
created_at: '2024-01-20 14:30:00',
|
||||
updated_at: '2024-01-25 10:15:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
transaction_type: 'feed_purchase',
|
||||
buyer_id: 2,
|
||||
seller_id: 5,
|
||||
buyer_name: '张三',
|
||||
seller_name: '饲料供应商A',
|
||||
product_name: '优质牧草饲料',
|
||||
quantity: 5000,
|
||||
unit: 'kg',
|
||||
unit_price: 3.50,
|
||||
total_amount: 17500.00,
|
||||
status: 'pending',
|
||||
payment_method: 'cash',
|
||||
delivery_method: 'delivery',
|
||||
delivery_address: '张三牧场',
|
||||
delivery_date: '2024-01-28 08:00:00',
|
||||
notes: '定期饲料采购',
|
||||
created_at: '2024-01-22 16:45:00',
|
||||
updated_at: '2024-01-22 16:45:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
transaction_type: 'equipment_sale',
|
||||
buyer_id: 4,
|
||||
seller_id: 6,
|
||||
buyer_name: '王五',
|
||||
seller_name: '设备供应商B',
|
||||
product_name: '自动饮水设备',
|
||||
quantity: 2,
|
||||
unit: '套',
|
||||
unit_price: 8500.00,
|
||||
total_amount: 17000.00,
|
||||
status: 'in_progress',
|
||||
payment_method: 'installment',
|
||||
delivery_method: 'installation',
|
||||
delivery_address: '王五牧场',
|
||||
delivery_date: '2024-01-30 10:00:00',
|
||||
notes: '包安装调试',
|
||||
created_at: '2024-01-19 11:20:00',
|
||||
updated_at: '2024-01-24 15:30:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
transactions: mockTransactions,
|
||||
pagination: {
|
||||
total: mockTransactions.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockTransactions.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际查询逻辑
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
const mockTransactions = [
|
||||
{
|
||||
id: 1,
|
||||
transaction_type: 'cattle_sale',
|
||||
buyer_name: '李四',
|
||||
seller_name: '张三',
|
||||
total_amount: 45000.00,
|
||||
status: 'completed',
|
||||
created_at: '2024-01-20 14:30:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,返回模拟数据',
|
||||
data: {
|
||||
transactions: mockTransactions,
|
||||
pagination: {
|
||||
total: mockTransactions.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockTransactions.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 构建查询条件
|
||||
let whereClause = '1=1';
|
||||
let queryParams = [];
|
||||
|
||||
if (status) {
|
||||
whereClause += ' AND t.status = ?';
|
||||
queryParams.push(status);
|
||||
}
|
||||
|
||||
if (transaction_type) {
|
||||
whereClause += ' AND t.transaction_type = ?';
|
||||
queryParams.push(transaction_type);
|
||||
}
|
||||
|
||||
if (buyer_id) {
|
||||
whereClause += ' AND t.buyer_id = ?';
|
||||
queryParams.push(buyer_id);
|
||||
}
|
||||
|
||||
if (seller_id) {
|
||||
whereClause += ' AND t.seller_id = ?';
|
||||
queryParams.push(seller_id);
|
||||
}
|
||||
|
||||
if (start_date) {
|
||||
whereClause += ' AND t.created_at >= ?';
|
||||
queryParams.push(start_date);
|
||||
}
|
||||
|
||||
if (end_date) {
|
||||
whereClause += ' AND t.created_at <= ?';
|
||||
queryParams.push(end_date);
|
||||
}
|
||||
|
||||
if (search) {
|
||||
whereClause += ' AND (buyer.real_name LIKE ? OR seller.real_name LIKE ? OR t.notes LIKE ?)';
|
||||
const searchTerm = `%${search}%`;
|
||||
queryParams.push(searchTerm, searchTerm, searchTerm);
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
const [countResult] = await pool.execute(
|
||||
`SELECT COUNT(*) as total
|
||||
FROM transactions t
|
||||
LEFT JOIN users buyer ON t.buyer_id = buyer.id
|
||||
LEFT JOIN users seller ON t.seller_id = seller.id
|
||||
WHERE ${whereClause}`,
|
||||
queryParams
|
||||
);
|
||||
const total = countResult[0].total;
|
||||
|
||||
// 获取交易记录列表
|
||||
const [transactions] = await pool.execute(
|
||||
`SELECT t.*,
|
||||
buyer.real_name as buyer_name, buyer.phone as buyer_phone,
|
||||
seller.real_name as seller_name, seller.phone as seller_phone
|
||||
FROM transactions t
|
||||
LEFT JOIN users buyer ON t.buyer_id = buyer.id
|
||||
LEFT JOIN users seller ON t.seller_id = seller.id
|
||||
WHERE ${whereClause}
|
||||
ORDER BY t.created_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...queryParams, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
transactions,
|
||||
pagination: {
|
||||
total,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取交易记录失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取交易记录失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取交易详情
|
||||
router.get('/transactions/:id', authenticateToken, checkPermission('transaction_view'), async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockTransaction = {
|
||||
id: parseInt(id),
|
||||
transaction_type: 'cattle_sale',
|
||||
buyer_id: 3,
|
||||
seller_id: 2,
|
||||
buyer_name: '李四',
|
||||
seller_name: '张三',
|
||||
buyer_phone: '13900000003',
|
||||
seller_phone: '13900000002',
|
||||
cattle_ids: '1,2,3',
|
||||
cattle_count: 3,
|
||||
cattle_details: [
|
||||
{ id: 1, tag_number: 'C001', breed: '西门塔尔牛', age_months: 24, weight: 450, price: 15000 },
|
||||
{ id: 2, tag_number: 'C002', breed: '西门塔尔牛', age_months: 30, weight: 520, price: 15000 },
|
||||
{ id: 3, tag_number: 'C003', breed: '安格斯牛', age_months: 28, weight: 480, price: 15000 }
|
||||
],
|
||||
unit_price: 15000.00,
|
||||
total_amount: 45000.00,
|
||||
status: 'completed',
|
||||
payment_method: 'bank_transfer',
|
||||
payment_status: 'paid',
|
||||
delivery_method: 'pickup',
|
||||
delivery_address: '锡林浩特市郊区牧场',
|
||||
delivery_date: '2024-01-25 09:00:00',
|
||||
delivery_status: 'delivered',
|
||||
contract_id: 'CON001',
|
||||
contract_url: '/uploads/contracts/CON001.pdf',
|
||||
notes: '优质西门塔尔牛,健康状况良好,已完成检疫',
|
||||
created_at: '2024-01-20 14:30:00',
|
||||
updated_at: '2024-01-25 10:15:00'
|
||||
};
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: mockTransaction
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际查询逻辑
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,返回模拟数据',
|
||||
data: {
|
||||
id: parseInt(id),
|
||||
transaction_type: 'cattle_sale',
|
||||
buyer_name: '李四',
|
||||
seller_name: '张三',
|
||||
total_amount: 45000.00,
|
||||
status: 'completed'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取交易详情
|
||||
const [transactions] = await pool.execute(
|
||||
`SELECT t.*,
|
||||
buyer.real_name as buyer_name, buyer.phone as buyer_phone, buyer.email as buyer_email,
|
||||
seller.real_name as seller_name, seller.phone as seller_phone, seller.email as seller_email,
|
||||
c.contract_number, c.contract_url
|
||||
FROM transactions t
|
||||
LEFT JOIN users buyer ON t.buyer_id = buyer.id
|
||||
LEFT JOIN users seller ON t.seller_id = seller.id
|
||||
LEFT JOIN contracts c ON t.contract_id = c.id
|
||||
WHERE t.id = ?`,
|
||||
[id]
|
||||
);
|
||||
|
||||
if (transactions.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '交易记录不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const transaction = transactions[0];
|
||||
|
||||
// 如果是牛只交易,获取相关牛只信息
|
||||
if (transaction.transaction_type === 'cattle_sale' && transaction.cattle_ids) {
|
||||
const cattleIds = transaction.cattle_ids.split(',');
|
||||
const [cattleList] = await pool.execute(
|
||||
`SELECT id, tag_number, breed, age_months, weight, health_status
|
||||
FROM cattle WHERE id IN (${cattleIds.map(() => '?').join(',')})`,
|
||||
cattleIds
|
||||
);
|
||||
transaction.cattle_details = cattleList;
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: transaction
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取交易详情失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取交易详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 创建新交易
|
||||
router.post('/transactions', authenticateToken, checkPermission('transaction_create'), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
transaction_type,
|
||||
buyer_id,
|
||||
seller_id,
|
||||
cattle_ids,
|
||||
product_name,
|
||||
quantity,
|
||||
unit,
|
||||
unit_price,
|
||||
total_amount,
|
||||
payment_method,
|
||||
delivery_method,
|
||||
delivery_address,
|
||||
delivery_date,
|
||||
notes
|
||||
} = req.body;
|
||||
|
||||
// 验证必需字段
|
||||
if (!transaction_type || !buyer_id || !seller_id || !total_amount) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '缺少必需的字段'
|
||||
});
|
||||
}
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟响应
|
||||
const mockTransaction = {
|
||||
id: Math.floor(Math.random() * 1000) + 100,
|
||||
transaction_type,
|
||||
buyer_id,
|
||||
seller_id,
|
||||
total_amount,
|
||||
status: 'pending',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
return res.status(201).json({
|
||||
success: true,
|
||||
message: '交易创建成功(模拟数据)',
|
||||
data: mockTransaction
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际创建逻辑
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
return res.status(201).json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,模拟创建成功',
|
||||
data: {
|
||||
id: Math.floor(Math.random() * 1000) + 100,
|
||||
transaction_type,
|
||||
status: 'pending',
|
||||
created_at: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 验证买家和卖家是否存在
|
||||
const [buyerCheck] = await pool.execute('SELECT id FROM users WHERE id = ?', [buyer_id]);
|
||||
const [sellerCheck] = await pool.execute('SELECT id FROM users WHERE id = ?', [seller_id]);
|
||||
|
||||
if (buyerCheck.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '买家不存在'
|
||||
});
|
||||
}
|
||||
|
||||
if (sellerCheck.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '卖家不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 创建交易记录
|
||||
const [result] = await pool.execute(
|
||||
`INSERT INTO transactions (
|
||||
transaction_type, buyer_id, seller_id, cattle_ids, product_name,
|
||||
quantity, unit, unit_price, total_amount, payment_method,
|
||||
delivery_method, delivery_address, delivery_date, notes, status
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending')`,
|
||||
[
|
||||
transaction_type, buyer_id, seller_id, cattle_ids, product_name,
|
||||
quantity, unit, unit_price, total_amount, payment_method,
|
||||
delivery_method, delivery_address, delivery_date, notes
|
||||
]
|
||||
);
|
||||
|
||||
// 获取创建的交易记录
|
||||
const [newTransaction] = await pool.execute(
|
||||
`SELECT t.*,
|
||||
buyer.real_name as buyer_name,
|
||||
seller.real_name as seller_name
|
||||
FROM transactions t
|
||||
LEFT JOIN users buyer ON t.buyer_id = buyer.id
|
||||
LEFT JOIN users seller ON t.seller_id = seller.id
|
||||
WHERE t.id = ?`,
|
||||
[result.insertId]
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '交易创建成功',
|
||||
data: newTransaction[0]
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建交易失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建交易失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 更新交易状态
|
||||
router.put('/transactions/:id/status', authenticateToken, checkPermission('transaction_manage'), async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { status, notes } = req.body;
|
||||
|
||||
if (!status) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '状态不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
const validStatuses = ['pending', 'confirmed', 'in_progress', 'completed', 'cancelled', 'refunded'];
|
||||
if (!validStatuses.includes(status)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '无效的状态值'
|
||||
});
|
||||
}
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟响应
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '交易状态更新成功(模拟数据)',
|
||||
data: {
|
||||
id: parseInt(id),
|
||||
status,
|
||||
updated_at: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 数据库可用时的实际更新逻辑
|
||||
try {
|
||||
await pool.execute('SELECT 1');
|
||||
} catch (dbError) {
|
||||
// 数据库连接失败,返回模拟数据
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '数据库连接不可用,模拟更新成功',
|
||||
data: {
|
||||
id: parseInt(id),
|
||||
status,
|
||||
updated_at: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 检查交易是否存在
|
||||
const [existingTransaction] = await pool.execute(
|
||||
'SELECT id, status FROM transactions WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (existingTransaction.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '交易记录不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新交易状态
|
||||
const updateData = [status, id];
|
||||
let updateQuery = 'UPDATE transactions SET status = ?, updated_at = CURRENT_TIMESTAMP';
|
||||
|
||||
if (notes) {
|
||||
updateQuery += ', notes = ?';
|
||||
updateData.splice(1, 0, notes);
|
||||
}
|
||||
|
||||
updateQuery += ' WHERE id = ?';
|
||||
|
||||
await pool.execute(updateQuery, updateData);
|
||||
|
||||
// 获取更新后的交易记录
|
||||
const [updatedTransaction] = await pool.execute(
|
||||
`SELECT t.*,
|
||||
buyer.real_name as buyer_name,
|
||||
seller.real_name as seller_name
|
||||
FROM transactions t
|
||||
LEFT JOIN users buyer ON t.buyer_id = buyer.id
|
||||
LEFT JOIN users seller ON t.seller_id = seller.id
|
||||
WHERE t.id = ?`,
|
||||
[id]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '交易状态更新成功',
|
||||
data: updatedTransaction[0]
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新交易状态失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新交易状态失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ======================================
|
||||
// 合同管理相关接口
|
||||
// ======================================
|
||||
|
||||
// 获取合同列表
|
||||
router.get('/contracts', authenticateToken, checkPermission('contract_view'), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 10,
|
||||
status,
|
||||
contract_type,
|
||||
party_a_id,
|
||||
party_b_id,
|
||||
search
|
||||
} = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockContracts = [
|
||||
{
|
||||
id: 1,
|
||||
contract_number: 'CON2024001',
|
||||
contract_type: 'cattle_sale',
|
||||
party_a_id: 2,
|
||||
party_b_id: 3,
|
||||
party_a_name: '张三',
|
||||
party_b_name: '李四',
|
||||
contract_amount: 45000.00,
|
||||
signing_date: '2024-01-20',
|
||||
effective_date: '2024-01-20',
|
||||
expiry_date: '2024-01-30',
|
||||
status: 'active',
|
||||
contract_url: '/uploads/contracts/CON2024001.pdf',
|
||||
notes: '牛只买卖合同',
|
||||
created_at: '2024-01-20 14:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
contract_number: 'CON2024002',
|
||||
contract_type: 'feed_supply',
|
||||
party_a_id: 5,
|
||||
party_b_id: 2,
|
||||
party_a_name: '饲料供应商A',
|
||||
party_b_name: '张三',
|
||||
contract_amount: 52500.00,
|
||||
signing_date: '2024-01-22',
|
||||
effective_date: '2024-01-22',
|
||||
expiry_date: '2024-12-31',
|
||||
status: 'active',
|
||||
contract_url: '/uploads/contracts/CON2024002.pdf',
|
||||
notes: '饲料长期供应合同',
|
||||
created_at: '2024-01-22 16:45:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
contracts: mockContracts,
|
||||
pagination: {
|
||||
total: mockContracts.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockContracts.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 实际的数据库查询逻辑...
|
||||
res.json({
|
||||
success: true,
|
||||
message: '合同管理功能开发中',
|
||||
data: { contracts: [], pagination: { total: 0, page: parseInt(page), limit: parseInt(limit), pages: 0 } }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取合同列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取合同列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ======================================
|
||||
// 交易统计分析接口
|
||||
// ======================================
|
||||
|
||||
// 获取交易统计数据
|
||||
router.get('/statistics', authenticateToken, checkPermission('transaction_view'), async (req, res) => {
|
||||
try {
|
||||
const { period = 'month', start_date, end_date } = req.query;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockStats = {
|
||||
total_transactions: 156,
|
||||
total_amount: 2450000.00,
|
||||
completed_transactions: 134,
|
||||
pending_transactions: 15,
|
||||
cancelled_transactions: 7,
|
||||
average_transaction_amount: 15705.13,
|
||||
transaction_types: {
|
||||
cattle_sale: { count: 89, amount: 1850000.00 },
|
||||
feed_purchase: { count: 45, amount: 420000.00 },
|
||||
equipment_sale: { count: 22, amount: 180000.00 }
|
||||
},
|
||||
monthly_trend: [
|
||||
{ month: '2023-11', transactions: 12, amount: 195000.00 },
|
||||
{ month: '2023-12', transactions: 18, amount: 285000.00 },
|
||||
{ month: '2024-01', transactions: 24, amount: 385000.00 }
|
||||
],
|
||||
top_buyers: [
|
||||
{ user_id: 3, name: '李四', transaction_count: 8, total_amount: 125000.00 },
|
||||
{ user_id: 4, name: '王五', transaction_count: 6, total_amount: 98000.00 }
|
||||
],
|
||||
top_sellers: [
|
||||
{ user_id: 2, name: '张三', transaction_count: 12, total_amount: 185000.00 },
|
||||
{ user_id: 5, name: '饲料供应商A', transaction_count: 15, total_amount: 142000.00 }
|
||||
]
|
||||
};
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: mockStats
|
||||
});
|
||||
}
|
||||
|
||||
// 实际的统计查询逻辑...
|
||||
res.json({
|
||||
success: true,
|
||||
message: '交易统计功能开发中',
|
||||
data: { total_transactions: 0, total_amount: 0 }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取交易统计失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取交易统计失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 导出模块
|
||||
module.exports = {
|
||||
router,
|
||||
setMiddleware
|
||||
};
|
||||
518
backend/api/routes/users.js
Normal file
518
backend/api/routes/users.js
Normal file
@@ -0,0 +1,518 @@
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcrypt');
|
||||
const router = express.Router();
|
||||
|
||||
// 中间件将在服务器启动时设置
|
||||
let authenticateToken = (req, res, next) => {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '认证中间件未初始化',
|
||||
code: 'AUTH_NOT_INITIALIZED'
|
||||
});
|
||||
};
|
||||
|
||||
let checkPermission = (permission) => {
|
||||
return (req, res, next) => {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '权限中间件未初始化',
|
||||
code: 'PERMISSION_NOT_INITIALIZED'
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
let pool = null;
|
||||
|
||||
// 设置中间件和数据库连接(从主服务器导入)
|
||||
function setMiddleware(auth, permission, dbPool) {
|
||||
authenticateToken = auth;
|
||||
checkPermission = permission;
|
||||
pool = dbPool;
|
||||
}
|
||||
|
||||
// 获取用户列表
|
||||
router.get('/', authenticateToken, checkPermission('user_manage'), async (req, res) => {
|
||||
try {
|
||||
const { page = 1, limit = 10, user_type, status, search } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockUsers = [
|
||||
{
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
email: 'admin@xlxumu.com',
|
||||
real_name: '系统管理员',
|
||||
user_type: 'admin',
|
||||
status: 1,
|
||||
last_login: '2024-01-01 10:00:00',
|
||||
created_at: '2024-01-01 00:00:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: 'farmer001',
|
||||
email: 'farmer001@example.com',
|
||||
real_name: '张三',
|
||||
user_type: 'farmer',
|
||||
status: 1,
|
||||
last_login: '2024-01-02 08:30:00',
|
||||
created_at: '2024-01-01 01:00:00'
|
||||
}
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
users: mockUsers,
|
||||
pagination: {
|
||||
total: mockUsers.length,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(mockUsers.length / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 构建查询条件
|
||||
let whereClause = '1=1';
|
||||
let queryParams = [];
|
||||
|
||||
if (user_type) {
|
||||
whereClause += ' AND user_type = ?';
|
||||
queryParams.push(user_type);
|
||||
}
|
||||
|
||||
if (status !== undefined) {
|
||||
whereClause += ' AND status = ?';
|
||||
queryParams.push(parseInt(status));
|
||||
}
|
||||
|
||||
if (search) {
|
||||
whereClause += ' AND (username LIKE ? OR real_name LIKE ? OR email LIKE ?)';
|
||||
const searchTerm = `%${search}%`;
|
||||
queryParams.push(searchTerm, searchTerm, searchTerm);
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
const [countResult] = await pool.execute(
|
||||
`SELECT COUNT(*) as total FROM users WHERE ${whereClause}`,
|
||||
queryParams
|
||||
);
|
||||
const total = countResult[0].total;
|
||||
|
||||
// 获取用户列表
|
||||
const [users] = await pool.execute(
|
||||
`SELECT id, username, email, phone, real_name, user_type, status, last_login, created_at
|
||||
FROM users
|
||||
WHERE ${whereClause}
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...queryParams, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
users,
|
||||
pagination: {
|
||||
total,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
pages: Math.ceil(total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取用户列表错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取用户列表失败',
|
||||
code: 'GET_USERS_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取用户详情
|
||||
router.get('/:id', authenticateToken, checkPermission('user_manage'), async (req, res) => {
|
||||
try {
|
||||
const userId = req.params.id;
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取用户基本信息
|
||||
const [users] = await pool.execute(
|
||||
'SELECT id, username, email, phone, real_name, user_type, status, last_login, created_at FROM users WHERE id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在',
|
||||
code: 'USER_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
// 获取用户角色
|
||||
const [roles] = await pool.execute(`
|
||||
SELECT r.id, r.name, r.description
|
||||
FROM user_roles ur
|
||||
JOIN roles r ON ur.role_id = r.id
|
||||
WHERE ur.user_id = ?
|
||||
`, [userId]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
user: users[0],
|
||||
roles
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取用户详情错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取用户详情失败',
|
||||
code: 'GET_USER_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 创建用户
|
||||
router.post('/', authenticateToken, checkPermission('user_manage'), async (req, res) => {
|
||||
try {
|
||||
const { username, email, phone, password, real_name, user_type, role_ids } = req.body;
|
||||
|
||||
// 输入验证
|
||||
if (!username || !password || !user_type) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '用户名、密码和用户类型为必填项',
|
||||
code: 'MISSING_REQUIRED_FIELDS'
|
||||
});
|
||||
}
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在
|
||||
const [existingUsers] = await pool.execute(
|
||||
'SELECT id FROM users WHERE username = ? OR email = ? OR phone = ?',
|
||||
[username, email || null, phone || null]
|
||||
);
|
||||
|
||||
if (existingUsers.length > 0) {
|
||||
return res.status(409).json({
|
||||
success: false,
|
||||
message: '用户名、邮箱或手机号已存在',
|
||||
code: 'USER_EXISTS'
|
||||
});
|
||||
}
|
||||
|
||||
// 密码加密
|
||||
const saltRounds = 10;
|
||||
const password_hash = await bcrypt.hash(password, saltRounds);
|
||||
|
||||
// 开始事务
|
||||
const connection = await pool.getConnection();
|
||||
await connection.beginTransaction();
|
||||
|
||||
try {
|
||||
// 插入用户
|
||||
const [userResult] = await connection.execute(
|
||||
'INSERT INTO users (username, email, phone, password_hash, real_name, user_type) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[username, email || null, phone || null, password_hash, real_name || null, user_type]
|
||||
);
|
||||
|
||||
const newUserId = userResult.insertId;
|
||||
|
||||
// 分配角色
|
||||
if (role_ids && role_ids.length > 0) {
|
||||
for (const roleId of role_ids) {
|
||||
await connection.execute(
|
||||
'INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)',
|
||||
[newUserId, roleId]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await connection.commit();
|
||||
connection.release();
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '用户创建成功',
|
||||
data: {
|
||||
userId: newUserId,
|
||||
username,
|
||||
user_type
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
await connection.rollback();
|
||||
connection.release();
|
||||
throw error;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建用户错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建用户失败',
|
||||
code: 'CREATE_USER_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 更新用户
|
||||
router.put('/:id', authenticateToken, checkPermission('user_manage'), async (req, res) => {
|
||||
try {
|
||||
const userId = req.params.id;
|
||||
const { email, phone, real_name, status, role_ids } = req.body;
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查用户是否存在
|
||||
const [users] = await pool.execute('SELECT id FROM users WHERE id = ?', [userId]);
|
||||
if (users.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在',
|
||||
code: 'USER_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查邮箱和手机号是否被其他用户使用
|
||||
if (email || phone) {
|
||||
const [existingUsers] = await pool.execute(
|
||||
'SELECT id FROM users WHERE (email = ? OR phone = ?) AND id != ?',
|
||||
[email || null, phone || null, userId]
|
||||
);
|
||||
|
||||
if (existingUsers.length > 0) {
|
||||
return res.status(409).json({
|
||||
success: false,
|
||||
message: '邮箱或手机号已被其他用户使用',
|
||||
code: 'CONTACT_EXISTS'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
const connection = await pool.getConnection();
|
||||
await connection.beginTransaction();
|
||||
|
||||
try {
|
||||
// 更新用户基本信息
|
||||
await connection.execute(
|
||||
'UPDATE users SET email = ?, phone = ?, real_name = ?, status = ? WHERE id = ?',
|
||||
[email || null, phone || null, real_name || null, status !== undefined ? status : 1, userId]
|
||||
);
|
||||
|
||||
// 更新用户角色
|
||||
if (role_ids !== undefined) {
|
||||
// 删除现有角色
|
||||
await connection.execute('DELETE FROM user_roles WHERE user_id = ?', [userId]);
|
||||
|
||||
// 添加新角色
|
||||
if (role_ids.length > 0) {
|
||||
for (const roleId of role_ids) {
|
||||
await connection.execute(
|
||||
'INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)',
|
||||
[userId, roleId]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await connection.commit();
|
||||
connection.release();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '用户更新成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
await connection.rollback();
|
||||
connection.release();
|
||||
throw error;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新用户错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新用户失败',
|
||||
code: 'UPDATE_USER_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 删除用户
|
||||
router.delete('/:id', authenticateToken, checkPermission('user_manage'), async (req, res) => {
|
||||
try {
|
||||
const userId = req.params.id;
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查是否是当前用户
|
||||
if (parseInt(userId) === req.user.userId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '不能删除当前登录用户',
|
||||
code: 'CANNOT_DELETE_SELF'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查用户是否存在
|
||||
const [users] = await pool.execute('SELECT id FROM users WHERE id = ?', [userId]);
|
||||
if (users.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在',
|
||||
code: 'USER_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
// 删除用户(级联删除用户角色关联)
|
||||
await pool.execute('DELETE FROM users WHERE id = ?', [userId]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '用户删除成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('删除用户错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除用户失败',
|
||||
code: 'DELETE_USER_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 重置用户密码
|
||||
router.post('/:id/reset-password', authenticateToken, checkPermission('user_manage'), async (req, res) => {
|
||||
try {
|
||||
const userId = req.params.id;
|
||||
const { new_password } = req.body;
|
||||
|
||||
if (!new_password) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '新密码为必填项',
|
||||
code: 'MISSING_PASSWORD'
|
||||
});
|
||||
}
|
||||
|
||||
if (!pool) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '数据库连接不可用',
|
||||
code: 'DB_UNAVAILABLE'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查用户是否存在
|
||||
const [users] = await pool.execute('SELECT id FROM users WHERE id = ?', [userId]);
|
||||
if (users.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在',
|
||||
code: 'USER_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
// 加密新密码
|
||||
const saltRounds = 10;
|
||||
const password_hash = await bcrypt.hash(new_password, saltRounds);
|
||||
|
||||
// 更新密码
|
||||
await pool.execute('UPDATE users SET password_hash = ? WHERE id = ?', [password_hash, userId]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '密码重置成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('重置密码错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '重置密码失败',
|
||||
code: 'RESET_PASSWORD_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取所有角色(用于分配角色)
|
||||
router.get('/roles/list', authenticateToken, checkPermission('user_manage'), async (req, res) => {
|
||||
try {
|
||||
if (!pool) {
|
||||
// 数据库不可用时返回模拟数据
|
||||
const mockRoles = [
|
||||
{ id: 1, name: 'admin', description: '系统管理员' },
|
||||
{ id: 2, name: 'farmer', description: '养殖户' },
|
||||
{ id: 3, name: 'banker', description: '银行职员' },
|
||||
{ id: 4, name: 'insurer', description: '保险员' },
|
||||
{ id: 5, name: 'government', description: '政府监管人员' },
|
||||
{ id: 6, name: 'trader', description: '交易员' }
|
||||
];
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: mockRoles
|
||||
});
|
||||
}
|
||||
|
||||
const [roles] = await pool.execute('SELECT id, name, description FROM roles ORDER BY id');
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: roles
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取角色列表错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取角色列表失败',
|
||||
code: 'GET_ROLES_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
setMiddleware
|
||||
};
|
||||
@@ -9,7 +9,7 @@ dotenv.config();
|
||||
|
||||
// 创建Express应用
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3350; // 生产环境使用3350端口
|
||||
const PORT = process.env.PORT || 8000;
|
||||
|
||||
// 中间件
|
||||
app.use(helmet()); // 安全头部
|
||||
@@ -20,7 +20,7 @@ app.use(express.urlencoded({ extended: true, limit: '10mb' })); // URL编码解
|
||||
// 速率限制
|
||||
const limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15分钟
|
||||
max: 1000, // 生产环境提高限制
|
||||
max: 100, // 限制每个IP 15分钟内最多100个请求
|
||||
message: '请求过于频繁,请稍后再试'
|
||||
});
|
||||
app.use(limiter);
|
||||
@@ -30,30 +30,14 @@ app.get('/', (req, res) => {
|
||||
res.json({
|
||||
message: '欢迎使用锡林郭勒盟地区智慧养殖产业平台API服务',
|
||||
version: '1.0.0',
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
timestamp: new Date().toISOString(),
|
||||
docs: 'https://xlapi.jiebanke.com/docs'
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'OK',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: process.uptime(),
|
||||
memory: process.memoryUsage()
|
||||
});
|
||||
});
|
||||
|
||||
// API信息端点
|
||||
app.get('/api/info', (req, res) => {
|
||||
res.json({
|
||||
name: 'xlxumu-api',
|
||||
version: '1.0.0',
|
||||
environment: process.env.NODE_ENV,
|
||||
port: PORT,
|
||||
node_version: process.version,
|
||||
platform: process.platform
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
@@ -168,46 +152,9 @@ app.get('/api/v1/dashboard/map/region/:regionId', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 错误处理中间件
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('服务器错误:', err.stack);
|
||||
res.status(500).json({
|
||||
error: '内部服务器错误',
|
||||
message: process.env.NODE_ENV === 'development' ? err.message : 'Something went wrong!'
|
||||
});
|
||||
});
|
||||
|
||||
// 404处理
|
||||
app.use((req, res) => {
|
||||
res.status(404).json({
|
||||
error: '接口未找到',
|
||||
path: req.path,
|
||||
method: req.method
|
||||
});
|
||||
});
|
||||
|
||||
// 优雅关机处理
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n收到SIGINT信号,正在优雅关闭服务器...');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('\n收到SIGTERM信号,正在优雅关闭服务器...');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// 启动服务器
|
||||
const server = app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`🚀 API服务器正在运行:`);
|
||||
console.log(` 📍 本地: http://localhost:${PORT}`);
|
||||
console.log(` 🌐 网络: http://0.0.0.0:${PORT}`);
|
||||
console.log(` 🏷️ 环境: ${process.env.NODE_ENV || 'development'}`);
|
||||
console.log(` ⏰ 启动时间: ${new Date().toLocaleString()}`);
|
||||
app.listen(PORT, () => {
|
||||
console.log(`API服务器正在端口 ${PORT} 上运行`);
|
||||
});
|
||||
|
||||
// 设置超时
|
||||
server.timeout = 60000;
|
||||
server.keepAliveTimeout = 5000;
|
||||
|
||||
module.exports = app;
|
||||
53
backend/api/test-db-connection.js
Normal file
53
backend/api/test-db-connection.js
Normal file
@@ -0,0 +1,53 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
require('dotenv').config();
|
||||
|
||||
async function testConnection() {
|
||||
console.log('🔍 测试数据库连接...');
|
||||
console.log(`Host: ${process.env.DB_HOST}`);
|
||||
console.log(`Port: ${process.env.DB_PORT}`);
|
||||
console.log(`User: ${process.env.DB_USER}`);
|
||||
console.log(`Database: ${process.env.DB_NAME}`);
|
||||
|
||||
const config = {
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT),
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
connectTimeout: 60000,
|
||||
acquireTimeout: 60000,
|
||||
timeout: 60000
|
||||
};
|
||||
|
||||
try {
|
||||
console.log('📡 尝试连接...');
|
||||
const connection = await mysql.createConnection(config);
|
||||
console.log('✅ 连接成功!');
|
||||
|
||||
// 测试简单查询
|
||||
const [rows] = await connection.execute('SELECT 1 as test');
|
||||
console.log('✅ 查询测试成功:', rows);
|
||||
|
||||
// 测试数据库信息
|
||||
const [version] = await connection.execute('SELECT VERSION() as version');
|
||||
console.log('📝 MySQL版本:', version[0].version);
|
||||
|
||||
await connection.end();
|
||||
console.log('✅ 连接正常关闭');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 连接失败:');
|
||||
console.error('错误代码:', error.code);
|
||||
console.error('错误信息:', error.message);
|
||||
console.error('错误详情:', error.sqlMessage || 'N/A');
|
||||
|
||||
if (error.code === 'ER_ACCESS_DENIED_ERROR') {
|
||||
console.log('\n💡 可能的解决方案:');
|
||||
console.log('1. 检查用户名和密码是否正确');
|
||||
console.log('2. 确认用户有访问该数据库的权限');
|
||||
console.log('3. 检查IP白名单是否包含当前服务器IP');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testConnection();
|
||||
37
backend/database/.env
Normal file
37
backend/database/.env
Normal file
@@ -0,0 +1,37 @@
|
||||
# 服务器配置
|
||||
PORT=8888
|
||||
NODE_ENV=development
|
||||
|
||||
# 数据库配置
|
||||
DB_HOST=nj-cdb-3pwh2kz1.sql.tencentcdb.com
|
||||
DB_PORT=20784
|
||||
DB_USER=xymg
|
||||
DB_PASSWORD=aiot741$xymg
|
||||
DB_NAME=xumgdata
|
||||
DB_CHARSET=utf8mb4
|
||||
|
||||
# Redis配置 (待配置)
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
|
||||
# JWT配置
|
||||
JWT_SECRET=xlxumu_jwt_secret_key_2024
|
||||
JWT_EXPIRES_IN=24h
|
||||
|
||||
# 腾讯云对象存储配置 (待配置)
|
||||
COS_SECRET_ID=
|
||||
COS_SECRET_KEY=
|
||||
COS_BUCKET=
|
||||
COS_REGION=
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL=info
|
||||
LOG_FILE=./logs/app.log
|
||||
|
||||
# 安全配置
|
||||
RATE_LIMIT_MAX=100
|
||||
RATE_LIMIT_WINDOW=15
|
||||
|
||||
# WebSocket配置
|
||||
WS_PORT=8001
|
||||
220
backend/database/DATABASE_SETUP.md
Normal file
220
backend/database/DATABASE_SETUP.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# 数据库配置指南
|
||||
|
||||
## 数据库配置信息
|
||||
|
||||
### 腾讯云MySQL配置
|
||||
- **主机地址**: nj-cdb-3pwh2kz1.sql.tencentcdb.com
|
||||
- **端口**: 20784
|
||||
- **数据库名**: xumgdata
|
||||
- **用户名**: xymg
|
||||
- **密码**: aiot741$xymg
|
||||
|
||||
### 当前状态
|
||||
🔴 **连接状态**: 访问受限(IP白名单问题)
|
||||
📍 **当前IP**: 43.153.101.71(需要添加到白名单)
|
||||
|
||||
## 解决IP白名单问题
|
||||
|
||||
### 步骤1:登录腾讯云控制台
|
||||
1. 访问 [腾讯云控制台](https://console.cloud.tencent.com/)
|
||||
2. 登录账户
|
||||
|
||||
### 步骤2:找到数据库实例
|
||||
1. 进入 **云数据库 MySQL** 控制台
|
||||
2. 找到实例:`nj-cdb-3pwh2kz1`
|
||||
|
||||
### 步骤3:配置安全组/白名单
|
||||
1. 点击实例进入详情页
|
||||
2. 找到 **安全组** 或 **白名单** 设置
|
||||
3. 添加以下IP地址:
|
||||
- `43.153.101.71` (当前开发服务器IP)
|
||||
- `0.0.0.0/0` (临时开放所有IP,生产环境不推荐)
|
||||
|
||||
### 步骤4:验证连接
|
||||
执行以下命令测试连接:
|
||||
```bash
|
||||
cd /Users/ainongkeji/code/vue/xlxumu/backend-java/services/farming-service/src/main/resources
|
||||
node database-manager.js test
|
||||
```
|
||||
|
||||
## 数据库初始化
|
||||
|
||||
### 一键初始化
|
||||
```bash
|
||||
# 测试连接
|
||||
node database-manager.js test
|
||||
|
||||
# 初始化数据库表结构
|
||||
node database-manager.js init
|
||||
|
||||
# 重置数据库(删除所有数据后重新创建)
|
||||
node database-manager.js reset
|
||||
```
|
||||
|
||||
### 手动初始化
|
||||
如果自动化工具无法使用,可以手动执行SQL脚本:
|
||||
|
||||
1. **连接数据库**:
|
||||
```bash
|
||||
mysql -h nj-cdb-3pwh2kz1.sql.tencentcdb.com -P 20784 -u xymg -p xumgdata
|
||||
```
|
||||
|
||||
2. **执行表结构脚本**:
|
||||
```sql
|
||||
source /Users/ainongkeji/code/vue/xlxumu/backend-java/services/farming-service/src/main/resources/sql/init_tables.sql;
|
||||
```
|
||||
|
||||
3. **执行初始数据脚本**:
|
||||
```sql
|
||||
source /Users/ainongkeji/code/vue/xlxumu/backend-java/services/farming-service/src/main/resources/sql/init_data.sql;
|
||||
```
|
||||
|
||||
## 数据库表结构
|
||||
|
||||
### 核心业务表(21张)
|
||||
|
||||
#### 1. 用户权限模块
|
||||
- `users` - 用户表
|
||||
- `roles` - 角色表
|
||||
- `permissions` - 权限表
|
||||
- `user_roles` - 用户角色关联表
|
||||
- `role_permissions` - 角色权限关联表
|
||||
|
||||
#### 2. 牛只档案模块
|
||||
- `cattle` - 牛只档案表
|
||||
- `feeding_records` - 饲养记录表
|
||||
- `breeding_records` - 繁殖记录表
|
||||
|
||||
#### 3. 金融业务模块
|
||||
- `loan_applications` - 贷款申请表
|
||||
- `insurance_applications` - 保险申请表
|
||||
- `claims` - 理赔申请表
|
||||
|
||||
#### 4. 交易管理模块
|
||||
- `transactions` - 交易记录表
|
||||
- `contracts` - 合同表
|
||||
|
||||
#### 5. 政府监管模块
|
||||
- `farms` - 牧场注册表
|
||||
- `government_inspections` - 政府检查记录表
|
||||
- `policies` - 政策法规表
|
||||
|
||||
#### 6. 商城管理模块
|
||||
- `products` - 商品表
|
||||
- `orders` - 订单表
|
||||
- `order_items` - 订单商品表
|
||||
- `product_reviews` - 商品评价表
|
||||
|
||||
#### 7. 质量追溯模块
|
||||
- `product_traceability` - 产品追溯表
|
||||
|
||||
#### 8. 系统管理模块
|
||||
- `operation_logs` - 操作日志表
|
||||
|
||||
## 初始数据说明
|
||||
|
||||
### 默认用户账户
|
||||
| 用户名 | 密码 | 角色 | 姓名 | 描述 |
|
||||
|--------|------|------|------|------|
|
||||
| admin | 123456 | 系统管理员 | 系统管理员 | 拥有所有权限 |
|
||||
| farmer001 | 123456 | 养殖户 | 张三 | 测试养殖户1 |
|
||||
| farmer002 | 123456 | 养殖户 | 李四 | 测试养殖户2 |
|
||||
| banker001 | 123456 | 银行职员 | 王五 | 测试银行职员 |
|
||||
| insurer001 | 123456 | 保险员 | 赵六 | 测试保险员 |
|
||||
| inspector001 | 123456 | 政府检查员 | 钱七 | 测试政府检查员 |
|
||||
| trader001 | 123456 | 交易员 | 孙八 | 测试交易员 |
|
||||
| merchant001 | 123456 | 商户 | 周九 | 测试商户 |
|
||||
|
||||
### 角色权限配置
|
||||
- **管理员**: 拥有所有权限
|
||||
- **养殖户**: 牛只管理、金融申请、交易参与
|
||||
- **银行职员**: 贷款审核和管理
|
||||
- **保险员**: 保险审核和理赔管理
|
||||
- **政府检查员**: 监管检查和质量追溯
|
||||
- **政府管理员**: 监管数据统计和政策管理
|
||||
- **交易员**: 交易和合同管理
|
||||
- **商户**: 商品和订单管理
|
||||
|
||||
### 示例数据
|
||||
- **3个示例牧场**:锡林浩特市第一牧场、东乌旗生态牧场、西乌旗示范牧场
|
||||
- **5头示例牛只**:包含不同品种和基本信息
|
||||
- **5个示例商品**:牛肉、乳制品、肉制品等
|
||||
|
||||
## 验证安装
|
||||
|
||||
### 1. 检查表创建
|
||||
```sql
|
||||
SELECT TABLE_NAME, TABLE_COMMENT
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = 'xumgdata'
|
||||
ORDER BY TABLE_NAME;
|
||||
```
|
||||
|
||||
### 2. 检查数据插入
|
||||
```sql
|
||||
-- 检查角色数量
|
||||
SELECT COUNT(*) as role_count FROM roles;
|
||||
|
||||
-- 检查权限数量
|
||||
SELECT COUNT(*) as permission_count FROM permissions;
|
||||
|
||||
-- 检查用户数量
|
||||
SELECT COUNT(*) as user_count FROM users;
|
||||
|
||||
-- 检查管理员用户
|
||||
SELECT username, real_name, user_type FROM users WHERE user_type = 'admin';
|
||||
```
|
||||
|
||||
### 3. 测试API连接
|
||||
启动API服务器后,访问:
|
||||
```
|
||||
http://localhost:8889/health
|
||||
http://localhost:8889/api/v1/database/status
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见错误
|
||||
|
||||
#### 1. Access denied for user
|
||||
**错误**: `Access denied for user 'xymg'@'43.153.101.71'`
|
||||
**解决**: 检查IP白名单设置
|
||||
|
||||
#### 2. Can't connect to MySQL server
|
||||
**错误**: `Can't connect to MySQL server`
|
||||
**解决**: 检查网络连接和端口访问
|
||||
|
||||
#### 3. Unknown database
|
||||
**错误**: `Unknown database 'xumgdata'`
|
||||
**解决**: 确认数据库名称正确
|
||||
|
||||
#### 4. Table already exists
|
||||
**错误**: `Table 'xxx' already exists`
|
||||
**解决**: 正常情况,使用 `CREATE TABLE IF NOT EXISTS`
|
||||
|
||||
### 联系支持
|
||||
如遇到问题,请检查:
|
||||
1. 网络连接是否正常
|
||||
2. 腾讯云账户状态
|
||||
3. 数据库实例状态
|
||||
4. IP白名单配置
|
||||
|
||||
## 生产环境注意事项
|
||||
|
||||
### 安全配置
|
||||
1. **修改默认密码**: 所有测试账户密码
|
||||
2. **限制IP访问**: 仅允许必要的IP访问
|
||||
3. **启用SSL**: 加密数据传输
|
||||
4. **定期备份**: 设置自动备份策略
|
||||
|
||||
### 性能优化
|
||||
1. **索引优化**: 根据查询模式优化索引
|
||||
2. **分区表**: 对大数据量表进行分区
|
||||
3. **连接池**: 优化数据库连接池配置
|
||||
4. **监控**: 设置性能监控和告警
|
||||
|
||||
### 运维管理
|
||||
1. **日志管理**: 配置慢查询日志
|
||||
2. **权限管理**: 定期审查用户权限
|
||||
3. **版本管理**: 使用数据库迁移工具
|
||||
4. **容灾**: 配置主从复制和故障转移
|
||||
231
backend/database/database-manager.js
Normal file
231
backend/database/database-manager.js
Normal file
@@ -0,0 +1,231 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
require('dotenv').config();
|
||||
|
||||
// 数据库连接配置
|
||||
const dbConfig = {
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
charset: 'utf8mb4',
|
||||
multipleStatements: true
|
||||
};
|
||||
|
||||
async function initializeDatabase() {
|
||||
let connection = null;
|
||||
|
||||
try {
|
||||
console.log('🔗 正在连接数据库...');
|
||||
console.log(`📍 连接地址: ${dbConfig.host}:${dbConfig.port}`);
|
||||
console.log(`📊 数据库名: ${dbConfig.database}`);
|
||||
|
||||
// 创建数据库连接
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
console.log('✅ 数据库连接成功!');
|
||||
|
||||
// 读取初始化脚本
|
||||
const sqlFilePath = path.join(__dirname, 'init_tables.sql');
|
||||
console.log(`📄 读取SQL脚本: ${sqlFilePath}`);
|
||||
|
||||
if (!fs.existsSync(sqlFilePath)) {
|
||||
throw new Error(`SQL脚本文件不存在: ${sqlFilePath}`);
|
||||
}
|
||||
|
||||
const sqlScript = fs.readFileSync(sqlFilePath, 'utf8');
|
||||
console.log(`📋 SQL脚本大小: ${(sqlScript.length / 1024).toFixed(2)} KB`);
|
||||
|
||||
// 执行初始化脚本
|
||||
console.log('🚀 开始执行数据库初始化...');
|
||||
const [results] = await connection.execute(sqlScript);
|
||||
|
||||
console.log('✅ 数据库初始化完成!');
|
||||
|
||||
// 验证表创建情况
|
||||
console.log('🔍 验证表创建情况...');
|
||||
const [tables] = await connection.execute(
|
||||
'SELECT TABLE_NAME, TABLE_COMMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? ORDER BY TABLE_NAME',
|
||||
[dbConfig.database]
|
||||
);
|
||||
|
||||
console.log(`📊 成功创建 ${tables.length} 张表:`);
|
||||
tables.forEach((table, index) => {
|
||||
console.log(` ${index + 1}. ${table.TABLE_NAME} - ${table.TABLE_COMMENT || '无注释'}`);
|
||||
});
|
||||
|
||||
// 检查初始数据
|
||||
console.log('\n🔍 检查初始数据...');
|
||||
|
||||
// 检查角色表
|
||||
const [roles] = await connection.execute('SELECT COUNT(*) as count FROM roles');
|
||||
console.log(`👥 角色数量: ${roles[0].count}`);
|
||||
|
||||
// 检查权限表
|
||||
const [permissions] = await connection.execute('SELECT COUNT(*) as count FROM permissions');
|
||||
console.log(`🔐 权限数量: ${permissions[0].count}`);
|
||||
|
||||
// 检查用户表
|
||||
const [users] = await connection.execute('SELECT COUNT(*) as count FROM users');
|
||||
console.log(`👤 用户数量: ${users[0].count}`);
|
||||
|
||||
if (users[0].count > 0) {
|
||||
const [adminUser] = await connection.execute(
|
||||
'SELECT username, real_name, user_type FROM users WHERE user_type = "admin" LIMIT 1'
|
||||
);
|
||||
if (adminUser.length > 0) {
|
||||
console.log(`🔧 管理员用户: ${adminUser[0].username} (${adminUser[0].real_name})`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n🎉 数据库初始化成功完成!');
|
||||
console.log('📝 下一步可以启动API服务器进行测试');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 数据库初始化失败:', error.message);
|
||||
|
||||
if (error.code === 'ENOTFOUND') {
|
||||
console.error('🌐 网络连接问题:无法解析数据库主机名');
|
||||
} else if (error.code === 'ER_ACCESS_DENIED_ERROR') {
|
||||
console.error('🔐 认证失败:用户名或密码错误');
|
||||
} else if (error.code === 'ECONNREFUSED') {
|
||||
console.error('🚫 连接被拒绝:数据库服务器可能未运行或端口被封锁');
|
||||
} else if (error.code === 'ER_BAD_DB_ERROR') {
|
||||
console.error('🗃️ 数据库不存在:请先创建目标数据库');
|
||||
} else if (error.message.includes('Access denied')) {
|
||||
console.error('🛡️ IP访问限制:请检查数据库白名单设置');
|
||||
console.error('💡 解决方案:在腾讯云控制台添加当前IP到数据库白名单');
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
} finally {
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
console.log('🔚 数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试数据库连接
|
||||
async function testConnection() {
|
||||
let connection = null;
|
||||
|
||||
try {
|
||||
console.log('🧪 测试数据库连接...');
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
|
||||
const [result] = await connection.execute('SELECT 1 as test, NOW() as current_time');
|
||||
console.log('✅ 连接测试成功!');
|
||||
console.log(`⏰ 数据库时间: ${result[0].current_time}`);
|
||||
|
||||
const [versionResult] = await connection.execute('SELECT VERSION() as version');
|
||||
console.log(`🗄️ MySQL版本: ${versionResult[0].version}`);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 连接测试失败:', error.message);
|
||||
return false;
|
||||
} finally {
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除所有表(危险操作,仅用于重置)
|
||||
async function dropAllTables() {
|
||||
let connection = null;
|
||||
|
||||
try {
|
||||
console.log('⚠️ 警告:即将删除所有表!');
|
||||
console.log('3秒后开始执行...');
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
|
||||
// 获取所有表
|
||||
const [tables] = await connection.execute(
|
||||
'SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ?',
|
||||
[dbConfig.database]
|
||||
);
|
||||
|
||||
if (tables.length === 0) {
|
||||
console.log('📭 数据库中没有表需要删除');
|
||||
return;
|
||||
}
|
||||
|
||||
// 禁用外键检查
|
||||
await connection.execute('SET FOREIGN_KEY_CHECKS = 0');
|
||||
|
||||
// 删除所有表
|
||||
for (const table of tables) {
|
||||
console.log(`🗑️ 删除表: ${table.TABLE_NAME}`);
|
||||
await connection.execute(`DROP TABLE IF EXISTS \`${table.TABLE_NAME}\``);
|
||||
}
|
||||
|
||||
// 启用外键检查
|
||||
await connection.execute('SET FOREIGN_KEY_CHECKS = 1');
|
||||
|
||||
console.log('✅ 所有表已删除');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 删除表失败:', error.message);
|
||||
} finally {
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
const command = process.argv[2];
|
||||
|
||||
switch (command) {
|
||||
case 'test':
|
||||
await testConnection();
|
||||
break;
|
||||
case 'init':
|
||||
await initializeDatabase();
|
||||
break;
|
||||
case 'reset':
|
||||
console.log('⚠️ 确认要重置数据库吗?这将删除所有数据!');
|
||||
console.log('如果确认,请在5秒内按Ctrl+C取消,否则将继续执行...');
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
await dropAllTables();
|
||||
await initializeDatabase();
|
||||
break;
|
||||
default:
|
||||
console.log('🔧 锡林郭勒盟智慧养殖平台 - 数据库管理工具');
|
||||
console.log('');
|
||||
console.log('使用方法:');
|
||||
console.log(' node database-manager.js test - 测试数据库连接');
|
||||
console.log(' node database-manager.js init - 初始化数据库表');
|
||||
console.log(' node database-manager.js reset - 重置数据库(删除所有表后重新创建)');
|
||||
console.log('');
|
||||
console.log('环境变量配置:');
|
||||
console.log(` DB_HOST: ${process.env.DB_HOST || '未设置'}`);
|
||||
console.log(` DB_PORT: ${process.env.DB_PORT || '未设置'}`);
|
||||
console.log(` DB_USER: ${process.env.DB_USER || '未设置'}`);
|
||||
console.log(` DB_NAME: ${process.env.DB_NAME || '未设置'}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 优雅处理进程退出
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n👋 程序被用户中断');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error('❌ 未处理的Promise拒绝:', reason);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// 运行主函数
|
||||
main().catch(error => {
|
||||
console.error('❌ 程序执行失败:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
232
backend/database/init_data.sql
Normal file
232
backend/database/init_data.sql
Normal file
@@ -0,0 +1,232 @@
|
||||
-- ======================================
|
||||
-- 锡林郭勒盟智慧养殖产业平台 - 初始数据脚本
|
||||
-- ======================================
|
||||
|
||||
-- 清理现有数据(可选)
|
||||
-- DELETE FROM user_roles;
|
||||
-- DELETE FROM role_permissions;
|
||||
-- DELETE FROM users WHERE id > 1;
|
||||
|
||||
-- ======================================
|
||||
-- 1. 角色和权限初始化
|
||||
-- ======================================
|
||||
|
||||
-- 插入角色数据
|
||||
INSERT IGNORE INTO `roles` (`name`, `description`) VALUES
|
||||
('admin', '系统管理员 - 拥有所有权限'),
|
||||
('farmer', '养殖户 - 管理自己的牛只和交易'),
|
||||
('banker', '银行职员 - 处理贷款申请'),
|
||||
('insurer', '保险员 - 处理保险和理赔'),
|
||||
('government_inspector', '政府检查员 - 进行合规检查'),
|
||||
('government_admin', '政府管理员 - 查看监管数据'),
|
||||
('trader', '交易员 - 处理交易业务'),
|
||||
('merchant', '商户 - 管理商城商品');
|
||||
|
||||
-- 插入权限数据
|
||||
INSERT IGNORE INTO `permissions` (`name`, `description`, `module`) VALUES
|
||||
-- 用户管理权限
|
||||
('user_view', '查看用户信息', 'user'),
|
||||
('user_create', '创建用户', 'user'),
|
||||
('user_edit', '编辑用户信息', 'user'),
|
||||
('user_delete', '删除用户', 'user'),
|
||||
('user_manage', '用户管理(包含所有用户操作)', 'user'),
|
||||
|
||||
-- 牛只管理权限
|
||||
('cattle_view', '查看牛只信息', 'cattle'),
|
||||
('cattle_create', '创建牛只档案', 'cattle'),
|
||||
('cattle_edit', '编辑牛只信息', 'cattle'),
|
||||
('cattle_delete', '删除牛只档案', 'cattle'),
|
||||
('cattle_manage', '牛只管理(包含所有牛只操作)', 'cattle'),
|
||||
|
||||
-- 金融服务权限
|
||||
('loan_view', '查看贷款信息', 'finance'),
|
||||
('loan_create', '创建贷款申请', 'finance'),
|
||||
('loan_review', '审核贷款申请', 'finance'),
|
||||
('loan_manage', '贷款管理', 'finance'),
|
||||
('insurance_view', '查看保险信息', 'finance'),
|
||||
('insurance_create', '创建保险申请', 'finance'),
|
||||
('insurance_review', '审核保险申请', 'finance'),
|
||||
('insurance_manage', '保险管理', 'finance'),
|
||||
|
||||
-- 交易管理权限
|
||||
('transaction_view', '查看交易信息', 'trading'),
|
||||
('transaction_create', '创建交易记录', 'trading'),
|
||||
('transaction_manage', '交易管理', 'trading'),
|
||||
('contract_view', '查看合同信息', 'trading'),
|
||||
('contract_create', '创建合同', 'trading'),
|
||||
('contract_manage', '合同管理', 'trading'),
|
||||
|
||||
-- 政府监管权限
|
||||
('government_supervision', '政府监管权限', 'government'),
|
||||
('government_inspection', '政府检查权限', 'government'),
|
||||
('government_statistics', '政府统计权限', 'government'),
|
||||
('government_report', '政府报告权限', 'government'),
|
||||
('quality_trace', '质量追溯权限', 'government'),
|
||||
('policy_view', '查看政策法规', 'government'),
|
||||
('policy_manage', '管理政策法规', 'government'),
|
||||
|
||||
-- 商城管理权限
|
||||
('product_view', '查看商品信息', 'mall'),
|
||||
('product_create', '创建商品', 'mall'),
|
||||
('product_edit', '编辑商品信息', 'mall'),
|
||||
('product_delete', '删除商品', 'mall'),
|
||||
('product_manage', '商品管理', 'mall'),
|
||||
('order_view', '查看订单信息', 'mall'),
|
||||
('order_manage', '订单管理', 'mall'),
|
||||
('mall_statistics', '商城统计', 'mall'),
|
||||
|
||||
-- 系统管理权限
|
||||
('system_config', '系统配置', 'system'),
|
||||
('data_view', '数据查看', 'system'),
|
||||
('data_export', '数据导出', 'system'),
|
||||
('log_view', '日志查看', 'system');
|
||||
|
||||
-- ======================================
|
||||
-- 2. 角色权限分配
|
||||
-- ======================================
|
||||
|
||||
-- 管理员角色 - 拥有所有权限
|
||||
INSERT IGNORE INTO `role_permissions` (`role_id`, `permission_id`)
|
||||
SELECT r.id, p.id FROM `roles` r, `permissions` p WHERE r.name = 'admin';
|
||||
|
||||
-- 养殖户角色权限
|
||||
INSERT IGNORE INTO `role_permissions` (`role_id`, `permission_id`)
|
||||
SELECT r.id, p.id FROM `roles` r, `permissions` p
|
||||
WHERE r.name = 'farmer' AND p.name IN (
|
||||
'cattle_view', 'cattle_create', 'cattle_edit', 'cattle_manage',
|
||||
'loan_view', 'loan_create', 'insurance_view', 'insurance_create',
|
||||
'transaction_view', 'transaction_create', 'contract_view',
|
||||
'product_view', 'order_view', 'data_view'
|
||||
);
|
||||
|
||||
-- 银行职员角色权限
|
||||
INSERT IGNORE INTO `role_permissions` (`role_id`, `permission_id`)
|
||||
SELECT r.id, p.id FROM `roles` r, `permissions` p
|
||||
WHERE r.name = 'banker' AND p.name IN (
|
||||
'loan_view', 'loan_review', 'loan_manage',
|
||||
'cattle_view', 'user_view', 'data_view'
|
||||
);
|
||||
|
||||
-- 保险员角色权限
|
||||
INSERT IGNORE INTO `role_permissions` (`role_id`, `permission_id`)
|
||||
SELECT r.id, p.id FROM `roles` r, `permissions` p
|
||||
WHERE r.name = 'insurer' AND p.name IN (
|
||||
'insurance_view', 'insurance_review', 'insurance_manage',
|
||||
'cattle_view', 'user_view', 'data_view'
|
||||
);
|
||||
|
||||
-- 政府检查员角色权限
|
||||
INSERT IGNORE INTO `role_permissions` (`role_id`, `permission_id`)
|
||||
SELECT r.id, p.id FROM `roles` r, `permissions` p
|
||||
WHERE r.name = 'government_inspector' AND p.name IN (
|
||||
'government_supervision', 'government_inspection', 'quality_trace',
|
||||
'cattle_view', 'user_view', 'policy_view', 'data_view'
|
||||
);
|
||||
|
||||
-- 政府管理员角色权限
|
||||
INSERT IGNORE INTO `role_permissions` (`role_id`, `permission_id`)
|
||||
SELECT r.id, p.id FROM `roles` r, `permissions` p
|
||||
WHERE r.name = 'government_admin' AND p.name IN (
|
||||
'government_supervision', 'government_statistics', 'government_report',
|
||||
'policy_view', 'policy_manage', 'quality_trace',
|
||||
'cattle_view', 'user_view', 'data_view', 'data_export'
|
||||
);
|
||||
|
||||
-- 交易员角色权限
|
||||
INSERT IGNORE INTO `role_permissions` (`role_id`, `permission_id`)
|
||||
SELECT r.id, p.id FROM `roles` r, `permissions` p
|
||||
WHERE r.name = 'trader' AND p.name IN (
|
||||
'transaction_view', 'transaction_create', 'transaction_manage',
|
||||
'contract_view', 'contract_create', 'contract_manage',
|
||||
'cattle_view', 'user_view', 'data_view'
|
||||
);
|
||||
|
||||
-- 商户角色权限
|
||||
INSERT IGNORE INTO `role_permissions` (`role_id`, `permission_id`)
|
||||
SELECT r.id, p.id FROM `roles` r, `permissions` p
|
||||
WHERE r.name = 'merchant' AND p.name IN (
|
||||
'product_view', 'product_create', 'product_edit', 'product_manage',
|
||||
'order_view', 'order_manage', 'mall_statistics',
|
||||
'data_view'
|
||||
);
|
||||
|
||||
-- ======================================
|
||||
-- 3. 测试用户数据
|
||||
-- ======================================
|
||||
|
||||
-- 创建测试用户(密码都是:123456)
|
||||
INSERT IGNORE INTO `users` (`username`, `email`, `phone`, `password_hash`, `real_name`, `user_type`, `status`) VALUES
|
||||
('admin', 'admin@xlxumu.com', '13900000001', '$2b$10$N9qo8uLOickgx2ZMRZoMye1VrVCjhAhLOEI8aSqJZmL4Q9DQKqWV.', '系统管理员', 'admin', 1),
|
||||
('farmer001', 'farmer001@example.com', '13900000002', '$2b$10$N9qo8uLOickgx2ZMRZoMye1VrVCjhAhLOEI8aSqJZmL4Q9DQKqWV.', '张三', 'farmer', 1),
|
||||
('farmer002', 'farmer002@example.com', '13900000003', '$2b$10$N9qo8uLOickgx2ZMRZoMye1VrVCjhAhLOEI8aSqJZmL4Q9DQKqWV.', '李四', 'farmer', 1),
|
||||
('banker001', 'banker001@example.com', '13900000004', '$2b$10$N9qo8uLOickgx2ZMRZoMye1VrVCjhAhLOEI8aSqJZmL4Q9DQKqWV.', '王五', 'banker', 1),
|
||||
('insurer001', 'insurer001@example.com', '13900000005', '$2b$10$N9qo8uLOickgx2ZMRZoMye1VrVCjhAhLOEI8aSqJZmL4Q9DQKqWV.', '赵六', 'insurer', 1),
|
||||
('inspector001', 'inspector001@example.com', '13900000006', '$2b$10$N9qo8uLOickgx2ZMRZoMye1VrVCjhAhLOEI8aSqJZmL4Q9DQKqWV.', '钱七', 'government', 1),
|
||||
('trader001', 'trader001@example.com', '13900000007', '$2b$10$N9qo8uLOickgx2ZMRZoMye1VrVCjhAhLOEI8aSqJZmL4Q9DQKqWV.', '孙八', 'trader', 1),
|
||||
('merchant001', 'merchant001@example.com', '13900000008', '$2b$10$N9qo8uLOickgx2ZMRZoMye1VrVCjhAhLOEI8aSqJZmL4Q9DQKqWV.', '周九', 'trader', 1);
|
||||
|
||||
-- 分配用户角色
|
||||
INSERT IGNORE INTO `user_roles` (`user_id`, `role_id`)
|
||||
SELECT u.id, r.id FROM `users` u, `roles` r
|
||||
WHERE u.username = 'admin' AND r.name = 'admin';
|
||||
|
||||
INSERT IGNORE INTO `user_roles` (`user_id`, `role_id`)
|
||||
SELECT u.id, r.id FROM `users` u, `roles` r
|
||||
WHERE u.username = 'farmer001' AND r.name = 'farmer';
|
||||
|
||||
INSERT IGNORE INTO `user_roles` (`user_id`, `role_id`)
|
||||
SELECT u.id, r.id FROM `users` u, `roles` r
|
||||
WHERE u.username = 'farmer002' AND r.name = 'farmer';
|
||||
|
||||
INSERT IGNORE INTO `user_roles` (`user_id`, `role_id`)
|
||||
SELECT u.id, r.id FROM `users` u, `roles` r
|
||||
WHERE u.username = 'banker001' AND r.name = 'banker';
|
||||
|
||||
INSERT IGNORE INTO `user_roles` (`user_id`, `role_id`)
|
||||
SELECT u.id, r.id FROM `users` u, `roles` r
|
||||
WHERE u.username = 'insurer001' AND r.name = 'insurer';
|
||||
|
||||
INSERT IGNORE INTO `user_roles` (`user_id`, `role_id`)
|
||||
SELECT u.id, r.id FROM `users` u, `roles` r
|
||||
WHERE u.username = 'inspector001' AND r.name = 'government_inspector';
|
||||
|
||||
INSERT IGNORE INTO `user_roles` (`user_id`, `role_id`)
|
||||
SELECT u.id, r.id FROM `users` u, `roles` r
|
||||
WHERE u.username = 'trader001' AND r.name = 'trader';
|
||||
|
||||
INSERT IGNORE INTO `user_roles` (`user_id`, `role_id`)
|
||||
SELECT u.id, r.id FROM `users` u, `roles` r
|
||||
WHERE u.username = 'merchant001' AND r.name = 'merchant';
|
||||
|
||||
-- ======================================
|
||||
-- 4. 示例牧场数据
|
||||
-- ======================================
|
||||
|
||||
INSERT IGNORE INTO `farms` (`farm_name`, `registration_number`, `owner_id`, `legal_representative`, `contact_phone`, `contact_email`, `address`, `region`, `farm_area`, `cattle_capacity`, `current_cattle_count`, `registration_date`, `license_number`, `license_expiry_date`, `status`, `compliance_status`) VALUES
|
||||
('锡林浩特市第一牧场', 'REG2024001', 2, '张三', '13900000002', 'farm001@example.com', '锡林浩特市郊区草原路123号', '锡林浩特市', 150.5, 300, 240, '2023-06-15', 'LIC2023001', '2025-06-15', 'active', 'compliant'),
|
||||
('东乌旗生态牧场', 'REG2024002', 3, '李四', '13900000003', 'farm002@example.com', '东乌旗珠恩嘎达布其镇', '东乌旗', 200.8, 400, 320, '2023-08-20', 'LIC2023002', '2025-08-20', 'active', 'compliant'),
|
||||
('西乌旗示范牧场', 'REG2024003', 2, '张三', '13900000002', 'farm003@example.com', '西乌旗巴拉嘎尔高勒镇', '西乌旗', 300.2, 500, 450, '2023-05-10', 'LIC2023003', '2025-05-10', 'active', 'compliant');
|
||||
|
||||
-- ======================================
|
||||
-- 5. 示例牛只数据
|
||||
-- ======================================
|
||||
|
||||
INSERT IGNORE INTO `cattle` (`ear_tag`, `name`, `breed`, `gender`, `birth_date`, `color`, `weight`, `health_status`, `owner_id`, `farm_location`, `status`) VALUES
|
||||
('C001', '小黄', '西门塔尔牛', 'female', '2022-03-15', '黄色', 450.5, 'healthy', 2, '锡林浩特市第一牧场', 'active'),
|
||||
('C002', '大力', '安格斯牛', 'male', '2021-11-20', '黑色', 620.8, 'healthy', 2, '锡林浩特市第一牧场', 'active'),
|
||||
('C003', '花花', '夏洛莱牛', 'female', '2022-07-08', '白色', 380.2, 'healthy', 3, '东乌旗生态牧场', 'active'),
|
||||
('C004', '壮壮', '利木赞牛', 'male', '2021-09-12', '金黄色', 580.0, 'healthy', 3, '东乌旗生态牧场', 'active'),
|
||||
('C005', '美美', '西门塔尔牛', 'female', '2022-12-25', '棕色', 420.3, 'healthy', 2, '西乌旗示范牧场', 'active');
|
||||
|
||||
-- ======================================
|
||||
-- 6. 示例商品数据
|
||||
-- ======================================
|
||||
|
||||
INSERT IGNORE INTO `products` (`name`, `sku`, `category`, `description`, `price`, `original_price`, `stock`, `weight`, `origin`, `brand`, `seller_id`, `status`, `featured`, `rating`, `review_count`) VALUES
|
||||
('优质牛肉礼盒装', 'BEEF001', 'beef', '来自锡林浩特优质牧场的新鲜牛肉,肉质鲜美,营养丰富', 268.00, 298.00, 45, 2.000, '锡林浩特市第一牧场', '草原优品', 2, 'active', 1, 4.8, 56),
|
||||
('有机牛奶', 'DAIRY001', 'dairy', '纯天然有机牛奶,无添加剂,营养价值高', 35.00, 35.00, 120, 1.000, '东乌旗生态牧场', '草原乳业', 3, 'active', 1, 4.6, 32),
|
||||
('草原牛肉干', 'SNACK001', 'snacks', '传统工艺制作的牛肉干,口感醇香,营养丰富', 68.00, 78.00, 88, 0.500, '西乌旗牧场', '草原食品', 8, 'active', 0, 4.9, 78),
|
||||
('精选牛排', 'BEEF002', 'beef', '精选优质牛排,适合煎烤,肉质鲜嫩', 158.00, 168.00, 25, 1.500, '锡林浩特市第一牧场', '草原优品', 2, 'active', 1, 4.7, 43),
|
||||
('酸奶', 'DAIRY002', 'dairy', '传统发酵工艺制作的酸奶,口感醇厚', 25.00, 25.00, 200, 0.500, '东乌旗生态牧场', '草原乳业', 3, 'active', 0, 4.5, 67);
|
||||
|
||||
SELECT '初始数据插入完成!' AS message;
|
||||
614
backend/database/init_tables.sql
Normal file
614
backend/database/init_tables.sql
Normal file
@@ -0,0 +1,614 @@
|
||||
-- ======================================
|
||||
-- 锡林郭勒盟智慧养殖产业平台 - 数据库初始化脚本
|
||||
-- ======================================
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ======================================
|
||||
-- 1. 用户权限相关表
|
||||
-- ======================================
|
||||
|
||||
-- 用户表
|
||||
CREATE TABLE IF NOT EXISTS `users` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
|
||||
`username` VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名(用于登录)',
|
||||
`email` VARCHAR(100) UNIQUE COMMENT '邮箱(用于通知和找回密码)',
|
||||
`phone` VARCHAR(20) UNIQUE COMMENT '手机号(实名认证用)',
|
||||
`password_hash` VARCHAR(255) NOT NULL COMMENT '密码哈希值(BCrypt加密)',
|
||||
`real_name` VARCHAR(50) COMMENT '真实姓名(需与身份证一致)',
|
||||
`avatar_url` VARCHAR(255) COMMENT '头像URL(OSS存储路径)',
|
||||
`user_type` ENUM('farmer', 'banker', 'insurer', 'government', 'trader', 'admin') NOT NULL COMMENT '用户类型:牧民/银行职员/保险员/政府人员/交易员/管理员',
|
||||
`status` TINYINT DEFAULT 1 COMMENT '状态: 1-正常, 0-禁用(禁用用户无法登录)',
|
||||
`last_login` TIMESTAMP NULL COMMENT '最后登录时间(用于活跃度分析)',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(不可修改)',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间(自动维护)',
|
||||
INDEX `idx_username` (`username`),
|
||||
INDEX `idx_email` (`email`),
|
||||
INDEX `idx_phone` (`phone`),
|
||||
INDEX `idx_user_type` (`user_type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
|
||||
|
||||
-- 角色表
|
||||
CREATE TABLE IF NOT EXISTS `roles` (
|
||||
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '角色ID',
|
||||
`name` VARCHAR(50) NOT NULL UNIQUE COMMENT '角色名称',
|
||||
`description` TEXT COMMENT '角色描述',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
|
||||
|
||||
-- 用户角色关联表
|
||||
CREATE TABLE IF NOT EXISTS `user_roles` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
|
||||
`role_id` INT UNSIGNED NOT NULL COMMENT '角色ID',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE CASCADE,
|
||||
UNIQUE KEY `uk_user_role` (`user_id`, `role_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';
|
||||
|
||||
-- 权限表
|
||||
CREATE TABLE IF NOT EXISTS `permissions` (
|
||||
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '权限ID',
|
||||
`name` VARCHAR(100) NOT NULL UNIQUE COMMENT '权限名称',
|
||||
`description` TEXT COMMENT '权限描述',
|
||||
`module` VARCHAR(50) COMMENT '所属模块',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';
|
||||
|
||||
-- 角色权限关联表
|
||||
CREATE TABLE IF NOT EXISTS `role_permissions` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
`role_id` INT UNSIGNED NOT NULL COMMENT '角色ID',
|
||||
`permission_id` INT UNSIGNED NOT NULL COMMENT '权限ID',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`permission_id`) REFERENCES `permissions`(`id`) ON DELETE CASCADE,
|
||||
UNIQUE KEY `uk_role_permission` (`role_id`, `permission_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限关联表';
|
||||
|
||||
-- ======================================
|
||||
-- 2. 牛只档案相关表
|
||||
-- ======================================
|
||||
|
||||
-- 牛只档案表
|
||||
CREATE TABLE IF NOT EXISTS `cattle` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '牛只ID',
|
||||
`ear_tag` VARCHAR(50) NOT NULL UNIQUE COMMENT '耳标号',
|
||||
`name` VARCHAR(50) COMMENT '名称',
|
||||
`breed` VARCHAR(50) COMMENT '品种',
|
||||
`gender` ENUM('male', 'female') COMMENT '性别',
|
||||
`birth_date` DATE COMMENT '出生日期',
|
||||
`color` VARCHAR(30) COMMENT '毛色',
|
||||
`weight` DECIMAL(5,2) COMMENT '体重(kg)',
|
||||
`health_status` ENUM('healthy', 'sick', 'quarantine', 'dead') DEFAULT 'healthy' COMMENT '健康状况',
|
||||
`owner_id` BIGINT UNSIGNED COMMENT '所有者ID(牧民)',
|
||||
`farm_location` VARCHAR(255) COMMENT '牧场位置',
|
||||
`status` ENUM('active', 'sold', 'dead', 'quarantine') DEFAULT 'active' COMMENT '状态',
|
||||
`image_url` VARCHAR(255) COMMENT '图片URL',
|
||||
`qr_code_url` VARCHAR(255) COMMENT '二维码URL',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`owner_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
|
||||
INDEX `idx_ear_tag` (`ear_tag`),
|
||||
INDEX `idx_owner` (`owner_id`),
|
||||
INDEX `idx_breed` (`breed`),
|
||||
INDEX `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='牛只档案表';
|
||||
|
||||
-- 饲养记录表
|
||||
CREATE TABLE IF NOT EXISTS `feeding_records` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '记录ID',
|
||||
`cattle_id` BIGINT UNSIGNED NOT NULL COMMENT '牛只ID',
|
||||
`record_type` ENUM('feed', 'vaccine', 'treatment', 'checkup') NOT NULL COMMENT '记录类型: 饲料, 疫苗, 治疗, 检查',
|
||||
`feed_type` VARCHAR(100) COMMENT '饲料类型',
|
||||
`feed_amount` DECIMAL(6,2) COMMENT '饲料量(kg)',
|
||||
`vaccine_name` VARCHAR(100) COMMENT '疫苗名称',
|
||||
`treatment_desc` TEXT COMMENT '治疗描述',
|
||||
`medicine_name` VARCHAR(100) COMMENT '药品名称',
|
||||
`dosage` VARCHAR(100) COMMENT '用量',
|
||||
`veterinarian` VARCHAR(50) COMMENT '兽医',
|
||||
`cost` DECIMAL(10,2) COMMENT '费用',
|
||||
`record_date` DATE NOT NULL COMMENT '记录日期',
|
||||
`notes` TEXT COMMENT '备注',
|
||||
`operator_id` BIGINT UNSIGNED COMMENT '操作员ID',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`cattle_id`) REFERENCES `cattle`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`operator_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
|
||||
INDEX `idx_cattle_date` (`cattle_id`, `record_date`),
|
||||
INDEX `idx_record_type` (`record_type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饲养记录表';
|
||||
|
||||
-- 繁殖记录表
|
||||
CREATE TABLE IF NOT EXISTS `breeding_records` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '记录ID',
|
||||
`cattle_id` BIGINT UNSIGNED NOT NULL COMMENT '母牛ID',
|
||||
`breeding_method` ENUM('natural', 'artificial') NOT NULL COMMENT '配种方式',
|
||||
`breeding_date` DATE NOT NULL COMMENT '配种日期',
|
||||
`breeding_male_id` BIGINT UNSIGNED COMMENT '公牛ID',
|
||||
`semen_code` VARCHAR(50) COMMENT '冻精编号',
|
||||
`expected_delivery_date` DATE COMMENT '预产期',
|
||||
`actual_delivery_date` DATE COMMENT '实际产犊日期',
|
||||
`calf_count` TINYINT DEFAULT 1 COMMENT '产犊数',
|
||||
`calf_ids` JSON COMMENT '犊牛IDs',
|
||||
`breeding_result` ENUM('success', 'failed', 'pending') DEFAULT 'pending' COMMENT '配种结果',
|
||||
`notes` TEXT COMMENT '备注',
|
||||
`operator_id` BIGINT UNSIGNED COMMENT '操作员ID',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`cattle_id`) REFERENCES `cattle`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`breeding_male_id`) REFERENCES `cattle`(`id`) ON DELETE SET NULL,
|
||||
FOREIGN KEY (`operator_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
|
||||
INDEX `idx_cattle_date` (`cattle_id`, `breeding_date`),
|
||||
INDEX `idx_result` (`breeding_result`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='繁殖记录表';
|
||||
|
||||
-- ======================================
|
||||
-- 3. 金融业务相关表
|
||||
-- ======================================
|
||||
|
||||
-- 贷款申请表
|
||||
CREATE TABLE IF NOT EXISTS `loan_applications` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '贷款申请ID',
|
||||
`applicant_id` BIGINT UNSIGNED NOT NULL COMMENT '申请人ID',
|
||||
`loan_type` ENUM('cattle', 'farm', 'equipment', 'operating') NOT NULL COMMENT '贷款类型',
|
||||
`cattle_ids` JSON COMMENT '质押牛只IDs',
|
||||
`loan_amount` DECIMAL(15,2) NOT NULL COMMENT '贷款金额',
|
||||
`currency` VARCHAR(10) DEFAULT 'CNY' COMMENT '货币',
|
||||
`interest_rate` DECIMAL(5,4) COMMENT '利率',
|
||||
`term_months` INT COMMENT '期限(月)',
|
||||
`purpose` TEXT COMMENT '用途',
|
||||
`repayment_method` ENUM('equal_principal', 'equal_payment', 'bullet') COMMENT '还款方式',
|
||||
`guarantee_type` ENUM('cattle_pledge', 'guarantor', 'insurance', 'credit') COMMENT '担保方式',
|
||||
`status` ENUM('submitted', 'under_review', 'approved', 'rejected', 'disbursed', 'completed', 'overdue') DEFAULT 'submitted' COMMENT '状态',
|
||||
`reviewer_id` BIGINT UNSIGNED COMMENT '审核人ID',
|
||||
`review_notes` TEXT COMMENT '审核备注',
|
||||
`approved_amount` DECIMAL(15,2) COMMENT '批准金额',
|
||||
`approved_date` TIMESTAMP NULL COMMENT '批准日期',
|
||||
`disbursement_date` TIMESTAMP NULL COMMENT '放款日期',
|
||||
`repayment_schedule` JSON COMMENT '还款计划',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`applicant_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`reviewer_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
|
||||
INDEX `idx_applicant` (`applicant_id`),
|
||||
INDEX `idx_status` (`status`),
|
||||
INDEX `idx_type` (`loan_type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='贷款申请表';
|
||||
|
||||
-- 保险申请表
|
||||
CREATE TABLE IF NOT EXISTS `insurance_applications` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '保险申请ID',
|
||||
`applicant_id` BIGINT UNSIGNED NOT NULL COMMENT '申请人ID',
|
||||
`insurance_type` ENUM('cattle_death', 'cattle_health', 'cattle_theft', 'property') NOT NULL COMMENT '保险类型',
|
||||
`cattle_ids` JSON COMMENT '保险牛只IDs',
|
||||
`policy_number` VARCHAR(50) UNIQUE COMMENT '保单号',
|
||||
`insured_amount` DECIMAL(15,2) COMMENT '保险金额',
|
||||
`premium` DECIMAL(12,2) COMMENT '保费',
|
||||
`currency` VARCHAR(10) DEFAULT 'CNY' COMMENT '货币',
|
||||
`start_date` DATE COMMENT '起保日期',
|
||||
`end_date` DATE COMMENT '终保日期',
|
||||
`status` ENUM('applied', 'underwriting', 'issued', 'active', 'expired', 'cancelled', 'claiming', 'settled') DEFAULT 'applied' COMMENT '状态',
|
||||
`underwriter_id` BIGINT UNSIGNED COMMENT '核保人ID',
|
||||
`underwriting_notes` TEXT COMMENT '核保备注',
|
||||
`policy_file_url` VARCHAR(255) COMMENT '保单文件URL',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`applicant_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`underwriter_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
|
||||
INDEX `idx_applicant` (`applicant_id`),
|
||||
INDEX `idx_policy_number` (`policy_number`),
|
||||
INDEX `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='保险申请表';
|
||||
|
||||
-- 理赔申请表
|
||||
CREATE TABLE IF NOT EXISTS `claims` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '理赔申请ID',
|
||||
`insurance_id` BIGINT UNSIGNED NOT NULL COMMENT '保险ID',
|
||||
`applicant_id` BIGINT UNSIGNED NOT NULL COMMENT '申请人ID',
|
||||
`claim_amount` DECIMAL(12,2) NOT NULL COMMENT '理赔金额',
|
||||
`currency` VARCHAR(10) DEFAULT 'CNY' COMMENT '货币',
|
||||
`incident_date` DATE NOT NULL COMMENT '事故日期',
|
||||
`incident_type` ENUM('death', 'illness', 'accident', 'theft') NOT NULL COMMENT '事故类型',
|
||||
`description` TEXT COMMENT '事故描述',
|
||||
`evidence_files` JSON COMMENT '证据文件URL列表',
|
||||
`status` ENUM('submitted', 'under_review', 'approved', 'rejected', 'paid') DEFAULT 'submitted' COMMENT '状态',
|
||||
`reviewer_id` BIGINT UNSIGNED COMMENT '审核人ID',
|
||||
`review_notes` TEXT COMMENT '审核备注',
|
||||
`approved_amount` DECIMAL(12,2) COMMENT '批准金额',
|
||||
`paid_amount` DECIMAL(12,2) COMMENT '赔付金额',
|
||||
`submitted_at` TIMESTAMP NULL COMMENT '提交时间',
|
||||
`reviewed_at` TIMESTAMP NULL COMMENT '审核时间',
|
||||
`approved_at` TIMESTAMP NULL COMMENT '批准时间',
|
||||
`paid_at` TIMESTAMP NULL COMMENT '赔付时间',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`insurance_id`) REFERENCES `insurance_applications`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`applicant_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`reviewer_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
|
||||
INDEX `idx_insurance` (`insurance_id`),
|
||||
INDEX `idx_applicant` (`applicant_id`),
|
||||
INDEX `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='理赔申请表';
|
||||
|
||||
-- ======================================
|
||||
-- 4. 交易管理相关表
|
||||
-- ======================================
|
||||
|
||||
-- 交易记录表
|
||||
CREATE TABLE IF NOT EXISTS `transactions` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '交易ID',
|
||||
`transaction_number` VARCHAR(50) UNIQUE NOT NULL COMMENT '交易编号',
|
||||
`transaction_type` ENUM('cattle_sale', 'feed_purchase', 'equipment_sale', 'service') NOT NULL COMMENT '交易类型',
|
||||
`buyer_id` BIGINT UNSIGNED NOT NULL COMMENT '买方ID',
|
||||
`seller_id` BIGINT UNSIGNED NOT NULL COMMENT '卖方ID',
|
||||
`cattle_ids` JSON COMMENT '交易牛只IDs',
|
||||
`product_name` VARCHAR(200) COMMENT '商品名称',
|
||||
`quantity` DECIMAL(10,2) COMMENT '数量',
|
||||
`unit` VARCHAR(20) COMMENT '单位',
|
||||
`unit_price` DECIMAL(12,2) COMMENT '单价',
|
||||
`total_amount` DECIMAL(15,2) NOT NULL COMMENT '总金额',
|
||||
`currency` VARCHAR(10) DEFAULT 'CNY' COMMENT '货币',
|
||||
`payment_method` ENUM('cash', 'bank_transfer', 'installment', 'check') COMMENT '付款方式',
|
||||
`payment_status` ENUM('pending', 'paid', 'partial', 'overdue') DEFAULT 'pending' COMMENT '付款状态',
|
||||
`delivery_method` ENUM('pickup', 'delivery', 'installation') COMMENT '交付方式',
|
||||
`delivery_address` TEXT COMMENT '交付地址',
|
||||
`delivery_date` TIMESTAMP NULL COMMENT '交付日期',
|
||||
`delivery_status` ENUM('pending', 'in_transit', 'delivered', 'cancelled') DEFAULT 'pending' COMMENT '交付状态',
|
||||
`status` ENUM('pending', 'confirmed', 'in_progress', 'completed', 'cancelled', 'refunded') DEFAULT 'pending' COMMENT '交易状态',
|
||||
`contract_id` BIGINT UNSIGNED COMMENT '合同ID',
|
||||
`notes` TEXT COMMENT '备注',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`buyer_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT,
|
||||
FOREIGN KEY (`seller_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT,
|
||||
INDEX `idx_transaction_number` (`transaction_number`),
|
||||
INDEX `idx_buyer` (`buyer_id`),
|
||||
INDEX `idx_seller` (`seller_id`),
|
||||
INDEX `idx_status` (`status`),
|
||||
INDEX `idx_type` (`transaction_type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='交易记录表';
|
||||
|
||||
-- 合同表
|
||||
CREATE TABLE IF NOT EXISTS `contracts` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '合同ID',
|
||||
`contract_number` VARCHAR(50) UNIQUE NOT NULL COMMENT '合同编号',
|
||||
`contract_type` ENUM('cattle_sale', 'feed_supply', 'equipment_purchase', 'service') NOT NULL COMMENT '合同类型',
|
||||
`party_a_id` BIGINT UNSIGNED NOT NULL COMMENT '甲方ID',
|
||||
`party_b_id` BIGINT UNSIGNED NOT NULL COMMENT '乙方ID',
|
||||
`contract_amount` DECIMAL(15,2) NOT NULL COMMENT '合同金额',
|
||||
`currency` VARCHAR(10) DEFAULT 'CNY' COMMENT '货币',
|
||||
`signing_date` DATE COMMENT '签订日期',
|
||||
`effective_date` DATE COMMENT '生效日期',
|
||||
`expiry_date` DATE COMMENT '到期日期',
|
||||
`payment_terms` TEXT COMMENT '付款条款',
|
||||
`delivery_terms` TEXT COMMENT '交付条款',
|
||||
`status` ENUM('draft', 'active', 'completed', 'cancelled', 'expired') DEFAULT 'draft' COMMENT '合同状态',
|
||||
`contract_content` LONGTEXT COMMENT '合同内容',
|
||||
`contract_url` VARCHAR(255) COMMENT '合同文件URL',
|
||||
`digital_signature_a` TEXT COMMENT '甲方数字签名',
|
||||
`digital_signature_b` TEXT COMMENT '乙方数字签名',
|
||||
`witness_id` BIGINT UNSIGNED COMMENT '见证人ID',
|
||||
`notes` TEXT COMMENT '备注',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`party_a_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT,
|
||||
FOREIGN KEY (`party_b_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT,
|
||||
FOREIGN KEY (`witness_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
|
||||
INDEX `idx_contract_number` (`contract_number`),
|
||||
INDEX `idx_party_a` (`party_a_id`),
|
||||
INDEX `idx_party_b` (`party_b_id`),
|
||||
INDEX `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同表';
|
||||
|
||||
-- ======================================
|
||||
-- 5. 政府监管相关表
|
||||
-- ======================================
|
||||
|
||||
-- 牧场注册表
|
||||
CREATE TABLE IF NOT EXISTS `farms` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '牧场ID',
|
||||
`farm_name` VARCHAR(100) NOT NULL COMMENT '牧场名称',
|
||||
`registration_number` VARCHAR(50) UNIQUE NOT NULL COMMENT '注册编号',
|
||||
`owner_id` BIGINT UNSIGNED NOT NULL COMMENT '所有者ID',
|
||||
`legal_representative` VARCHAR(50) COMMENT '法定代表人',
|
||||
`contact_phone` VARCHAR(20) COMMENT '联系电话',
|
||||
`contact_email` VARCHAR(100) COMMENT '联系邮箱',
|
||||
`address` TEXT COMMENT '详细地址',
|
||||
`region` VARCHAR(50) COMMENT '所属区域',
|
||||
`coordinates` POINT COMMENT '经纬度坐标',
|
||||
`farm_area` DECIMAL(10,2) COMMENT '牧场面积(亩)',
|
||||
`cattle_capacity` INT COMMENT '牲畜容量',
|
||||
`current_cattle_count` INT DEFAULT 0 COMMENT '当前牲畜数量',
|
||||
`registration_date` DATE COMMENT '注册日期',
|
||||
`license_number` VARCHAR(50) COMMENT '许可证号',
|
||||
`license_expiry_date` DATE COMMENT '许可证到期日期',
|
||||
`status` ENUM('active', 'inactive', 'suspended', 'cancelled') DEFAULT 'active' COMMENT '状态',
|
||||
`compliance_status` ENUM('compliant', 'warning', 'violation', 'pending') DEFAULT 'pending' COMMENT '合规状态',
|
||||
`last_inspection_date` DATE COMMENT '最后检查日期',
|
||||
`next_inspection_date` DATE COMMENT '下次检查日期',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`owner_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT,
|
||||
INDEX `idx_registration_number` (`registration_number`),
|
||||
INDEX `idx_owner` (`owner_id`),
|
||||
INDEX `idx_region` (`region`),
|
||||
INDEX `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='牧场注册表';
|
||||
|
||||
-- 政府检查记录表
|
||||
CREATE TABLE IF NOT EXISTS `government_inspections` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '检查记录ID',
|
||||
`farm_id` BIGINT UNSIGNED NOT NULL COMMENT '牧场ID',
|
||||
`inspector_id` BIGINT UNSIGNED NOT NULL COMMENT '检查员ID',
|
||||
`inspection_type` ENUM('routine', 'follow_up', 'complaint', 'emergency') NOT NULL COMMENT '检查类型',
|
||||
`inspection_date` DATE NOT NULL COMMENT '检查日期',
|
||||
`inspection_scope` JSON COMMENT '检查范围',
|
||||
`checklist` JSON COMMENT '检查清单',
|
||||
`score` DECIMAL(5,2) COMMENT '检查评分',
|
||||
`result` ENUM('passed', 'conditional_pass', 'failed') COMMENT '检查结果',
|
||||
`violations` JSON COMMENT '违规事项',
|
||||
`improvements` JSON COMMENT '改进建议',
|
||||
`corrective_actions` JSON COMMENT '整改要求',
|
||||
`next_inspection_date` DATE COMMENT '下次检查日期',
|
||||
`report_file_url` VARCHAR(255) COMMENT '检查报告文件URL',
|
||||
`photos` JSON COMMENT '检查照片URLs',
|
||||
`inspector_notes` TEXT COMMENT '检查员备注',
|
||||
`farm_response` TEXT COMMENT '牧场回应',
|
||||
`status` ENUM('completed', 'pending_correction', 'follow_up_required') DEFAULT 'completed' COMMENT '状态',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`farm_id`) REFERENCES `farms`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`inspector_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT,
|
||||
INDEX `idx_farm_date` (`farm_id`, `inspection_date`),
|
||||
INDEX `idx_inspector` (`inspector_id`),
|
||||
INDEX `idx_result` (`result`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='政府检查记录表';
|
||||
|
||||
-- 政策法规表
|
||||
CREATE TABLE IF NOT EXISTS `policies` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '政策ID',
|
||||
`title` VARCHAR(200) NOT NULL COMMENT '政策标题',
|
||||
`policy_number` VARCHAR(50) UNIQUE COMMENT '政策编号',
|
||||
`category` ENUM('regulation', 'support_policy', 'subsidy', 'standard', 'guideline') NOT NULL COMMENT '政策类别',
|
||||
`authority` VARCHAR(100) COMMENT '发布机关',
|
||||
`content_summary` TEXT COMMENT '内容摘要',
|
||||
`content_detail` LONGTEXT COMMENT '详细内容',
|
||||
`document_url` VARCHAR(255) COMMENT '文档URL',
|
||||
`publish_date` DATE COMMENT '发布日期',
|
||||
`effective_date` DATE COMMENT '生效日期',
|
||||
`expiry_date` DATE COMMENT '失效日期',
|
||||
`status` ENUM('draft', 'active', 'expired', 'repealed') DEFAULT 'draft' COMMENT '状态',
|
||||
`target_audience` JSON COMMENT '适用对象',
|
||||
`keywords` VARCHAR(500) COMMENT '关键词',
|
||||
`created_by` BIGINT UNSIGNED COMMENT '创建人ID',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`created_by`) REFERENCES `users`(`id`) ON DELETE SET NULL,
|
||||
INDEX `idx_policy_number` (`policy_number`),
|
||||
INDEX `idx_category` (`category`),
|
||||
INDEX `idx_status` (`status`),
|
||||
FULLTEXT `idx_keywords` (`title`, `content_summary`, `keywords`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='政策法规表';
|
||||
|
||||
-- ======================================
|
||||
-- 6. 商城相关表
|
||||
-- ======================================
|
||||
|
||||
-- 商品表
|
||||
CREATE TABLE IF NOT EXISTS `products` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '商品ID',
|
||||
`name` VARCHAR(200) NOT NULL COMMENT '商品名称',
|
||||
`sku` VARCHAR(50) UNIQUE COMMENT '商品SKU',
|
||||
`category` ENUM('beef', 'dairy', 'snacks', 'processed', 'equipment', 'feed', 'other') NOT NULL COMMENT '商品类别',
|
||||
`subcategory` VARCHAR(50) COMMENT '子类别',
|
||||
`description` TEXT COMMENT '商品描述',
|
||||
`price` DECIMAL(10,2) NOT NULL COMMENT '价格',
|
||||
`original_price` DECIMAL(10,2) COMMENT '原价',
|
||||
`cost_price` DECIMAL(10,2) COMMENT '成本价',
|
||||
`currency` VARCHAR(10) DEFAULT 'CNY' COMMENT '货币',
|
||||
`stock` INT DEFAULT 0 COMMENT '库存数量',
|
||||
`sales_count` INT DEFAULT 0 COMMENT '销售数量',
|
||||
`weight` DECIMAL(8,3) COMMENT '重量(kg)',
|
||||
`dimensions` VARCHAR(100) COMMENT '尺寸',
|
||||
`shelf_life` INT COMMENT '保质期(天)',
|
||||
`storage_conditions` VARCHAR(200) COMMENT '储存条件',
|
||||
`origin` VARCHAR(100) COMMENT '产地',
|
||||
`brand` VARCHAR(100) COMMENT '品牌',
|
||||
`specifications` JSON COMMENT '规格参数',
|
||||
`images` JSON COMMENT '商品图片URLs',
|
||||
`video_url` VARCHAR(255) COMMENT '视频URL',
|
||||
`seller_id` BIGINT UNSIGNED NOT NULL COMMENT '卖家ID',
|
||||
`status` ENUM('active', 'inactive', 'out_of_stock', 'pending_review', 'rejected') DEFAULT 'pending_review' COMMENT '状态',
|
||||
`featured` TINYINT DEFAULT 0 COMMENT '是否推荐',
|
||||
`rating` DECIMAL(3,2) DEFAULT 0 COMMENT '评分',
|
||||
`review_count` INT DEFAULT 0 COMMENT '评价数量',
|
||||
`seo_title` VARCHAR(200) COMMENT 'SEO标题',
|
||||
`seo_description` TEXT COMMENT 'SEO描述',
|
||||
`seo_keywords` VARCHAR(500) COMMENT 'SEO关键词',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`seller_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT,
|
||||
INDEX `idx_sku` (`sku`),
|
||||
INDEX `idx_category` (`category`),
|
||||
INDEX `idx_seller` (`seller_id`),
|
||||
INDEX `idx_status` (`status`),
|
||||
INDEX `idx_featured` (`featured`),
|
||||
FULLTEXT `idx_search` (`name`, `description`, `seo_keywords`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
|
||||
|
||||
-- 订单表
|
||||
CREATE TABLE IF NOT EXISTS `orders` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '订单ID',
|
||||
`order_number` VARCHAR(50) UNIQUE NOT NULL COMMENT '订单号',
|
||||
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
|
||||
`order_type` ENUM('normal', 'group_buy', 'presale', 'custom') DEFAULT 'normal' COMMENT '订单类型',
|
||||
`total_amount` DECIMAL(12,2) NOT NULL COMMENT '商品总金额',
|
||||
`discount_amount` DECIMAL(12,2) DEFAULT 0 COMMENT '优惠金额',
|
||||
`shipping_fee` DECIMAL(10,2) DEFAULT 0 COMMENT '运费',
|
||||
`tax_amount` DECIMAL(10,2) DEFAULT 0 COMMENT '税费',
|
||||
`final_amount` DECIMAL(12,2) NOT NULL COMMENT '最终金额',
|
||||
`currency` VARCHAR(10) DEFAULT 'CNY' COMMENT '货币',
|
||||
`payment_method` ENUM('wechat_pay', 'alipay', 'bank_transfer', 'cash_on_delivery') COMMENT '支付方式',
|
||||
`payment_status` ENUM('pending', 'paid', 'partial', 'refunded', 'failed') DEFAULT 'pending' COMMENT '支付状态',
|
||||
`shipping_method` ENUM('express', 'self_pickup', 'same_city') COMMENT '配送方式',
|
||||
`shipping_address` TEXT COMMENT '收货地址',
|
||||
`shipping_name` VARCHAR(50) COMMENT '收货人姓名',
|
||||
`shipping_phone` VARCHAR(20) COMMENT '收货人电话',
|
||||
`tracking_number` VARCHAR(100) COMMENT '快递单号',
|
||||
`status` ENUM('pending_payment', 'paid', 'processing', 'shipping', 'delivered', 'completed', 'cancelled', 'refunded') DEFAULT 'pending_payment' COMMENT '订单状态',
|
||||
`coupon_code` VARCHAR(50) COMMENT '优惠券代码',
|
||||
`notes` TEXT COMMENT '订单备注',
|
||||
`order_date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '下单时间',
|
||||
`payment_date` TIMESTAMP NULL COMMENT '支付时间',
|
||||
`shipping_date` TIMESTAMP NULL COMMENT '发货时间',
|
||||
`delivery_date` TIMESTAMP NULL COMMENT '收货时间',
|
||||
`completion_date` TIMESTAMP NULL COMMENT '完成时间',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT,
|
||||
INDEX `idx_order_number` (`order_number`),
|
||||
INDEX `idx_user` (`user_id`),
|
||||
INDEX `idx_status` (`status`),
|
||||
INDEX `idx_payment_status` (`payment_status`),
|
||||
INDEX `idx_order_date` (`order_date`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
|
||||
|
||||
-- 订单商品表
|
||||
CREATE TABLE IF NOT EXISTS `order_items` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '订单商品ID',
|
||||
`order_id` BIGINT UNSIGNED NOT NULL COMMENT '订单ID',
|
||||
`product_id` BIGINT UNSIGNED NOT NULL COMMENT '商品ID',
|
||||
`product_name` VARCHAR(200) NOT NULL COMMENT '商品名称快照',
|
||||
`product_sku` VARCHAR(50) COMMENT '商品SKU快照',
|
||||
`product_image` VARCHAR(255) COMMENT '商品图片快照',
|
||||
`unit_price` DECIMAL(10,2) NOT NULL COMMENT '单价',
|
||||
`quantity` INT NOT NULL COMMENT '数量',
|
||||
`total_price` DECIMAL(12,2) NOT NULL COMMENT '小计',
|
||||
`currency` VARCHAR(10) DEFAULT 'CNY' COMMENT '货币',
|
||||
`specifications` JSON COMMENT '规格参数快照',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
FOREIGN KEY (`order_id`) REFERENCES `orders`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`product_id`) REFERENCES `products`(`id`) ON DELETE RESTRICT,
|
||||
INDEX `idx_order` (`order_id`),
|
||||
INDEX `idx_product` (`product_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单商品表';
|
||||
|
||||
-- 商品评价表
|
||||
CREATE TABLE IF NOT EXISTS `product_reviews` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '评价ID',
|
||||
`product_id` BIGINT UNSIGNED NOT NULL COMMENT '商品ID',
|
||||
`order_id` BIGINT UNSIGNED NOT NULL COMMENT '订单ID',
|
||||
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
|
||||
`rating` TINYINT NOT NULL COMMENT '评分(1-5)',
|
||||
`content` TEXT COMMENT '评价内容',
|
||||
`images` JSON COMMENT '评价图片URLs',
|
||||
`helpful_count` INT DEFAULT 0 COMMENT '有用数',
|
||||
`reply_content` TEXT COMMENT '商家回复',
|
||||
`reply_date` TIMESTAMP NULL COMMENT '回复时间',
|
||||
`status` ENUM('active', 'hidden', 'deleted') DEFAULT 'active' COMMENT '状态',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`product_id`) REFERENCES `products`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`order_id`) REFERENCES `orders`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
|
||||
INDEX `idx_product` (`product_id`),
|
||||
INDEX `idx_user` (`user_id`),
|
||||
INDEX `idx_rating` (`rating`),
|
||||
INDEX `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品评价表';
|
||||
|
||||
-- ======================================
|
||||
-- 7. 质量追溯相关表
|
||||
-- ======================================
|
||||
|
||||
-- 产品追溯表
|
||||
CREATE TABLE IF NOT EXISTS `product_traceability` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '追溯ID',
|
||||
`product_id` BIGINT UNSIGNED COMMENT '商品ID',
|
||||
`batch_number` VARCHAR(50) UNIQUE NOT NULL COMMENT '批次号',
|
||||
`cattle_ids` JSON COMMENT '源头牛只IDs',
|
||||
`farm_id` BIGINT UNSIGNED COMMENT '来源牧场ID',
|
||||
`slaughter_date` DATE COMMENT '屠宰日期',
|
||||
`slaughterhouse` VARCHAR(100) COMMENT '屠宰场',
|
||||
`processing_date` DATE COMMENT '加工日期',
|
||||
`processor` VARCHAR(100) COMMENT '加工商',
|
||||
`packaging_date` DATE COMMENT '包装日期',
|
||||
`quality_certificates` JSON COMMENT '质量证书URLs',
|
||||
`inspection_reports` JSON COMMENT '检验报告URLs',
|
||||
`transportation_records` JSON COMMENT '运输记录',
|
||||
`storage_conditions` JSON COMMENT '储存条件记录',
|
||||
`chain_of_custody` JSON COMMENT '监管链记录',
|
||||
`qr_code` VARCHAR(255) COMMENT '二维码内容',
|
||||
`blockchain_hash` VARCHAR(255) COMMENT '区块链哈希值',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`product_id`) REFERENCES `products`(`id`) ON DELETE SET NULL,
|
||||
FOREIGN KEY (`farm_id`) REFERENCES `farms`(`id`) ON DELETE SET NULL,
|
||||
INDEX `idx_batch_number` (`batch_number`),
|
||||
INDEX `idx_product` (`product_id`),
|
||||
INDEX `idx_farm` (`farm_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='产品追溯表';
|
||||
|
||||
-- ======================================
|
||||
-- 8. 系统日志相关表
|
||||
-- ======================================
|
||||
|
||||
-- 操作日志表
|
||||
CREATE TABLE IF NOT EXISTS `operation_logs` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '日志ID',
|
||||
`user_id` BIGINT UNSIGNED COMMENT '操作用户ID',
|
||||
`operation_type` VARCHAR(50) NOT NULL COMMENT '操作类型',
|
||||
`operation_module` VARCHAR(50) NOT NULL COMMENT '操作模块',
|
||||
`operation_desc` TEXT COMMENT '操作描述',
|
||||
`request_method` VARCHAR(10) COMMENT '请求方法',
|
||||
`request_url` VARCHAR(500) COMMENT '请求URL',
|
||||
`request_params` JSON COMMENT '请求参数',
|
||||
`response_code` INT COMMENT '响应状态码',
|
||||
`response_message` TEXT COMMENT '响应消息',
|
||||
`ip_address` VARCHAR(45) COMMENT 'IP地址',
|
||||
`user_agent` TEXT COMMENT '用户代理',
|
||||
`execution_time` INT COMMENT '执行时间(毫秒)',
|
||||
`success` TINYINT DEFAULT 1 COMMENT '是否成功',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
|
||||
INDEX `idx_user` (`user_id`),
|
||||
INDEX `idx_type` (`operation_type`),
|
||||
INDEX `idx_module` (`operation_module`),
|
||||
INDEX `idx_created_at` (`created_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='操作日志表';
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
-- ======================================
|
||||
-- 插入初始数据
|
||||
-- ======================================
|
||||
|
||||
-- 插入默认角色
|
||||
INSERT IGNORE INTO `roles` (`name`, `description`) VALUES
|
||||
('admin', '系统管理员'),
|
||||
('farmer', '养殖户'),
|
||||
('banker', '银行职员'),
|
||||
('insurer', '保险员'),
|
||||
('government', '政府监管人员'),
|
||||
('trader', '交易员');
|
||||
|
||||
-- 插入默认权限
|
||||
INSERT IGNORE INTO `permissions` (`name`, `description`, `module`) VALUES
|
||||
('user_manage', '用户管理', 'user'),
|
||||
('cattle_manage', '牛只管理', 'cattle'),
|
||||
('loan_manage', '贷款管理', 'loan'),
|
||||
('insurance_manage', '保险管理', 'insurance'),
|
||||
('trade_manage', '交易管理', 'trade'),
|
||||
('government_supervise', '政府监管', 'government'),
|
||||
('data_view', '数据查看', 'data'),
|
||||
('system_config', '系统配置', 'system');
|
||||
|
||||
-- 插入默认管理员用户 (密码: admin123)
|
||||
INSERT IGNORE INTO `users` (`username`, `email`, `password_hash`, `real_name`, `user_type`, `status`) VALUES
|
||||
('admin', 'admin@xlxumu.com', '$2b$10$8K1p/a0dFd2XeyGWm7S9me5qHEF1K/ZEGPmU0ISGwXc7hdsXkn8ZO', '系统管理员', 'admin', 1);
|
||||
|
||||
SELECT '数据库表结构创建完成!' AS message;
|
||||
287
backend/database/init_tables_extended.sql
Normal file
287
backend/database/init_tables_extended.sql
Normal file
@@ -0,0 +1,287 @@
|
||||
-- ======================================
|
||||
-- 锡林郭勒盟智慧养殖产业平台 - 数据库初始化脚本(第二部分)
|
||||
-- 交易系统、商城管理、政府监管相关表
|
||||
-- ======================================
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ======================================
|
||||
-- 4. 交易系统相关表
|
||||
-- ======================================
|
||||
|
||||
-- 合同表
|
||||
CREATE TABLE IF NOT EXISTS `contracts` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '合同ID',
|
||||
`contract_number` VARCHAR(50) NOT NULL UNIQUE COMMENT '合同编号',
|
||||
`seller_id` BIGINT UNSIGNED NOT NULL COMMENT '卖方ID',
|
||||
`buyer_id` BIGINT UNSIGNED NOT NULL COMMENT '买方ID',
|
||||
`cattle_details` JSON COMMENT '牛只详情',
|
||||
`total_price` DECIMAL(15,2) NOT NULL COMMENT '总价',
|
||||
`currency` VARCHAR(10) DEFAULT 'CNY' COMMENT '货币',
|
||||
`contract_date` DATE NOT NULL COMMENT '合同日期',
|
||||
`effective_date` DATE COMMENT '生效日期',
|
||||
`expiry_date` DATE COMMENT '到期日期',
|
||||
`payment_terms` TEXT COMMENT '付款条款',
|
||||
`delivery_terms` TEXT COMMENT '交付条款',
|
||||
`contract_status` ENUM('draft', 'signed', 'active', 'completed', 'cancelled') DEFAULT 'draft' COMMENT '合同状态',
|
||||
`contract_file_url` VARCHAR(255) COMMENT '合同文件URL',
|
||||
`notes` TEXT COMMENT '备注',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`seller_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`buyer_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
|
||||
INDEX `idx_contract_number` (`contract_number`),
|
||||
INDEX `idx_seller` (`seller_id`),
|
||||
INDEX `idx_buyer` (`buyer_id`),
|
||||
INDEX `idx_status` (`contract_status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同表';
|
||||
|
||||
-- 交易记录表
|
||||
CREATE TABLE IF NOT EXISTS `transactions` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '交易ID',
|
||||
`cattle_id` BIGINT UNSIGNED NOT NULL COMMENT '牛只ID',
|
||||
`seller_id` BIGINT UNSIGNED NOT NULL COMMENT '卖方ID',
|
||||
`buyer_id` BIGINT UNSIGNED NOT NULL COMMENT '买方ID',
|
||||
`transaction_type` ENUM('direct', 'auction', 'platform') NOT NULL COMMENT '交易类型',
|
||||
`price` DECIMAL(12,2) NOT NULL COMMENT '交易价格',
|
||||
`currency` VARCHAR(10) DEFAULT 'CNY' COMMENT '货币',
|
||||
`transaction_date` DATETIME NOT NULL COMMENT '交易时间',
|
||||
`contract_id` BIGINT UNSIGNED COMMENT '合同ID',
|
||||
`status` ENUM('pending', 'completed', 'cancelled') DEFAULT 'pending' COMMENT '状态',
|
||||
`payment_status` ENUM('unpaid', 'partial', 'paid') DEFAULT 'unpaid' COMMENT '付款状态',
|
||||
`delivery_status` ENUM('pending', 'in_transit', 'delivered') DEFAULT 'pending' COMMENT '交付状态',
|
||||
`notes` TEXT COMMENT '备注',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`cattle_id`) REFERENCES `cattle`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`seller_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`buyer_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`contract_id`) REFERENCES `contracts`(`id`) ON DELETE SET NULL,
|
||||
INDEX `idx_cattle_date` (`cattle_id`, `transaction_date`),
|
||||
INDEX `idx_seller` (`seller_id`),
|
||||
INDEX `idx_buyer` (`buyer_id`),
|
||||
INDEX `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='交易记录表';
|
||||
|
||||
-- ======================================
|
||||
-- 5. 商城管理相关表
|
||||
-- ======================================
|
||||
|
||||
-- 商品分类表
|
||||
CREATE TABLE IF NOT EXISTS `product_categories` (
|
||||
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '分类ID',
|
||||
`name` VARCHAR(50) NOT NULL COMMENT '分类名称',
|
||||
`parent_id` INT UNSIGNED DEFAULT 0 COMMENT '父分类ID',
|
||||
`level` TINYINT DEFAULT 1 COMMENT '层级',
|
||||
`sort_order` INT DEFAULT 0 COMMENT '排序',
|
||||
`image_url` VARCHAR(255) COMMENT '图片URL',
|
||||
`status` TINYINT DEFAULT 1 COMMENT '状态: 1-正常, 0-禁用',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
INDEX `idx_parent` (`parent_id`),
|
||||
INDEX `idx_level` (`level`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品分类表';
|
||||
|
||||
-- 商品表
|
||||
CREATE TABLE IF NOT EXISTS `products` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '商品ID',
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '商品名称',
|
||||
`description` TEXT COMMENT '商品描述',
|
||||
`category_id` INT UNSIGNED COMMENT '分类ID',
|
||||
`sku` VARCHAR(50) UNIQUE COMMENT 'SKU',
|
||||
`price` DECIMAL(10,2) NOT NULL COMMENT '价格',
|
||||
`original_price` DECIMAL(10,2) COMMENT '原价',
|
||||
`currency` VARCHAR(10) DEFAULT 'CNY' COMMENT '货币',
|
||||
`stock_quantity` INT DEFAULT 0 COMMENT '库存数量',
|
||||
`min_stock` INT DEFAULT 0 COMMENT '最低库存',
|
||||
`unit` VARCHAR(20) COMMENT '单位',
|
||||
`weight` DECIMAL(8,3) COMMENT '重量(kg)',
|
||||
`origin` VARCHAR(100) COMMENT '产地',
|
||||
`production_date` DATE COMMENT '生产日期',
|
||||
`expiration_date` DATE COMMENT '保质期',
|
||||
`cattle_id` BIGINT UNSIGNED COMMENT '关联牛只ID',
|
||||
`status` ENUM('active', 'inactive', 'discontinued') DEFAULT 'active' COMMENT '状态',
|
||||
`image_urls` JSON COMMENT '图片URL列表',
|
||||
`tags` JSON COMMENT '标签',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`cattle_id`) REFERENCES `cattle`(`id`) ON DELETE SET NULL,
|
||||
INDEX `idx_name` (`name`),
|
||||
INDEX `idx_category` (`category_id`),
|
||||
INDEX `idx_sku` (`sku`),
|
||||
INDEX `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
|
||||
|
||||
-- 订单表
|
||||
CREATE TABLE IF NOT EXISTS `orders` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '订单ID',
|
||||
`order_number` VARCHAR(50) NOT NULL UNIQUE COMMENT '订单编号',
|
||||
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
|
||||
`total_amount` DECIMAL(12,2) NOT NULL COMMENT '订单总额',
|
||||
`currency` VARCHAR(10) DEFAULT 'CNY' COMMENT '货币',
|
||||
`order_status` ENUM('pending', 'confirmed', 'processing', 'shipped', 'delivered', 'cancelled', 'refunded') DEFAULT 'pending' COMMENT '订单状态',
|
||||
`payment_status` ENUM('unpaid', 'partial', 'paid') DEFAULT 'unpaid' COMMENT '付款状态',
|
||||
`shipping_status` ENUM('unshipped', 'shipped', 'delivered') DEFAULT 'unshipped' COMMENT '发货状态',
|
||||
`receiver_name` VARCHAR(50) COMMENT '收货人姓名',
|
||||
`receiver_phone` VARCHAR(20) COMMENT '收货人电话',
|
||||
`receiver_address` TEXT COMMENT '收货地址',
|
||||
`shipping_method` VARCHAR(50) COMMENT '配送方式',
|
||||
`shipping_fee` DECIMAL(10,2) DEFAULT 0.00 COMMENT '运费',
|
||||
`notes` TEXT COMMENT '备注',
|
||||
`payment_method` VARCHAR(50) COMMENT '付款方式',
|
||||
`paid_at` TIMESTAMP NULL COMMENT '付款时间',
|
||||
`shipped_at` TIMESTAMP NULL COMMENT '发货时间',
|
||||
`delivered_at` TIMESTAMP NULL COMMENT '送达时间',
|
||||
`cancelled_at` TIMESTAMP NULL COMMENT '取消时间',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
|
||||
INDEX `idx_order_number` (`order_number`),
|
||||
INDEX `idx_user` (`user_id`),
|
||||
INDEX `idx_status` (`order_status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
|
||||
|
||||
-- 订单项表
|
||||
CREATE TABLE IF NOT EXISTS `order_items` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '订单项ID',
|
||||
`order_id` BIGINT UNSIGNED NOT NULL COMMENT '订单ID',
|
||||
`product_id` BIGINT UNSIGNED NOT NULL COMMENT '商品ID',
|
||||
`quantity` INT NOT NULL COMMENT '数量',
|
||||
`unit_price` DECIMAL(10,2) NOT NULL COMMENT '单价',
|
||||
`total_price` DECIMAL(12,2) NOT NULL COMMENT '总价',
|
||||
`cattle_id` BIGINT UNSIGNED COMMENT '关联牛只ID',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`order_id`) REFERENCES `orders`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`product_id`) REFERENCES `products`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`cattle_id`) REFERENCES `cattle`(`id`) ON DELETE SET NULL,
|
||||
INDEX `idx_order` (`order_id`),
|
||||
INDEX `idx_product` (`product_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单项表';
|
||||
|
||||
-- 物流跟踪表
|
||||
CREATE TABLE IF NOT EXISTS `logistics_tracking` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '物流ID',
|
||||
`order_id` BIGINT UNSIGNED NOT NULL COMMENT '订单ID',
|
||||
`tracking_number` VARCHAR(100) UNIQUE COMMENT '物流单号',
|
||||
`carrier` VARCHAR(50) COMMENT '承运商',
|
||||
`status` ENUM('pending', 'in_transit', 'delivered', 'exception') DEFAULT 'pending' COMMENT '物流状态',
|
||||
`origin` VARCHAR(255) COMMENT '起始地',
|
||||
`destination` VARCHAR(255) COMMENT '目的地',
|
||||
`estimated_delivery_date` DATE COMMENT '预计送达日期',
|
||||
`actual_delivery_date` DATE COMMENT '实际送达日期',
|
||||
`current_location` VARCHAR(255) COMMENT '当前位置',
|
||||
`tracking_info` JSON COMMENT '物流跟踪信息',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`order_id`) REFERENCES `orders`(`id`) ON DELETE CASCADE,
|
||||
INDEX `idx_tracking_number` (`tracking_number`),
|
||||
INDEX `idx_order` (`order_id`),
|
||||
INDEX `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物流跟踪表';
|
||||
|
||||
-- ======================================
|
||||
-- 6. 政府监管相关表
|
||||
-- ======================================
|
||||
|
||||
-- 政府监管报告表
|
||||
CREATE TABLE IF NOT EXISTS `government_reports` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '报告ID',
|
||||
`report_type` ENUM('production', 'sales', 'disease', 'environment', 'finance') NOT NULL COMMENT '报告类型',
|
||||
`reporter_id` BIGINT UNSIGNED NOT NULL COMMENT '报告人ID',
|
||||
`reporting_period_start` DATE NOT NULL COMMENT '报告期开始日期',
|
||||
`reporting_period_end` DATE NOT NULL COMMENT '报告期结束日期',
|
||||
`data_content` JSON COMMENT '报告数据内容',
|
||||
`summary` TEXT COMMENT '摘要',
|
||||
`status` ENUM('draft', 'submitted', 'approved', 'rejected') DEFAULT 'draft' COMMENT '状态',
|
||||
`approver_id` BIGINT UNSIGNED COMMENT '审批人ID',
|
||||
`approval_notes` TEXT COMMENT '审批备注',
|
||||
`submitted_at` TIMESTAMP NULL COMMENT '提交时间',
|
||||
`approved_at` TIMESTAMP NULL COMMENT '审批时间',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
FOREIGN KEY (`reporter_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`approver_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
|
||||
INDEX `idx_reporter` (`reporter_id`),
|
||||
INDEX `idx_type` (`report_type`),
|
||||
INDEX `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='政府监管报告表';
|
||||
|
||||
-- 新闻资讯表
|
||||
CREATE TABLE IF NOT EXISTS `news_articles` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '新闻ID',
|
||||
`title` VARCHAR(150) NOT NULL COMMENT '标题',
|
||||
`subtitle` VARCHAR(200) COMMENT '副标题',
|
||||
`content` TEXT NOT NULL COMMENT '内容',
|
||||
`author` VARCHAR(50) COMMENT '作者',
|
||||
`source` VARCHAR(100) COMMENT '来源',
|
||||
`cover_image` VARCHAR(255) COMMENT '封面图片URL',
|
||||
`is_featured` BOOLEAN DEFAULT FALSE COMMENT '是否推荐',
|
||||
`status` ENUM('draft', 'published', 'archived') DEFAULT 'draft' COMMENT '状态',
|
||||
`publish_date` TIMESTAMP NULL COMMENT '发布时间',
|
||||
`category` VARCHAR(50) COMMENT '分类',
|
||||
`views` INT UNSIGNED DEFAULT 0 COMMENT '浏览量',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
INDEX `idx_status` (`status`),
|
||||
INDEX `idx_category` (`category`),
|
||||
INDEX `idx_publish_date` (`publish_date`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='新闻资讯表';
|
||||
|
||||
-- 政策公告表
|
||||
CREATE TABLE IF NOT EXISTS `policy_announcements` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '公告ID',
|
||||
`title` VARCHAR(150) NOT NULL COMMENT '标题',
|
||||
`content` TEXT NOT NULL COMMENT '内容',
|
||||
`issuer` VARCHAR(100) NOT NULL COMMENT '发布机构',
|
||||
`issue_date` DATE NOT NULL COMMENT '发布日期',
|
||||
`effective_date` DATE COMMENT '生效日期',
|
||||
`document_number` VARCHAR(50) COMMENT '文号',
|
||||
`attachment_url` VARCHAR(255) COMMENT '附件URL',
|
||||
`is_important` BOOLEAN DEFAULT FALSE COMMENT '是否重要公告',
|
||||
`status` ENUM('draft', 'published', 'expired') DEFAULT 'draft' COMMENT '状态',
|
||||
`views` INT UNSIGNED DEFAULT 0 COMMENT '浏览量',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
INDEX `idx_issue_date` (`issue_date`),
|
||||
INDEX `idx_status` (`status`),
|
||||
INDEX `idx_issuer` (`issuer`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='政策公告表';
|
||||
|
||||
-- 环境监测表
|
||||
CREATE TABLE IF NOT EXISTS `environment_monitoring` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '记录ID',
|
||||
`location` VARCHAR(255) NOT NULL COMMENT '监测位置',
|
||||
`temperature` DECIMAL(5,2) COMMENT '温度(℃)',
|
||||
`humidity` DECIMAL(5,2) COMMENT '湿度(%)',
|
||||
`air_quality` VARCHAR(50) COMMENT '空气质量',
|
||||
`ammonia_concentration` DECIMAL(6,3) COMMENT '氨气浓度(ppm)',
|
||||
`carbon_dioxide` DECIMAL(6,2) COMMENT '二氧化碳浓度(ppm)',
|
||||
`noise_level` DECIMAL(5,2) COMMENT '噪音(dB)',
|
||||
`light_intensity` DECIMAL(7,2) COMMENT '光照强度(lux)',
|
||||
`monitoring_date` DATETIME NOT NULL COMMENT '监测时间',
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
INDEX `idx_location_date` (`location`, `monitoring_date`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='环境监测表';
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
-- ======================================
|
||||
-- 插入测试数据
|
||||
-- ======================================
|
||||
|
||||
-- 插入商品分类
|
||||
INSERT IGNORE INTO `product_categories` (`name`, `parent_id`, `level`, `sort_order`) VALUES
|
||||
('牛肉制品', 0, 1, 1),
|
||||
('新鲜牛肉', 1, 2, 1),
|
||||
('牛肉干', 1, 2, 2),
|
||||
('牛肉罐头', 1, 2, 3);
|
||||
|
||||
-- 插入测试牛只数据
|
||||
INSERT IGNORE INTO `cattle` (`ear_tag`, `name`, `breed`, `gender`, `birth_date`, `color`, `weight`, `farm_location`, `owner_id`) VALUES
|
||||
('XL001', '小花', '西门塔尔牛', 'female', '2022-03-15', '黄白花', 450.50, '锡林浩特市第一牧场', 1),
|
||||
('XL002', '壮壮', '安格斯牛', 'male', '2021-08-20', '黑色', 580.75, '锡林浩特市第一牧场', 1),
|
||||
('XL003', '美美', '夏洛莱牛', 'female', '2022-05-10', '白色', 420.30, '东乌旗牧场A', 1);
|
||||
|
||||
SELECT '数据库扩展表结构创建完成!' AS message;
|
||||
160
backend/database/package-lock.json
generated
Normal file
160
backend/database/package-lock.json
generated
Normal file
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"name": "database",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "database",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"dotenv": "^17.2.2",
|
||||
"mysql2": "^3.14.4"
|
||||
}
|
||||
},
|
||||
"node_modules/aws-ssl-profiles": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
|
||||
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz",
|
||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-17.2.2.tgz",
|
||||
"integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/generate-function": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/generate-function/-/generate-function-2.3.1.tgz",
|
||||
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-property": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.0.tgz",
|
||||
"integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/is-property": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/is-property/-/is-property-1.0.2.tgz",
|
||||
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmmirror.com/long/-/long-5.3.2.tgz",
|
||||
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "7.18.3",
|
||||
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-7.18.3.tgz",
|
||||
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/lru.min": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/lru.min/-/lru.min-1.1.2.tgz",
|
||||
"integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"bun": ">=1.0.0",
|
||||
"deno": ">=1.30.0",
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wellwelwel"
|
||||
}
|
||||
},
|
||||
"node_modules/mysql2": {
|
||||
"version": "3.14.4",
|
||||
"resolved": "https://registry.npmmirror.com/mysql2/-/mysql2-3.14.4.tgz",
|
||||
"integrity": "sha512-Cs/jx3WZPNrYHVz+Iunp9ziahaG5uFMvD2R8Zlmc194AqXNxt9HBNu7ZsPYrUtmJsF0egETCWIdMIYAwOGjL1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"aws-ssl-profiles": "^1.1.1",
|
||||
"denque": "^2.1.0",
|
||||
"generate-function": "^2.3.1",
|
||||
"iconv-lite": "^0.7.0",
|
||||
"long": "^5.2.1",
|
||||
"lru.min": "^1.0.0",
|
||||
"named-placeholders": "^1.1.3",
|
||||
"seq-queue": "^0.0.5",
|
||||
"sqlstring": "^2.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/named-placeholders": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/named-placeholders/-/named-placeholders-1.1.3.tgz",
|
||||
"integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lru-cache": "^7.14.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/seq-queue": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/seq-queue/-/seq-queue-0.0.5.tgz",
|
||||
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
|
||||
},
|
||||
"node_modules/sqlstring": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmmirror.com/sqlstring/-/sqlstring-2.3.3.tgz",
|
||||
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
backend/database/package.json
Normal file
16
backend/database/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "database",
|
||||
"version": "1.0.0",
|
||||
"description": "## 概述",
|
||||
"main": "setup-database.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"dotenv": "^17.2.2",
|
||||
"mysql2": "^3.14.4"
|
||||
}
|
||||
}
|
||||
205
backend/database/setup-database.js
Normal file
205
backend/database/setup-database.js
Normal file
@@ -0,0 +1,205 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
require('dotenv').config();
|
||||
|
||||
class DatabaseSetup {
|
||||
constructor() {
|
||||
this.config = {
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT),
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
multipleStatements: true
|
||||
};
|
||||
}
|
||||
|
||||
async checkConnection() {
|
||||
console.log('🔍 检查数据库连接...');
|
||||
console.log(`📍 服务器: ${this.config.host}:${this.config.port}`);
|
||||
console.log(`👤 用户: ${this.config.user}`);
|
||||
console.log(`🗄️ 数据库: ${this.config.database}`);
|
||||
|
||||
try {
|
||||
const connection = await mysql.createConnection(this.config);
|
||||
console.log('✅ 数据库连接成功!');
|
||||
|
||||
// 获取数据库版本信息
|
||||
const [version] = await connection.execute('SELECT VERSION() as version');
|
||||
console.log(`📋 MySQL版本: ${version[0].version}`);
|
||||
|
||||
await connection.end();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 数据库连接失败:', error.message);
|
||||
|
||||
if (error.code === 'ER_ACCESS_DENIED_ERROR') {
|
||||
console.log('\n💡 可能的解决方案:');
|
||||
console.log('1. 检查用户名和密码是否正确');
|
||||
console.log('2. 确认用户有访问该数据库的权限');
|
||||
console.log('3. 检查IP白名单是否包含当前服务器IP');
|
||||
console.log(` 当前尝试连接的IP需要添加到腾讯云数据库白名单中`);
|
||||
} else if (error.code === 'ENOTFOUND') {
|
||||
console.log('\n💡 域名解析失败,请检查:');
|
||||
console.log('1. 数据库服务器地址是否正确');
|
||||
console.log('2. 网络连接是否正常');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async executeSQL(sqlFile) {
|
||||
console.log(`\n📄 执行SQL文件: ${sqlFile}`);
|
||||
|
||||
try {
|
||||
const sqlPath = path.join(__dirname, sqlFile);
|
||||
const sql = fs.readFileSync(sqlPath, 'utf8');
|
||||
|
||||
const connection = await mysql.createConnection(this.config);
|
||||
|
||||
// 分割SQL语句并逐个执行
|
||||
const statements = sql.split(';').filter(stmt => stmt.trim().length > 0);
|
||||
let successCount = 0;
|
||||
|
||||
for (const statement of statements) {
|
||||
const trimmedStmt = statement.trim();
|
||||
if (trimmedStmt) {
|
||||
try {
|
||||
await connection.execute(trimmedStmt);
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
if (!error.message.includes('already exists')) {
|
||||
console.warn(`⚠️ 执行语句时警告: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ 成功执行 ${successCount} 条SQL语句`);
|
||||
await connection.end();
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ 执行SQL文件失败: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async checkTables() {
|
||||
console.log('\n📋 检查数据库表结构...');
|
||||
|
||||
try {
|
||||
const connection = await mysql.createConnection(this.config);
|
||||
|
||||
// 获取所有表
|
||||
const [tables] = await connection.execute(
|
||||
'SELECT TABLE_NAME, TABLE_COMMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = ?',
|
||||
[this.config.database]
|
||||
);
|
||||
|
||||
console.log(`📊 数据库 ${this.config.database} 中共有 ${tables.length} 张表:`);
|
||||
|
||||
tables.forEach((table, index) => {
|
||||
console.log(` ${index + 1}. ${table.TABLE_NAME} - ${table.TABLE_COMMENT || '无注释'}`);
|
||||
});
|
||||
|
||||
// 检查重要表是否存在
|
||||
const requiredTables = ['users', 'cattle', 'loan_applications', 'insurance_applications', 'contracts', 'products', 'orders'];
|
||||
const existingTables = tables.map(t => t.TABLE_NAME);
|
||||
const missingTables = requiredTables.filter(table => !existingTables.includes(table));
|
||||
|
||||
if (missingTables.length === 0) {
|
||||
console.log('✅ 所有核心表已创建');
|
||||
} else {
|
||||
console.log(`⚠️ 缺少核心表: ${missingTables.join(', ')}`);
|
||||
}
|
||||
|
||||
await connection.end();
|
||||
return { tables: existingTables, missing: missingTables };
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 检查表结构失败:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getMyIPAddress() {
|
||||
try {
|
||||
const https = require('https');
|
||||
return new Promise((resolve, reject) => {
|
||||
https.get('https://api.ipify.org', (resp) => {
|
||||
let data = '';
|
||||
resp.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
resp.on('end', () => {
|
||||
resolve(data);
|
||||
});
|
||||
}).on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
return '无法获取';
|
||||
}
|
||||
}
|
||||
|
||||
async setup() {
|
||||
console.log('🚀 开始数据库初始化流程...\n');
|
||||
|
||||
// 获取当前IP地址
|
||||
const myIP = await this.getMyIPAddress();
|
||||
console.log(`🌐 当前公网IP地址: ${myIP}`);
|
||||
console.log('📝 请确保此IP已添加到腾讯云数据库白名单中\n');
|
||||
|
||||
// 检查连接
|
||||
const connected = await this.checkConnection();
|
||||
if (!connected) {
|
||||
console.log('\n❌ 数据库连接失败,请先解决连接问题');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 执行基础表创建
|
||||
console.log('\n🔨 创建基础表结构...');
|
||||
const basicResult = await this.executeSQL('init_tables.sql');
|
||||
if (!basicResult) {
|
||||
console.log('❌ 基础表创建失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 执行扩展表创建
|
||||
console.log('\n🔧 创建扩展表结构...');
|
||||
const extendedResult = await this.executeSQL('init_tables_extended.sql');
|
||||
if (!extendedResult) {
|
||||
console.log('❌ 扩展表创建失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证表结构
|
||||
await this.checkTables();
|
||||
|
||||
console.log('\n🎉 数据库初始化完成!');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此脚本
|
||||
if (require.main === module) {
|
||||
const setup = new DatabaseSetup();
|
||||
setup.setup().then((success) => {
|
||||
if (success) {
|
||||
console.log('\n✅ 数据库设置成功完成');
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log('\n❌ 数据库设置失败');
|
||||
process.exit(1);
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('💥 发生错误:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = DatabaseSetup;
|
||||
@@ -70,26 +70,26 @@ FLUSH PRIVILEGES;
|
||||
```bash
|
||||
# 克隆代码
|
||||
git clone [repository_url] /opt/xlxumu
|
||||
cd /opt/xlxumu/backend/api
|
||||
cd /opt/xlxumu/backend-java
|
||||
|
||||
# 安装依赖
|
||||
npm install
|
||||
# 构建项目
|
||||
mvn clean package
|
||||
|
||||
# 配置环境变量
|
||||
cp .env.example .env
|
||||
# 编辑.env文件,配置数据库连接等信息
|
||||
cp services/farming-service/src/main/resources/application.yml.example services/farming-service/src/main/resources/application.yml
|
||||
# 编辑application.yml文件,配置数据库连接等信息
|
||||
|
||||
# 启动服务
|
||||
npm start
|
||||
java -jar services/farming-service/target/farming-service.jar
|
||||
```
|
||||
|
||||
### 3. 前端应用部署
|
||||
```bash
|
||||
# 构建各个前端应用
|
||||
cd /opt/xlxumu/frontend/website
|
||||
cd /opt/xlxumu/frontend/official-website
|
||||
# 构建静态网站
|
||||
|
||||
cd /opt/xlxumu/frontend/farming-management
|
||||
cd /opt/xlxumu/frontend/admin-system
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
@@ -104,7 +104,7 @@ server {
|
||||
|
||||
# 静态网站
|
||||
location / {
|
||||
root /opt/xlxumu/frontend/website/dist;
|
||||
root /opt/xlxumu/frontend/official-website/dist;
|
||||
index index.html;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
@@ -119,7 +119,7 @@ server {
|
||||
|
||||
# 各个管理系统
|
||||
location /farming-management/ {
|
||||
alias /opt/xlxumu/frontend/farming-management/dist/;
|
||||
alias /opt/xlxumu/frontend/admin-system/dist/;
|
||||
try_files $uri $uri/ /farming-management/index.html;
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ services:
|
||||
- "3306:3306"
|
||||
|
||||
api:
|
||||
build: ./backend/api
|
||||
build: ./backend-java/services/farming-service
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
|
||||
145
docs/API_DOCUMENTATION_MAINTENANCE.md
Normal file
145
docs/API_DOCUMENTATION_MAINTENANCE.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# API文档维护流程
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本文档定义了xlxumu项目中API文档的维护流程和规范,适用于Node.js和Java两种技术栈的后端服务。
|
||||
|
||||
## 2. 维护责任
|
||||
|
||||
### 2.1 责任分配
|
||||
- **Node.js服务**: 由Node.js开发团队负责维护
|
||||
- **Java服务**: 由Java开发团队负责维护
|
||||
- **架构师**: 负责审核重大变更和规范更新
|
||||
- **技术负责人**: 负责日常审核和质量把控
|
||||
|
||||
### 2.2 维护周期
|
||||
- **实时更新**: API变更时必须同步更新文档
|
||||
- **定期检查**: 每月进行一次文档准确性检查
|
||||
- **版本发布**: 每个版本发布前必须完成文档更新
|
||||
|
||||
## 3. 更新流程
|
||||
|
||||
### 3.1 新增API
|
||||
1. 开发人员在实现API时同步编写文档
|
||||
2. 提交代码时包含API文档更新
|
||||
3. 技术负责人审核API文档
|
||||
4. 合并到主分支
|
||||
|
||||
### 3.2 修改API
|
||||
1. 评估变更影响范围
|
||||
2. 更新API文档
|
||||
3. 标注变更历史
|
||||
4. 技术负责人审核
|
||||
5. 发布更新
|
||||
|
||||
### 3.3 删除API
|
||||
1. 标记API为废弃状态
|
||||
2. 提供替代方案说明
|
||||
3. 设置废弃时间表
|
||||
4. 技术负责人审核
|
||||
5. 到期后删除文档
|
||||
|
||||
## 4. 技术栈特定规范
|
||||
|
||||
### 4.1 Node.js API文档维护
|
||||
- 文档位置: `docs/design/api/[service].md`
|
||||
- 使用Markdown格式编写
|
||||
- 遵循API文档标准规范
|
||||
|
||||
### 4.2 Java API文档维护
|
||||
- 文档位置: `docs/design/api/[service].md`
|
||||
- 使用Markdown格式编写
|
||||
- 遵循API文档标准规范
|
||||
- 响应格式需包含timestamp字段
|
||||
|
||||
## 5. 版本管理
|
||||
|
||||
### 5.1 版本号规则
|
||||
- 采用语义化版本号: `v主版本号.次版本号.修订号`
|
||||
- 与对应服务版本保持一致
|
||||
|
||||
### 5.2 版本兼容性
|
||||
- **主版本号变更**: 不兼容的API修改
|
||||
- **次版本号变更**: 向下兼容的功能性新增
|
||||
- **修订号变更**: 向下兼容的问题修正
|
||||
|
||||
### 5.3 版本发布流程
|
||||
1. 确认API文档已更新
|
||||
2. 技术负责人审核
|
||||
3. 生成版本标签
|
||||
4. 发布到文档服务器
|
||||
|
||||
## 6. 审核机制
|
||||
|
||||
### 6.1 审核层级
|
||||
- **开发人员自检**: 文档完整性和准确性
|
||||
- **技术负责人审核**: 技术规范和一致性
|
||||
- **架构师审核**: 重大变更和架构影响
|
||||
|
||||
### 6.2 审核要点
|
||||
- API功能描述准确性
|
||||
- 请求参数完整性
|
||||
- 响应格式正确性
|
||||
- 错误码定义完整性
|
||||
- 示例代码有效性
|
||||
|
||||
## 7. 质量保证
|
||||
|
||||
### 7.1 文档检查清单
|
||||
- [ ] 接口概述完整
|
||||
- [ ] 请求参数描述准确
|
||||
- [ ] 响应示例完整
|
||||
- [ ] 错误码定义完整
|
||||
- [ ] 示例代码可运行
|
||||
- [ ] 版本信息正确
|
||||
|
||||
### 7.2 自动化检查
|
||||
- 使用文档校验工具检查格式
|
||||
- 定期运行示例代码验证
|
||||
- 自动生成API文档网站
|
||||
|
||||
## 8. 变更记录
|
||||
|
||||
### 8.1 记录要求
|
||||
每次文档更新需要记录:
|
||||
- 更新日期
|
||||
- 更新内容
|
||||
- 更新人员
|
||||
- 影响范围
|
||||
|
||||
### 8.2 记录格式
|
||||
```markdown
|
||||
## [版本号] - [日期]
|
||||
### 新增
|
||||
- [新增内容]
|
||||
|
||||
### 修改
|
||||
- [修改内容]
|
||||
|
||||
### 删除
|
||||
- [删除内容]
|
||||
```
|
||||
|
||||
## 9. 发布与分发
|
||||
|
||||
### 9.1 发布渠道
|
||||
- 内部文档服务器
|
||||
- Git仓库
|
||||
- API文档网站
|
||||
|
||||
### 9.2 访问控制
|
||||
- 开发团队: 完全访问权限
|
||||
- 测试团队: 查看权限
|
||||
- 外部合作伙伴: 按需授权
|
||||
|
||||
## 10. 培训与支持
|
||||
|
||||
### 10.1 培训计划
|
||||
- 新员工入职培训
|
||||
- 技术栈专项培训
|
||||
- 文档规范培训
|
||||
|
||||
### 10.2 支持机制
|
||||
- 技术负责人提供文档编写支持
|
||||
- 定期组织文档评审会议
|
||||
- 建立文档问题反馈渠道
|
||||
@@ -1,195 +1,174 @@
|
||||
# API 文档格式规范标准
|
||||
# API文档规范标准
|
||||
|
||||
## 1. 文档结构规范
|
||||
## 1. 概述
|
||||
|
||||
### 1.1 文档头部
|
||||
```markdown
|
||||
# [系统名称] API 文档 (v[版本号])
|
||||
本文档定义了xlxumu项目中API文档的编写标准和规范,适用于Node.js和Java两种技术栈的后端服务。
|
||||
|
||||
## 1. 接口概述
|
||||
## 2. 文档结构
|
||||
|
||||
### 1.1 功能范围
|
||||
- [功能点1]
|
||||
- [功能点2]
|
||||
- [功能点3]
|
||||
每个API文档应包含以下部分:
|
||||
|
||||
### 1.2 基础路径
|
||||
`/api/v[版本号]/[系统名称]`
|
||||
1. 接口概述
|
||||
2. 技术栈说明
|
||||
3. 公共参数说明
|
||||
4. 接口列表
|
||||
5. 错误码定义
|
||||
6. 示例代码
|
||||
|
||||
### 1.3 权限控制
|
||||
- 公开接口(无需认证):[接口描述]
|
||||
- 管理接口(需要认证):[接口描述]
|
||||
## 3. 技术栈规范
|
||||
|
||||
### 1.4 全局错误码
|
||||
| 状态码 | 说明 |
|
||||
|--------|--------------------|
|
||||
| 400 | 请求参数无效 |
|
||||
| 401 | 未授权 |
|
||||
| 403 | 权限不足 |
|
||||
| 404 | 资源不存在 |
|
||||
| 500 | 服务器内部错误 |
|
||||
```
|
||||
### 3.1 Node.js技术栈API规范
|
||||
|
||||
## 2. 接口定义规范
|
||||
#### 3.1.1 基本信息
|
||||
- 协议:HTTP/HTTPS
|
||||
- 数据格式:JSON
|
||||
- 字符编码:UTF-8
|
||||
- 版本控制:通过URL路径实现(如/v1/)
|
||||
|
||||
### 2.1 接口格式
|
||||
```markdown
|
||||
### [序号].[序号] [接口名称]
|
||||
```
|
||||
[HTTP方法] [接口路径]
|
||||
#### 3.1.2 请求规范
|
||||
- Content-Type: application/json
|
||||
- Authorization: Bearer {token}
|
||||
- 请求体采用JSON格式
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|-------------|--------|------|--------------------|
|
||||
| param1 | string | 是 | 参数说明 |
|
||||
| param2 | number | 否 | 参数说明 |
|
||||
|
||||
**响应示例**:
|
||||
#### 3.1.3 响应规范
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Java技术栈API规范
|
||||
|
||||
#### 3.2.1 基本信息
|
||||
- 协议:HTTP/HTTPS
|
||||
- 数据格式:JSON
|
||||
- 字符编码:UTF-8
|
||||
- 版本控制:通过URL路径实现(如/v1/)
|
||||
|
||||
#### 3.2.2 请求规范
|
||||
- Content-Type: application/json
|
||||
- Authorization: Bearer {token}
|
||||
- 请求体采用JSON格式
|
||||
|
||||
#### 3.2.3 响应规范
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {},
|
||||
"timestamp": "2024-01-20T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 公共参数说明
|
||||
|
||||
### 4.1 请求头
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| Authorization | string | 是 | 认证令牌,格式为Bearer {token} |
|
||||
| Content-Type | string | 是 | application/json |
|
||||
| X-Request-ID | string | 否 | 请求唯一标识 |
|
||||
|
||||
### 4.2 响应结构
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| code | integer | 状态码 |
|
||||
| message | string | 响应消息 |
|
||||
| data | object/array | 响应数据 |
|
||||
| timestamp | string | 响应时间戳(Java API特有) |
|
||||
|
||||
## 5. 接口列表规范
|
||||
|
||||
每个接口文档应包含以下信息:
|
||||
|
||||
### 5.1 接口基本信息
|
||||
- 接口名称
|
||||
- 请求方法 (GET/POST/PUT/DELETE)
|
||||
- 请求路径
|
||||
- 接口描述
|
||||
|
||||
### 5.2 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| 参数1 | string | 是 | 参数说明 |
|
||||
|
||||
### 5.3 请求示例
|
||||
```bash
|
||||
curl -X POST "http://api.example.com/v1/users" \
|
||||
-H "Authorization: Bearer token" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "张三"}'
|
||||
```
|
||||
|
||||
### 5.4 响应参数
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| 字段1 | string | 字段说明 |
|
||||
|
||||
### 5.5 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"field1": "value1",
|
||||
"field2": "value2"
|
||||
"id": 1,
|
||||
"name": "张三"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 错误响应格式
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"code": "ERROR_CODE",
|
||||
"message": "错误描述信息"
|
||||
}
|
||||
```
|
||||
## 6. 错误码定义
|
||||
|
||||
## 3. 数据类型规范
|
||||
### 6.1 通用错误码
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 200 | 请求成功 |
|
||||
| 400 | 请求参数错误 |
|
||||
| 401 | 未授权 |
|
||||
| 403 | 禁止访问 |
|
||||
| 404 | 资源不存在 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
### 3.1 基本数据类型
|
||||
- `string`: 字符串类型
|
||||
- `number`: 数字类型
|
||||
- `boolean`: 布尔类型
|
||||
- `array`: 数组类型
|
||||
- `object`: 对象类型
|
||||
- `file`: 文件类型
|
||||
### 6.2 业务错误码
|
||||
业务错误码从10000开始定义,每个服务有独立的错误码范围。
|
||||
|
||||
### 3.2 日期时间格式
|
||||
- 日期: YYYY-MM-DD
|
||||
- 时间: YYYY-MM-DDTHH:mm:ssZ (ISO 8601)
|
||||
## 7. 示例代码
|
||||
|
||||
## 4. 权限控制规范
|
||||
|
||||
### 4.1 权限级别
|
||||
- **公开**: 无需认证即可访问
|
||||
- **用户**: 需要用户登录认证
|
||||
- **管理员**: 需要管理员权限
|
||||
- **系统管理员**: 需要系统管理员权限
|
||||
|
||||
### 4.2 权限标识
|
||||
在接口定义中明确标注所需权限级别
|
||||
|
||||
## 5. 版本管理规范
|
||||
|
||||
### 5.1 版本号格式
|
||||
采用语义化版本号: `v主版本号.次版本号.修订号`
|
||||
|
||||
### 5.2 版本兼容性
|
||||
- 主版本号变更: 不兼容的API修改
|
||||
- 次版本号变更: 向下兼容的功能性新增
|
||||
- 修订号变更: 向下兼容的问题修正
|
||||
|
||||
## 6. 安全规范
|
||||
|
||||
### 6.1 数据传输
|
||||
- 敏感数据必须使用HTTPS加密传输
|
||||
- 密码等敏感信息需要加密存储
|
||||
|
||||
### 6.2 身份认证
|
||||
- 使用JWT Token进行身份认证
|
||||
- Token有效期设置合理时间
|
||||
|
||||
## 7. 文档维护规范
|
||||
|
||||
### 7.1 更新记录
|
||||
每次文档更新需要记录:
|
||||
- 更新日期
|
||||
- 更新内容
|
||||
- 更新人员
|
||||
|
||||
### 7.2 审核机制
|
||||
- 新增接口需要经过技术负责人审核
|
||||
- 重大变更需要经过架构师审核
|
||||
|
||||
## 8. 示例模板
|
||||
|
||||
```markdown
|
||||
# 示例系统 API 文档 (v1.0.0)
|
||||
|
||||
## 1. 接口概述
|
||||
|
||||
### 1.1 功能范围
|
||||
- 用户管理
|
||||
- 数据查询
|
||||
- 系统配置
|
||||
|
||||
### 1.2 基础路径
|
||||
`/api/v1/example`
|
||||
|
||||
### 1.3 权限控制
|
||||
- 公开接口(无需认证):数据查询
|
||||
- 管理接口(需要认证):用户管理、系统配置
|
||||
|
||||
### 1.4 全局错误码
|
||||
| 状态码 | 说明 |
|
||||
|--------|--------------------|
|
||||
| 400 | 请求参数无效 |
|
||||
| 401 | 未授权 |
|
||||
| 403 | 权限不足 |
|
||||
| 404 | 资源不存在 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 2. 接口明细
|
||||
|
||||
### 2.1 获取用户列表
|
||||
```
|
||||
GET /users
|
||||
```
|
||||
|
||||
**请求参数**:
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|-------------|--------|------|--------------------|
|
||||
| page | number | 否 | 页码(默认1) |
|
||||
| limit | number | 否 | 每页数量(默认10) |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"username": "user1",
|
||||
"email": "user1@example.com"
|
||||
}
|
||||
],
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"limit": 10
|
||||
### 7.1 JavaScript示例
|
||||
```javascript
|
||||
// 使用axios调用API
|
||||
const response = await axios.post('/api/v1/users', {
|
||||
name: '张三'
|
||||
}, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer token'
|
||||
}
|
||||
}
|
||||
```
|
||||
});
|
||||
```
|
||||
|
||||
## 9. 检查清单
|
||||
### 7.2 Java示例
|
||||
```java
|
||||
// 使用RestTemplate调用API
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", "Bearer token");
|
||||
HttpEntity<User> entity = new HttpEntity<>(user, headers);
|
||||
ResponseEntity<ApiResponse> response = restTemplate.postForEntity("/api/v1/users", entity, ApiResponse.class);
|
||||
```
|
||||
|
||||
- [ ] 文档头部格式符合规范
|
||||
- [ ] 接口定义格式统一
|
||||
- [ ] 数据类型标注准确
|
||||
- [ ] 权限控制明确标注
|
||||
- [ ] 错误响应格式规范
|
||||
- [ ] 版本号管理规范
|
||||
- [ ] 安全要求明确
|
||||
- [ ] 示例代码完整
|
||||
```
|
||||
## 8. 文档维护
|
||||
|
||||
### 8.1 更新频率
|
||||
- API变更时必须同步更新文档
|
||||
- 每月定期检查文档准确性
|
||||
|
||||
### 8.2 版本控制
|
||||
- 文档版本与API版本保持一致
|
||||
- 重大变更需要更新版本号
|
||||
|
||||
### 8.3 审核流程
|
||||
1. 文档编写完成
|
||||
2. 技术负责人审核
|
||||
3. 团队评审
|
||||
4. 发布更新
|
||||
@@ -1,183 +1,185 @@
|
||||
# 项目目录结构说明
|
||||
# 项目结构说明
|
||||
|
||||
## 整体结构
|
||||
## 1. 项目总体结构
|
||||
|
||||
```
|
||||
xlxumu/
|
||||
├── backend/ # 后端服务目录
|
||||
├── admin-system/ # 管理后台系统目录
|
||||
├── website/ # 官网项目目录
|
||||
├── mini_program/ # 微信小程序矩阵目录
|
||||
├── docs/ # 项目文档目录
|
||||
├── test/ # 测试文件目录
|
||||
├── deployment/ # 部署配置目录
|
||||
└── scripts/ # 工具脚本目录
|
||||
├── admin-system/ # 管理后台前端
|
||||
├── backend-java/ # Java后端服务
|
||||
├── backend/ # Node.js后端服务
|
||||
├── docs/ # 项目文档
|
||||
├── frontend/ # 用户端前端
|
||||
├── miniprogram/ # 微信小程序
|
||||
└── README.md # 项目说明
|
||||
```
|
||||
|
||||
## 详细目录说明
|
||||
## 2. 前端项目结构
|
||||
|
||||
### 1. 后端服务目录 (backend/)
|
||||
```
|
||||
backend/
|
||||
├── api/ # API接口服务
|
||||
│ ├── farming/ # 养殖管理API
|
||||
│ ├── finance/ # 金融服务API
|
||||
│ ├── government/ # 政府监管API
|
||||
│ ├── trade/ # 交易管理API
|
||||
│ ├── mall/ # 商城管理API
|
||||
│ ├── data-platform/ # 数据中台API
|
||||
│ ├── ai/ # AI能力API
|
||||
│ └── user-center/ # 用户中心API
|
||||
├── services/ # 微服务模块
|
||||
│ ├── farming-service/ # 养殖管理服务
|
||||
│ ├── finance-service/ # 金融服务
|
||||
│ ├── government-service/ # 政府监管服务
|
||||
│ ├── trade-service/ # 交易管理服务
|
||||
│ ├── mall-service/ # 商城管理服务
|
||||
│ ├── data-platform-service/ # 数据中台服务
|
||||
│ ├── ai-service/ # AI能力服务
|
||||
│ └── user-center-service/ # 用户中心服务
|
||||
├── database/ # 数据库脚本和设计
|
||||
└── utils/ # 工具函数和公共模块
|
||||
```
|
||||
|
||||
### 2. 管理后台系统目录 (admin-system/)
|
||||
### 2.1 admin-system (管理后台)
|
||||
```
|
||||
admin-system/
|
||||
├── farming-management/ # 养殖管理系统
|
||||
├── bank-supervision/ # 银行监管系统
|
||||
├── insurance-supervision/ # 保险监管系统
|
||||
├── government-platform/ # 政府监管平台
|
||||
├── cattle-trading/ # 活牛交易系统
|
||||
├── mall-management/ # 商城管理系统
|
||||
└── dashboard/ # 大屏可视化系统
|
||||
├── public/
|
||||
├── src/
|
||||
│ ├── api/ # API接口
|
||||
│ ├── assets/ # 静态资源
|
||||
│ ├── components/ # 公共组件
|
||||
│ ├── layout/ # 布局组件
|
||||
│ ├── router/ # 路由配置
|
||||
│ ├── store/ # 状态管理
|
||||
│ ├── styles/ # 样式文件
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── views/ # 页面组件
|
||||
│ └── App.vue
|
||||
├── .env # 环境变量
|
||||
├── .eslintrc.js # ESLint配置
|
||||
├── package.json # 依赖配置
|
||||
└── vue.config.js # Vue配置
|
||||
```
|
||||
|
||||
### 3. 官网项目目录 (website/)
|
||||
### 2.2 frontend (用户端)
|
||||
```
|
||||
website/
|
||||
├── index.html # 首页
|
||||
├── about.html # 关于页面
|
||||
├── data.html # 数据展示页面
|
||||
├── news.html # 新闻页面
|
||||
├── css/ # 样式文件
|
||||
├── js/ # JavaScript文件
|
||||
└── images/ # 图片资源
|
||||
frontend/
|
||||
├── public/
|
||||
├── src/
|
||||
│ ├── api/ # API接口
|
||||
│ ├── assets/ # 静态资源
|
||||
│ ├── components/ # 公共组件
|
||||
│ ├── composables/ # 组合式函数
|
||||
│ ├── layout/ # 布局组件
|
||||
│ ├── pages/ # 页面组件
|
||||
│ ├── router/ # 路由配置
|
||||
│ ├── stores/ # 状态管理
|
||||
│ ├── styles/ # 样式文件
|
||||
│ ├── utils/ # 工具函数
|
||||
│ └── App.vue
|
||||
├── .env # 环境变量
|
||||
├── .eslintrc.js # ESLint配置
|
||||
├── nuxt.config.ts # Nuxt配置
|
||||
├── package.json # 依赖配置
|
||||
└── tsconfig.json # TypeScript配置
|
||||
```
|
||||
|
||||
### 4. 微信小程序矩阵目录 (mini_program/)
|
||||
### 2.3 miniprogram (微信小程序)
|
||||
```
|
||||
mini_program/
|
||||
├── beef-mall/ # 牛肉商城小程序
|
||||
├── farming-manager/ # 养殖管理小程序
|
||||
├── bank-supervision/ # 银行监管小程序
|
||||
├── insurance-supervision/ # 保险监管小程序
|
||||
├── cattle-trading/ # 活牛交易小程序
|
||||
├── gov-supervision/ # 政府监管小程序
|
||||
├── data-platform/ # 数据中台小程序
|
||||
└── ai-capabilities/ # AI能力小程序
|
||||
miniprogram/
|
||||
├── components/ # 自定义组件
|
||||
├── images/ # 图片资源
|
||||
├── pages/ # 页面文件
|
||||
├── utils/ # 工具函数
|
||||
├── app.js # 小程序逻辑
|
||||
├── app.json # 小程序公共设置
|
||||
├── app.wxss # 公共样式
|
||||
├── package.json # 依赖配置
|
||||
└── project.config.json # 项目配置
|
||||
```
|
||||
|
||||
### 5. 项目文档目录 (docs/)
|
||||
## 3. 后端项目结构
|
||||
|
||||
### 3.1 Java后端 (backend-java)
|
||||
```
|
||||
backend-java/
|
||||
├── README.md # 项目说明
|
||||
├── pom.xml # Maven配置
|
||||
├── common/ # 公共模块
|
||||
│ ├── pom.xml
|
||||
│ └── src/
|
||||
├── config-server/ # 配置中心
|
||||
│ ├── pom.xml
|
||||
│ └── src/
|
||||
├── gateway/ # 网关服务
|
||||
│ ├── pom.xml
|
||||
│ └── src/
|
||||
├── registry/ # 注册中心
|
||||
│ ├── pom.xml
|
||||
│ └── src/
|
||||
├── services/ # 业务服务
|
||||
│ ├── farming-service/ # 养殖管理服务 (端口: 8081)
|
||||
│ │ ├── pom.xml
|
||||
│ │ └── src/
|
||||
│ ├── user-center-service/ # 用户中心服务 (端口: 8082)
|
||||
│ │ ├── pom.xml
|
||||
│ │ └── src/
|
||||
│ ├── trade-service/ # 交易服务 (端口: 8083)
|
||||
│ │ ├── pom.xml
|
||||
│ │ └── src/
|
||||
│ ├── finance-service/ # 金融服务 (端口: 8084)
|
||||
│ │ ├── pom.xml
|
||||
│ │ └── src/
|
||||
│ ├── data-platform-service/ # 数据平台服务 (端口: 8085)
|
||||
│ │ ├── pom.xml
|
||||
│ │ └── src/
|
||||
│ ├── government-service/ # 政务服务 (端口: 8086)
|
||||
│ │ ├── pom.xml
|
||||
│ │ └── src/
|
||||
│ ├── dashboard-service/ # 大屏服务 (端口: 8087)
|
||||
│ │ ├── pom.xml
|
||||
│ │ └── src/
|
||||
│ └── mall-service/ # 商城服务 (端口: 8088)
|
||||
│ ├── pom.xml
|
||||
│ └── src/
|
||||
└── scripts/ # 部署脚本
|
||||
```
|
||||
|
||||
### 3.2 Node.js后端 (backend)
|
||||
```
|
||||
backend/
|
||||
├── api/ # API接口
|
||||
├── config/ # 配置文件
|
||||
├── controllers/ # 控制器
|
||||
├── middleware/ # 中间件
|
||||
├── models/ # 数据模型
|
||||
├── routes/ # 路由配置
|
||||
├── services/ # 业务逻辑
|
||||
├── utils/ # 工具函数
|
||||
├── app.js # 应用入口
|
||||
├── package.json # 依赖配置
|
||||
└── server.js # 服务启动
|
||||
```
|
||||
|
||||
## 4. 文档结构
|
||||
|
||||
```
|
||||
docs/
|
||||
├── design/ # 设计文档
|
||||
│ ├── ARCHITECTURE.md # 系统架构设计
|
||||
│ ├── DEVELOPMENT_PLAN.md # 开发计划
|
||||
│ ├── api/ # API设计文档
|
||||
│ ├── database/ # 数据库设计文档
|
||||
│ └── system_architecture.svg # 系统架构图
|
||||
├── requirements/ # 需求文档
|
||||
│ ├── SYSTEM_REQUIREMENTS.md # 系统需求
|
||||
│ ├── FARMING_MANAGEMENT_REQUIREMENTS.md # 养殖管理需求
|
||||
│ ├── FINANCIAL_SERVICES_REQUIREMENTS.md # 金融服务需求
|
||||
│ ├── GOVERNMENT_SUPERVISION_REQUIREMENTS.md # 政府监管需求
|
||||
│ ├── MALL_MANAGEMENT_REQUIREMENTS.md # 商城管理需求
|
||||
│ ├── MARKET_TRADING_REQUIREMENTS.md # 市场交易需求
|
||||
│ ├── DATA_PLATFORM_REQUIREMENTS.md # 数据中台需求
|
||||
│ ├── AI_CAPABILITIES_REQUIREMENTS.md # AI能力需求
|
||||
│ ├── SYSTEM_INTEGRATION_REQUIREMENTS.md # 系统集成需求
|
||||
│ └── WEBSITE_REQUIREMENTS.md # 官网需求
|
||||
├── development_plans/ # 开发计划文档
|
||||
│ ├── backend_api_development_plan.md # 后端API开发计划
|
||||
│ ├── farming_management_development_plan.md # 养殖管理开发计划
|
||||
│ ├── bank_supervision_development_plan.md # 银行监管开发计划
|
||||
│ ├── insurance_supervision_development_plan.md # 保险监管开发计划
|
||||
│ ├── government_platform_development_plan.md # 政府平台开发计划
|
||||
│ ├── cattle_trading_development_plan.md # 活牛交易开发计划
|
||||
│ ├── mall_management_development_plan.md # 商城管理开发计划
|
||||
│ ├── dashboard_development_plan.md # 大屏可视化开发计划
|
||||
│ └── website_development_plan.md # 官网开发计划
|
||||
├── API_DOCUMENTATION_STANDARD.md # API文档规范标准
|
||||
├── API_DOCUMENTATION_CHECKLIST.md # API文档检查清单
|
||||
├── DOCUMENTATION_MAINTENANCE_PROCESS.md # 文档维护流程
|
||||
└── PROJECT_STRUCTURE.md # 项目结构说明(本文档)
|
||||
├── README.md # 文档目录说明
|
||||
├── API_DOCUMENTATION_STANDARD.md # API文档标准
|
||||
├── API_DOCUMENTATION_MAINTENANCE.md # API文档维护流程
|
||||
├── PROJECT_STRUCTURE.md # 项目结构说明
|
||||
├── design/ # 设计文档
|
||||
│ ├── ARCHITECTURE.md # 系统架构设计
|
||||
│ ├── DEVELOPMENT_PLAN.md # 开发计划
|
||||
│ ├── api/ # API设计
|
||||
│ └── database/ # 数据库设计
|
||||
├── development_plans/ # 开发计划
|
||||
└── requirements/ # 需求文档
|
||||
```
|
||||
|
||||
### 6. 测试文件目录 (test/)
|
||||
```
|
||||
test/
|
||||
├── unit/ # 单元测试
|
||||
├── integration/ # 集成测试
|
||||
├── e2e/ # 端到端测试
|
||||
└── performance/ # 性能测试
|
||||
```
|
||||
## 5. 部署配置
|
||||
|
||||
### 7. 部署配置目录 (deployment/)
|
||||
```
|
||||
deployment/
|
||||
├── docker/ # Docker配置
|
||||
├── kubernetes/ # Kubernetes配置
|
||||
├── nginx/ # Nginx配置
|
||||
├── scripts/ # 部署脚本
|
||||
└── README.md # 部署说明文档
|
||||
```
|
||||
### 5.1 环境配置
|
||||
- `.env`: 开发环境配置
|
||||
- `.env.staging`: 测试环境配置
|
||||
- `.env.production`: 生产环境配置
|
||||
|
||||
### 8. 工具脚本目录 (scripts/)
|
||||
```
|
||||
scripts/
|
||||
└── update_api_docs.py # API文档更新脚本
|
||||
```
|
||||
### 5.2 Docker配置
|
||||
- `docker-compose.yml`: Docker编排配置
|
||||
- `Dockerfile`: Docker镜像配置
|
||||
|
||||
## 目录命名规范
|
||||
## 6. 技术栈说明
|
||||
|
||||
1. **后端目录**: 使用英文单数形式,如 `backend/`
|
||||
2. **前端目录**: 使用英文单数形式,如 `admin-system/`
|
||||
3. **文档目录**: 使用英文复数形式,如 `docs/`
|
||||
4. **子目录**: 使用英文连字符分隔,如 `farming-management/`
|
||||
5. **API目录**: 使用英文单数形式,如 `api/farming/`
|
||||
### 6.1 前端技术栈
|
||||
- Vue 3 + Element Plus (管理后台)
|
||||
- Nuxt 3 (用户端)
|
||||
- 微信小程序原生开发
|
||||
|
||||
## 开发规范
|
||||
### 6.2 后端技术栈
|
||||
- Java Spring Boot (核心服务)
|
||||
- Node.js Express (部分服务)
|
||||
- MySQL (主数据库)
|
||||
- Redis (缓存)
|
||||
- RabbitMQ (消息队列)
|
||||
|
||||
1. **模块化开发**: 每个功能模块独立开发,便于维护和扩展
|
||||
2. **前后端分离**: 后端提供RESTful API,前端通过API调用数据
|
||||
3. **文档驱动**: 所有功能开发前必须先完成需求文档和设计文档
|
||||
4. **测试覆盖**: 每个模块都需要有相应的测试用例
|
||||
5. **代码规范**: 遵循统一的代码风格和命名规范
|
||||
|
||||
## 部署说明
|
||||
|
||||
1. **独立部署**: 每个子系统可以独立部署和运行
|
||||
2. **微服务架构**: 后端采用微服务架构,支持水平扩展
|
||||
3. **容器化**: 支持Docker容器化部署
|
||||
4. **自动化部署**: 提供自动化部署脚本和配置
|
||||
|
||||
## 维护说明
|
||||
|
||||
1. **文档更新**: 任何代码变更都需要同步更新相关文档
|
||||
2. **版本控制**: 使用Git进行版本控制,遵循语义化版本规范
|
||||
3. **代码审查**: 所有代码提交都需要经过代码审查
|
||||
4. **持续集成**: 配置CI/CD流水线,自动化测试和部署
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [API文档规范标准](./API_DOCUMENTATION_STANDARD.md)
|
||||
- [API文档检查清单](./API_DOCUMENTATION_CHECKLIST.md)
|
||||
- [文档维护流程](./DOCUMENTATION_MAINTENANCE_PROCESS.md)
|
||||
- [系统架构设计](./design/ARCHITECTURE.md)
|
||||
- [开发计划](./design/DEVELOPMENT_PLAN.md)
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2024年1月20日
|
||||
**版本**: v1.0.0
|
||||
### 6.3 DevOps工具
|
||||
- Git (版本控制)
|
||||
- Jenkins (持续集成)
|
||||
- Docker (容器化)
|
||||
- Kubernetes (容器编排)
|
||||
189
docs/README.md
189
docs/README.md
@@ -1,91 +1,126 @@
|
||||
# 项目文档
|
||||
# xlxumu项目文档
|
||||
|
||||
## 概述
|
||||
## 项目概述
|
||||
|
||||
本文档目录包含了锡林郭勒盟智慧养殖数字化管理平台的所有相关文档。
|
||||
xlxumu是一个综合性的畜牧管理系统,旨在为畜牧业提供全面的数字化解决方案。该系统涵盖了从养殖管理、金融服务、政府监管到电商交易等各个环节,通过现代化的技术架构和用户友好的界面设计,提升畜牧业的管理效率和经济效益。
|
||||
|
||||
## 文档列表
|
||||
## 文档结构
|
||||
|
||||
### 1. 架构文档
|
||||
- [系统架构文档](./design/ARCHITECTURE.md) - 系统整体架构设计,包含技术栈、系统架构图、前端系统架构、后端系统架构、用户权限管理架构、大屏可视化系统架构、系统集成架构、性能优化策略和部署架构
|
||||
- [开发计划文档](./design/DEVELOPMENT_PLAN.md) - 项目开发计划和时间安排
|
||||
```
|
||||
docs/
|
||||
├── requirements/ # 需求文档
|
||||
├── design/ # 设计文档
|
||||
├── development_plans/ # 开发计划
|
||||
├── API_DOCUMENTATION_STANDARD.md # API文档标准
|
||||
├── API_DOCUMENTATION_CHECKLIST.md # API文档检查清单
|
||||
├── DOCUMENTATION_MAINTENANCE_PROCESS.md # 文档维护流程
|
||||
├── PROJECT_STRUCTURE.md # 项目结构说明
|
||||
└── README.md # 文档目录说明(当前文件)
|
||||
```
|
||||
|
||||
### 2. 需求文档
|
||||
- [系统需求文档](./requirements/SYSTEM_REQUIREMENTS.md) - 系统非功能性需求
|
||||
- [养殖管理系统需求文档](./requirements/FARMING_MANAGEMENT_REQUIREMENTS.md) - 养殖管理系统的功能性需求
|
||||
- [金融服务系统需求文档](./requirements/FINANCIAL_SERVICES_REQUIREMENTS.md) - 金融服务系统的功能性需求
|
||||
- [政府监管系统需求文档](./requirements/GOVERNMENT_SUPERVISION_REQUIREMENTS.md) - 政府监管系统的功能性需求
|
||||
- [市场交易系统需求文档](./requirements/MARKET_TRADING_REQUIREMENTS.md) - 市场交易系统的功能性需求
|
||||
- [商城管理系统需求文档](./requirements/MALL_MANAGEMENT_REQUIREMENTS.md) - 商城管理系统的功能性需求
|
||||
- [数据中台系统需求文档](./requirements/DATA_PLATFORM_REQUIREMENTS.md) - 数据中台系统的功能性需求
|
||||
- [AI能力系统需求文档](./requirements/AI_CAPABILITIES_REQUIREMENTS.md) - AI能力系统的功能性需求
|
||||
- [官网需求文档](./requirements/WEBSITE_REQUIREMENTS.md) - 官网的功能性需求
|
||||
- [大屏可视化系统需求文档](./requirements/dashboard_requirements.md) - 大屏可视化系统的功能性需求
|
||||
- [系统集成需求文档](./requirements/SYSTEM_INTEGRATION_REQUIREMENTS.md) - 系统集成需求
|
||||
- 微信小程序需求文档:
|
||||
- [养殖户小程序需求文档](./requirements/farming_app_requirements.md) - 养殖户小程序的功能性需求
|
||||
- [金融服务小程序需求文档](./requirements/finance_app_requirements.md) - 金融服务小程序的功能性需求
|
||||
- [政府监管小程序需求文档](./requirements/gov_app_requirements.md) - 政府监管小程序的功能性需求
|
||||
- [市场交易小程序需求文档](./requirements/trading_app_requirements.md) - 市场交易小程序的功能性需求
|
||||
- [数据中台小程序需求文档](./requirements/data_platform_app_requirements.md) - 数据中台小程序的功能性需求
|
||||
- [AI能力小程序需求文档](./requirements/ai_app_requirements.md) - AI能力小程序的功能性需求
|
||||
- [牛肉商城小程序需求文档](./requirements/mall_app_requirements.md) - 牛肉商城小程序的功能性需求
|
||||
## 需求文档 (requirements/)
|
||||
|
||||
### 3. 设计文档
|
||||
- [养殖管理API](./design/api/farming.md) - 养殖管理相关接口
|
||||
- [金融服务API](./design/api/finance.md) - 金融服务相关接口
|
||||
- [政府监管API](./design/api/government.md) - 政府监管相关接口
|
||||
- [市场交易API](./design/api/trade.md) - 市场交易相关接口
|
||||
- [数据中台API](./design/api/data-platform.md) - 数据中台相关接口
|
||||
- [用户中心API](./design/api/user-center.md) - 用户中心相关接口
|
||||
- [官网API](./design/api/website.md) - 官网相关接口
|
||||
- [大屏可视化API](./design/api/dashboard.md) - 大屏可视化相关接口
|
||||
- 微信小程序API:
|
||||
- [养殖户小程序API](./design/api/miniprograms/farming-app.md) - 养殖户小程序相关接口
|
||||
- [政务人员小程序API](./design/api/miniprograms/gov-app.md) - 政务人员小程序相关接口
|
||||
- [商户小程序API](./design/api/miniprograms/merchant-app.md) - 商户小程序相关接口
|
||||
- [普通用户小程序API](./design/api/miniprograms/user-app.md) - 普通用户小程序相关接口
|
||||
### 系统级需求
|
||||
- [SYSTEM_REQUIREMENTS.md](requirements/SYSTEM_REQUIREMENTS.md) - 系统整体需求
|
||||
- [SYSTEM_INTEGRATION_REQUIREMENTS.md](requirements/SYSTEM_INTEGRATION_REQUIREMENTS.md) - 系统集成需求
|
||||
|
||||
### 4. 技术文档
|
||||
- 数据库设计文档 (位于[backend/database](file:///E:/vue/xlxumu/backend/database)目录)
|
||||
- 前端组件文档 (各前端项目中)
|
||||
- 部署文档 ([deployment/README.md](file:///E:/vue/xlxumu/deployment/README.md))
|
||||
### 业务模块需求
|
||||
- [FARMING_MANAGEMENT_REQUIREMENTS.md](requirements/FARMING_MANAGEMENT_REQUIREMENTS.md) - 养殖管理需求
|
||||
- [FINANCIAL_SERVICES_REQUIREMENTS.md](requirements/FINANCIAL_SERVICES_REQUIREMENTS.md) - 金融服务需求
|
||||
- [GOVERNMENT_SUPERVISION_REQUIREMENTS.md](requirements/GOVERNMENT_SUPERVISION_REQUIREMENTS.md) - 政府监管需求
|
||||
- [MARKET_TRADING_REQUIREMENTS.md](requirements/MARKET_TRADING_REQUIREMENTS.md) - 市场交易需求
|
||||
- [MALL_MANAGEMENT_REQUIREMENTS.md](requirements/MALL_MANAGEMENT_REQUIREMENTS.md) - 商城管理需求
|
||||
- [DATA_PLATFORM_REQUIREMENTS.md](requirements/DATA_PLATFORM_REQUIREMENTS.md) - 数据平台需求
|
||||
- [AI_CAPABILITIES_REQUIREMENTS.md](requirements/AI_CAPABILITIES_REQUIREMENTS.md) - AI能力需求
|
||||
- [WEBSITE_REQUIREMENTS.md](requirements/WEBSITE_REQUIREMENTS.md) - 官网需求
|
||||
- [RAISING_MANAGEMENT_REQUIREMENTS.md](requirements/RAISING_MANAGEMENT_REQUIREMENTS.md) - 养殖管理需求
|
||||
|
||||
### 5. 各系统详细开发计划
|
||||
- [养殖管理系统开发计划](./development_plans/farming_management_development_plan.md) - 养殖管理系统的详细开发计划
|
||||
- [银行监管系统开发计划](./development_plans/bank_supervision_development_plan.md) - 银行监管系统的详细开发计划
|
||||
- [保险监管系统开发计划](./development_plans/insurance_supervision_development_plan.md) - 保险监管系统的详细开发计划
|
||||
- [政府监管平台开发计划](./development_plans/government_platform_development_plan.md) - 政府监管平台的详细开发计划
|
||||
- [活牛交易系统开发计划](./development_plans/cattle_trading_development_plan.md) - 活牛交易系统的详细开发计划
|
||||
- [商城管理系统开发计划](./development_plans/mall_management_development_plan.md) - 商城管理系统的详细开发计划
|
||||
- [官网开发计划](./development_plans/website_development_plan.md) - 官网的详细开发计划
|
||||
- [后端API服务开发计划](./development_plans/backend_api_development_plan.md) - 后端API服务的详细开发计划
|
||||
- [大屏可视化系统开发计划](./development_plans/dashboard_development_plan.md) - 大屏可视化系统的详细开发计划
|
||||
### 小程序需求
|
||||
- [farming_app_requirements.md](requirements/farming_app_requirements.md) - 养殖管理小程序需求
|
||||
- [finance_app_requirements.md](requirements/finance_app_requirements.md) - 金融小程序需求
|
||||
- [gov_app_requirements.md](requirements/gov_app_requirements.md) - 政府监管小程序需求
|
||||
- [trading_app_requirements.md](requirements/trading_app_requirements.md) - 交易小程序需求
|
||||
- [mall_app_requirements.md](requirements/mall_app_requirements.md) - 商城小程序需求
|
||||
- [data_platform_app_requirements.md](requirements/data_platform_app_requirements.md) - 数据平台小程序需求
|
||||
- [ai_app_requirements.md](requirements/ai_app_requirements.md) - AI能力小程序需求
|
||||
- [dashboard_requirements.md](requirements/dashboard_requirements.md) - 仪表板需求
|
||||
|
||||
### 6. 微信小程序详细开发计划
|
||||
- [牛肉商城小程序开发计划](./development_plans/miniprograms/beef_mall_miniprogram_development_plan.md) - 牛肉商城小程序的详细开发计划
|
||||
- [养殖管理小程序开发计划](./development_plans/miniprograms/farming_management_miniprogram_development_plan.md) - 养殖管理小程序的详细开发计划
|
||||
- [银行监管小程序开发计划](./development_plans/miniprograms/bank_supervision_miniprogram_development_plan.md) - 银行监管小程序的详细开发计划
|
||||
- [保险监管小程序开发计划](./development_plans/miniprograms/insurance_supervision_miniprogram_development_plan.md) - 保险监管小程序的详细开发计划
|
||||
- [活牛交易小程序开发计划](./development_plans/miniprograms/cattle_trading_miniprogram_development_plan.md) - 活牛交易小程序的详细开发计划
|
||||
## 设计文档 (design/)
|
||||
|
||||
### 7. 用户文档
|
||||
- 用户手册 (待编写)
|
||||
- 管理员手册 (待编写)
|
||||
- 操作指南 (待编写)
|
||||
### 系统架构设计
|
||||
- [ARCHITECTURE.md](design/ARCHITECTURE.md) - 系统架构设计
|
||||
- [system_architecture.svg](design/system_architecture.svg) - 系统架构图
|
||||
|
||||
### 8. 其他文档
|
||||
- 测试报告 (待编写)
|
||||
- 项目总结报告 (待编写)
|
||||
### 数据库设计
|
||||
- [database/README.md](design/database/README.md) - 数据库设计说明
|
||||
- [database/DESIGN.md](design/database/DESIGN.md) - 数据库详细设计
|
||||
|
||||
### API设计
|
||||
- [api/farming.md](design/api/farming.md) - 养殖管理API设计
|
||||
- [api/finance.md](design/api/finance.md) - 金融服务API设计
|
||||
- [api/government.md](design/api/government.md) - 政府监管API设计
|
||||
- [api/trade.md](design/api/trade.md) - 交易管理API设计
|
||||
- [api/data-platform.md](design/api/data-platform.md) - 数据平台API设计
|
||||
- [api/user-center.md](design/api/user-center.md) - 用户中心API设计
|
||||
- [api/dashboard.md](design/api/dashboard.md) - 仪表板API设计
|
||||
- [api/website.md](design/api/website.md) - 官网API设计
|
||||
|
||||
### 小程序API设计
|
||||
- [api/miniprograms/](design/api/miniprograms/) - 小程序API设计目录
|
||||
|
||||
## 开发计划 (development_plans/)
|
||||
|
||||
### 后端开发计划
|
||||
- [backend_api_development_plan.md](development_plans/backend_api_development_plan.md) - 后端API开发计划
|
||||
|
||||
### 前端开发计划
|
||||
- [dashboard_development_plan.md](development_plans/dashboard_development_plan.md) - 仪表板开发计划
|
||||
- [farming_management_development_plan.md](development_plans/farming_management_development_plan.md) - 养殖管理开发计划
|
||||
- [cattle_trading_development_plan.md](development_plans/cattle_trading_development_plan.md) - 牛只交易开发计划
|
||||
- [mall_management_development_plan.md](development_plans/mall_management_development_plan.md) - 商城管理开发计划
|
||||
- [bank_supervision_development_plan.md](development_plans/bank_supervision_development_plan.md) - 银行监管开发计划
|
||||
- [insurance_supervision_development_plan.md](development_plans/insurance_supervision_development_plan.md) - 保险监管开发计划
|
||||
- [government_platform_development_plan.md](development_plans/government_platform_development_plan.md) - 政府平台开发计划
|
||||
- [website_development_plan.md](development_plans/website_development_plan.md) - 官网开发计划
|
||||
|
||||
### 小程序开发计划
|
||||
- [farming_management_miniprogram_development_plan.md](development_plans/farming_management_miniprogram_development_plan.md) - 养殖管理小程序开发计划
|
||||
- [cattle_trading_miniprogram_development_plan.md](development_plans/cattle_trading_miniprogram_development_plan.md) - 牛只交易小程序开发计划
|
||||
- [beef_mall_miniprogram_development_plan.md](development_plans/beef_mall_miniprogram_development_plan.md) - 牛肉商城小程序开发计划
|
||||
- [bank_supervision_miniprogram_development_plan.md](development_plans/bank_supervision_miniprogram_development_plan.md) - 银行监管小程序开发计划
|
||||
- [insurance_supervision_miniprogram_development_plan.md](development_plans/insurance_supervision_miniprogram_development_plan.md) - 保险监管小程序开发计划
|
||||
|
||||
## 标准与规范
|
||||
|
||||
- [API_DOCUMENTATION_STANDARD.md](API_DOCUMENTATION_STANDARD.md) - API文档标准
|
||||
- [API_DOCUMENTATION_CHECKLIST.md](API_DOCUMENTATION_CHECKLIST.md) - API文档检查清单
|
||||
- [DOCUMENTATION_MAINTENANCE_PROCESS.md](DOCUMENTATION_MAINTENANCE_PROCESS.md) - 文档维护流程
|
||||
- [PROJECT_STRUCTURE.md](PROJECT_STRUCTURE.md) - 项目结构说明
|
||||
|
||||
## 技术架构更新
|
||||
|
||||
随着项目的发展,我们新增了Java版本后端技术栈:
|
||||
|
||||
### Java后端技术栈
|
||||
- Java 8+ + Spring Boot 2.7.x
|
||||
- Spring Cloud 2021.x
|
||||
- Maven 3.8.x
|
||||
- MySQL 8.0
|
||||
|
||||
### 微服务架构
|
||||
项目现在采用微服务架构,包含以下服务:
|
||||
- farming-service (养殖管理服务)
|
||||
- user-center-service (用户中心服务)
|
||||
- finance-service (金融服务)
|
||||
- government-service (政府监管服务)
|
||||
- trade-service (交易管理服务)
|
||||
- mall-service (商城管理服务)
|
||||
- data-platform-service (数据平台服务)
|
||||
- ai-service (AI能力服务)
|
||||
- gateway (网关服务)
|
||||
- config-server (配置服务器)
|
||||
- registry (服务注册中心)
|
||||
|
||||
## 文档维护
|
||||
|
||||
所有文档应保持最新状态,与代码实现保持一致。当功能发生变化时,应及时更新相关文档。
|
||||
|
||||
## 贡献指南
|
||||
|
||||
如果您需要更新文档,请遵循以下步骤:
|
||||
1. Fork项目仓库
|
||||
2. 创建新的文档分支
|
||||
3. 进行文档修改
|
||||
4. 提交Pull Request
|
||||
请参考 [DOCUMENTATION_MAINTENANCE_PROCESS.md](DOCUMENTATION_MAINTENANCE_PROCESS.md) 了解文档维护流程和规范。
|
||||
@@ -1,499 +1,363 @@
|
||||
# 系统架构文档
|
||||
# 系统架构设计文档
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本项目是一个综合性的畜牧业数字化管理平台,主要面向锡林郭勒盟地区的养殖产业,包含官网展示、后台管理、移动端小程序、大屏可视化等多个子系统。平台集成了养殖管理、金融服务(银行监管、保险监管)、政府监管、牛只交易、牛肉商城、数据分析等功能模块,旨在通过数字化手段提升整个产业链的管理效率和透明度。
|
||||
本项目采用前后端分离的架构设计,后端基于双技术栈(Java Spring Boot + Node.js)的微服务架构,前端采用Vue.js和Nuxt.js技术栈,支持Web端和微信小程序等多种客户端。
|
||||
|
||||
## 2. 技术栈
|
||||
|
||||
### 2.1 前端技术栈
|
||||
- **官网首页**: HTML5 + CSS3 + JavaScript
|
||||
- **后台管理系统**: Vue.js 3 + TypeScript + Ant Design Vue + Pinia
|
||||
- **大屏可视化系统**: Vue.js 3 + ECharts + 自定义可视化组件
|
||||
- **微信小程序矩阵**: 微信小程序原生开发 + uni-app
|
||||
- **管理后台**: Vue 3 + Element Plus
|
||||
- **用户端**: Nuxt 3 (SSR)
|
||||
- **微信小程序**: 微信小程序原生开发
|
||||
- **构建工具**: Vite, Webpack
|
||||
- **状态管理**: Pinia, Vuex
|
||||
- **HTTP客户端**: Axios
|
||||
|
||||
### 2.2 后端技术栈
|
||||
- **API服务**: Node.js + Express.js + TypeScript + RESTful API
|
||||
- **数据库**: MySQL
|
||||
- **缓存系统**: Redis
|
||||
- **消息队列**: RabbitMQ(用于异步处理)
|
||||
- **文件存储**: 腾讯云对象存储
|
||||
- **实时通信**: WebSocket(用于大屏数据推送和实时通知)
|
||||
|
||||
### 2.3 数据库配置
|
||||
#### 2.2.1 Java技术栈(主要)
|
||||
- **核心框架**: Spring Boot 2.7.x, Spring Cloud 2021.x
|
||||
- **服务注册与发现**: Eureka
|
||||
- **配置中心**: Spring Cloud Config
|
||||
- **API网关**: Spring Cloud Gateway
|
||||
- **数据库**: MySQL 8.0, Spring Data JPA
|
||||
- **缓存**: Redis
|
||||
- **消息队列**: RabbitMQ
|
||||
- **安全框架**: Spring Security + JWT
|
||||
- **构建工具**: Maven 3.8.x
|
||||
|
||||
#### 2.3.1 测试环境
|
||||
- **主机**: `192.168.0.240` (MySQL主机地址)
|
||||
- **端口**: `3306` (MySQL端口)
|
||||
- **用户名**: `root`
|
||||
- **密码**: `aiot$Aiot123`
|
||||
- **数据库**: `xlxumudata`
|
||||
#### 2.2.2 Node.js技术栈(辅助)
|
||||
- **运行环境**: Node.js 16.x
|
||||
- **Web框架**: Express.js
|
||||
- **数据库**: MySQL 8.0
|
||||
- **ORM**: Sequelize
|
||||
- **构建工具**: NPM/Yarn
|
||||
|
||||
#### 2.3.2 生产环境
|
||||
- **主机**: `129.211.213.226`
|
||||
- **端口**: `9527`(端口号)
|
||||
- **用户名**: `root`
|
||||
- **密码**: `aiotAiot123!`
|
||||
- **数据库**: `xlxumudata`
|
||||
|
||||
### 2.4 安全架构
|
||||
- **身份认证**: JWT (JSON Web Tokens)
|
||||
- **权限管理**: 基于角色的访问控制 (RBAC)
|
||||
- **数据传输**: HTTPS 加密传输
|
||||
- **API防护**: 接口限流、参数校验、防SQL注入等
|
||||
- **密码安全**: BCrypt加密存储
|
||||
- **操作审计**: 用户操作日志记录和审计
|
||||
|
||||
### 2.5 数据架构
|
||||
- **实时数据采集**: IoT设备数据接入
|
||||
- **数据处理**: ETL数据处理流程
|
||||
- **数据存储**: 分层数据存储(操作数据、历史数据、统计数据)
|
||||
- **大数据分析**: 数据分析引擎
|
||||
- **智能预警**: 基于规则的预警系统
|
||||
- **可视化展示**: 图表化数据展示平台
|
||||
### 2.3 基础设施
|
||||
- **容器化**: Docker
|
||||
- **容器编排**: Kubernetes
|
||||
- **反向代理**: Nginx
|
||||
- **监控**: Prometheus + Grafana
|
||||
- **日志**: ELK Stack (Elasticsearch, Logstash, Kibana)
|
||||
- **CI/CD**: Jenkins
|
||||
|
||||
## 3. 系统架构图
|
||||
|
||||
为了更直观地展示系统架构,我们提供了一个可视化的系统架构图:
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "客户端层"
|
||||
A[Web浏览器] --> B[管理后台 admin-system]
|
||||
C[Web浏览器] --> D[用户端 frontend]
|
||||
E[微信客户端] --> F[微信小程序 miniprogram]
|
||||
end
|
||||
|
||||

|
||||
subgraph "网关层"
|
||||
G[API网关 gateway]
|
||||
end
|
||||
|
||||
图中展示了以下主要层次和组件:
|
||||
subgraph "服务注册与配置中心"
|
||||
H[Eureka注册中心 registry]
|
||||
I[Config配置中心 config-server]
|
||||
end
|
||||
|
||||
1. **用户接入层**:包括官网首页、专业管理系统和微信小程序矩阵
|
||||
2. **API服务层**:基于Node.js的各个业务模块API服务
|
||||
3. **数据处理与存储层**:包括关系数据库、缓存系统、消息队列、文件存储和日志存储
|
||||
4. **数据分析与展示层**:包含大数据分析引擎、智能预警系统、可视化平台和实时数据推送
|
||||
5. **监控与日志层**:应用性能监控、服务器资源监控、日志收集与分析以及异常告警
|
||||
6. **部署架构层**:容器化部署、负载均衡、自动化部署和容灾备份
|
||||
subgraph "核心业务服务层(Java)"
|
||||
J[养殖管理服务<br/>farming-service:8081]
|
||||
K[用户中心服务<br/>user-center-service:8082]
|
||||
L[交易服务<br/>trade-service:8083]
|
||||
M[金融服务<br/>finance-service:8084]
|
||||
N[数据平台服务<br/>data-platform-service:8085]
|
||||
O[政务服务<br/>government-service:8086]
|
||||
P[大屏服务<br/>dashboard-service:8087]
|
||||
Q[商城服务<br/>mall-service:8088]
|
||||
end
|
||||
|
||||
此外,图中还展示了各组件之间的连接关系和数据流向。
|
||||
subgraph "辅助业务服务层(Node.js)"
|
||||
R[AI能力服务<br/>ai-service:3001]
|
||||
S[部分遗留服务<br/>legacy-service:3002]
|
||||
end
|
||||
|
||||
## 4. 前端系统架构
|
||||
subgraph "数据存储层"
|
||||
T[MySQL主数据库]
|
||||
U[Redis缓存]
|
||||
V[RabbitMQ消息队列]
|
||||
end
|
||||
|
||||
### 4.1 官网首页系统
|
||||
- 纯HTML5、CSS3、JavaScript实现
|
||||
- 响应式设计,适配多种设备
|
||||
- 突出锡林郭勒盟地域元素和蒙古族文化特色
|
||||
- 绿色草原主题风格
|
||||
- 集成Chart.js实现数据可视化展示
|
||||
- 通过官网API获取新闻资讯和统计数据
|
||||
subgraph "基础设施层"
|
||||
W[Docker容器]
|
||||
X[Kubernetes集群]
|
||||
Y[Nginx反向代理]
|
||||
end
|
||||
|
||||
### 4.2 专业管理系统
|
||||
B --> G
|
||||
D --> G
|
||||
F --> G
|
||||
|
||||
#### 4.2.1 养殖管理系统
|
||||
- 基于Vue.js 3 Composition API
|
||||
- 使用Ant Design Vue组件库
|
||||
- 状态管理采用Pinia
|
||||
- TypeScript增强代码可维护性
|
||||
G --> H
|
||||
G --> J
|
||||
G --> K
|
||||
G --> L
|
||||
G --> M
|
||||
G --> N
|
||||
G --> O
|
||||
G --> P
|
||||
G --> Q
|
||||
G --> R
|
||||
G --> S
|
||||
|
||||
##### 功能模块
|
||||
1. 牛只档案管理
|
||||
2. 饲养记录管理
|
||||
3. 繁殖管理
|
||||
4. 环境监测数据展示
|
||||
5. 健康监测管理
|
||||
6. 生产计划制定和执行跟踪
|
||||
J --> T
|
||||
K --> T
|
||||
L --> T
|
||||
M --> T
|
||||
N --> T
|
||||
O --> T
|
||||
P --> T
|
||||
Q --> T
|
||||
R --> T
|
||||
S --> T
|
||||
|
||||
#### 4.2.2 银行监管系统
|
||||
- 基于Vue.js 3 Composition API
|
||||
- 使用Ant Design Vue组件库
|
||||
- 状态管理采用Pinia
|
||||
- TypeScript增强代码可维护性
|
||||
J --> U
|
||||
K --> U
|
||||
L --> U
|
||||
M --> U
|
||||
N --> U
|
||||
O --> U
|
||||
P --> U
|
||||
Q --> U
|
||||
R --> U
|
||||
S --> U
|
||||
|
||||
##### 功能模块
|
||||
1. 贷款申请和审批流程管理
|
||||
2. 质押物(牛只)状态监控
|
||||
3. 还款计划跟踪
|
||||
4. 风险评估数据展示
|
||||
5. 财务报表生成
|
||||
6. 风控管理
|
||||
J --> V
|
||||
K --> V
|
||||
L --> V
|
||||
M --> V
|
||||
N --> V
|
||||
O --> V
|
||||
P --> V
|
||||
Q --> V
|
||||
R --> V
|
||||
S --> V
|
||||
|
||||
#### 4.2.3 保险监管系统
|
||||
- 基于Vue.js 3 Composition API
|
||||
- 使用Ant Design Vue组件库
|
||||
- 状态管理采用Pinia
|
||||
- TypeScript增强代码可维护性
|
||||
H --> W
|
||||
I --> W
|
||||
J --> W
|
||||
K --> W
|
||||
L --> W
|
||||
M --> W
|
||||
N --> W
|
||||
O --> W
|
||||
P --> W
|
||||
Q --> W
|
||||
R --> W
|
||||
S --> W
|
||||
T --> W
|
||||
U --> W
|
||||
V --> W
|
||||
|
||||
##### 功能模块
|
||||
1. 保险投保管理
|
||||
2. 理赔申请和处理流程
|
||||
3. 风险评估和预警
|
||||
4. 保险记录查询
|
||||
5. 数据统计分析
|
||||
|
||||
#### 4.2.4 政府监管平台
|
||||
- 基于Vue.js 3 Composition API
|
||||
- 使用Ant Design Vue组件库
|
||||
- 状态管理采用Pinia
|
||||
- TypeScript增强代码可维护性
|
||||
|
||||
##### 功能模块
|
||||
1. 产业数据总览
|
||||
2. 各类监管数据汇总
|
||||
3. 政策发布和通知
|
||||
4. 合规性检查
|
||||
5. 统计报表生成
|
||||
6. 用户管理(集中式用户权限管理)
|
||||
7. 系统配置管理
|
||||
|
||||
#### 4.2.5 活牛交易系统
|
||||
- 基于Vue.js 3 Composition API
|
||||
- 使用Ant Design Vue组件库
|
||||
- 状态管理采用Pinia
|
||||
- TypeScript增强代码可维护性
|
||||
|
||||
##### 功能模块
|
||||
1. 牛只信息发布和展示
|
||||
2. 在线交易撮合
|
||||
3. 合同管理
|
||||
4. 支付流程管理
|
||||
5. 交易记录查询
|
||||
6. 商户管理
|
||||
7. 行情管理
|
||||
|
||||
#### 4.2.6 商城管理系统
|
||||
- 基于Vue.js 3 Composition API
|
||||
- 使用Ant Design Vue组件库
|
||||
- 状态管理采用Pinia
|
||||
- TypeScript增强代码可维护性
|
||||
|
||||
##### 功能模块
|
||||
1. 商品信息管理
|
||||
2. 库存管理
|
||||
3. 订单处理
|
||||
4. 物流跟踪
|
||||
5. 售后服务管理
|
||||
6. 营销管理
|
||||
|
||||
#### 4.2.7 大屏可视化系统
|
||||
- 基于Vue.js 3 Composition API
|
||||
- 使用ECharts和自定义可视化组件
|
||||
- 状态管理采用Pinia
|
||||
- TypeScript增强代码可维护性
|
||||
- WebSocket实现实时数据推送
|
||||
|
||||
##### 功能模块
|
||||
1. 产业概览
|
||||
2. 养殖监控
|
||||
3. 金融服务
|
||||
4. 交易统计
|
||||
5. 运输跟踪
|
||||
6. 风险预警
|
||||
7. 生态指标
|
||||
8. 政府监管
|
||||
|
||||
### 4.3 微信小程序矩阵
|
||||
- 使用微信小程序原生开发框架
|
||||
- 跨平台支持采用uni-app
|
||||
- 包含8个专门的小程序应用
|
||||
|
||||
#### 4.3.1 牛肉商城小程序(消费者端)
|
||||
- 商品浏览和搜索
|
||||
- 在线下单和支付
|
||||
- 订单查询和跟踪
|
||||
- 售后服务申请
|
||||
- **认养功能**:用户可以认养特定牛只,跟踪其成长过程
|
||||
|
||||
#### 4.3.2 养殖管理小程序(牧民端)
|
||||
- 移动端牛只档案查看
|
||||
- 饲养记录录入
|
||||
- 健康状况上报
|
||||
- 通知消息接收
|
||||
- 数据统计查看
|
||||
|
||||
#### 4.3.3 银行监管小程序(银行端)
|
||||
- 移动端贷款审批
|
||||
- 质押物状态查看
|
||||
- 风险数据监控
|
||||
- 移动办公支持
|
||||
|
||||
#### 4.3.4 保险监管小程序(保险端)
|
||||
- 移动端保险处理
|
||||
- 理赔流程管理
|
||||
- 风险评估查看
|
||||
- 移动查勘支持
|
||||
|
||||
#### 4.3.5 活牛交易小程序(交易员端)
|
||||
- 活牛信息发布
|
||||
- 在线交易撮合
|
||||
- 合同查看和管理
|
||||
- 交易记录查询
|
||||
- 支付状态跟踪
|
||||
|
||||
#### 4.3.6 政府监管小程序(监管人员端)
|
||||
- 防疫任务执行
|
||||
- 补贴申请审核
|
||||
- 政策信息查看
|
||||
- 监管数据上报
|
||||
|
||||
#### 4.3.7 数据中台小程序(数据人员端)
|
||||
- 数据查询和导出
|
||||
- 统计报表查看
|
||||
- 数据服务申请
|
||||
|
||||
#### 4.3.8 AI能力小程序(养殖户/兽医端)
|
||||
- 牛只体况评估
|
||||
- 饲料配方推荐
|
||||
- 智能诊断辅助
|
||||
|
||||
## 5. 后端系统架构
|
||||
|
||||
### 5.1 API服务层
|
||||
- 基于Node.js和Express.js构建
|
||||
- 使用TypeScript增强代码健壮性
|
||||
- RESTful API设计风格
|
||||
- JWT身份认证机制
|
||||
- 微服务架构设计(按业务模块划分)
|
||||
|
||||
#### 5.1.1 养殖管理API (`/api/v1/farming`)
|
||||
- 牛只档案管理
|
||||
- 饲喂记录
|
||||
- 防疫管理
|
||||
- 繁殖管理
|
||||
|
||||
#### 5.1.2 金融服务API (`/api/v1/finance`)
|
||||
- 贷款申请
|
||||
- 保险购买
|
||||
- 理赔处理
|
||||
- 贷款审批
|
||||
|
||||
#### 5.1.3 政府监管API (`/api/v1/gov`)
|
||||
- 防疫任务下发
|
||||
- 补贴发放
|
||||
- 检疫监管
|
||||
- 任务状态跟踪
|
||||
|
||||
#### 5.1.4 交易管理API (`/api/v1/trades`)
|
||||
- 商品发布/下架
|
||||
- 订单创建/支付
|
||||
- 物流跟踪
|
||||
- 订单状态查询
|
||||
|
||||
#### 5.1.5 商城管理API (`/api/v1/mall`)
|
||||
- 商品管理
|
||||
- 订单处理
|
||||
- 库存管理
|
||||
- 用户评价
|
||||
|
||||
#### 5.1.6 数据中台API (`/api/v1/data`)
|
||||
- 数据血缘追踪
|
||||
- 接口调用分析
|
||||
- 数据质量监控
|
||||
|
||||
#### 5.1.7 AI能力API (`/api/v1/ai`)
|
||||
- 牛只体况评估
|
||||
- 饲料配方推荐
|
||||
- 智能诊断辅助
|
||||
|
||||
#### 5.1.8 用户中心API (`/api/v1/users`)
|
||||
- 用户注册/登录/注销
|
||||
- 个人信息管理
|
||||
- 权限控制
|
||||
|
||||
#### 5.1.9 官网API (`/api/v1/website`)
|
||||
- 新闻资讯管理
|
||||
- 平台数据展示
|
||||
- 用户留言处理
|
||||
- 平台信息配置
|
||||
|
||||
#### 5.1.10 大屏可视化API (`/api/v1/dashboard`)
|
||||
- 实时数据展示(支持 WebSocket)
|
||||
- 历史数据查询(支持分页和排序)
|
||||
- 数据可视化配置(增删改查)
|
||||
- 告警信息推送(订阅/取消订阅)
|
||||
- 数据导出(CSV/JSON 格式)
|
||||
|
||||
### 5.2 数据存储层
|
||||
- 主数据库:MySQL关系型数据库
|
||||
- 缓存系统:Redis(用于会话缓存、数据缓存)
|
||||
- 消息队列:RabbitMQ(用于异步任务处理)
|
||||
- 文件存储:腾讯云对象存储服务
|
||||
- 日志存储:Elasticsearch(用于日志分析)
|
||||
|
||||
### 5.3 安全架构
|
||||
- 基于JWT的无状态认证
|
||||
- RBAC角色权限管理系统(集中式用户管理)
|
||||
- HTTPS加密传输
|
||||
- API接口安全防护(限流、防注入等)
|
||||
- 数据加密存储(BCrypt等)
|
||||
- 操作日志审计
|
||||
|
||||
### 5.4 数据分析层
|
||||
- 实时数据采集系统(IoT设备数据、用户行为数据)
|
||||
- 大数据分析引擎
|
||||
- 智能预警系统
|
||||
- 数据可视化展示平台
|
||||
|
||||
## 6. 项目结构
|
||||
|
||||
```
|
||||
.
|
||||
├── frontend/ # 前端项目
|
||||
│ ├── website/ # 官网首页
|
||||
│ ├── farming-management/ # 养殖管理系统
|
||||
│ ├── bank-supervision/ # 银行监管系统
|
||||
│ ├── insurance-supervision/ # 保险监管系统
|
||||
│ ├── government-platform/ # 政府监管平台
|
||||
│ ├── cattle-trading/ # 活牛交易系统
|
||||
│ ├── mall-management/ # 商城管理系统
|
||||
│ ├── dashboard/ # 大屏可视化系统
|
||||
│ └── mini-programs/ # 微信小程序矩阵
|
||||
│ ├── beef-mall/ # 牛肉商城小程序
|
||||
│ ├── farming-manager/ # 养殖管理小程序
|
||||
│ ├── bank-supervision/ # 银行监管小程序
|
||||
│ ├── insurance-supervision/ # 保险监管小程序
|
||||
│ ├── cattle-trading/ # 活牛交易小程序
|
||||
│ ├── gov-supervision/ # 政府监管小程序
|
||||
│ ├── data-platform/ # 数据中台小程序
|
||||
│ └── ai-capabilities/ # AI能力小程序
|
||||
├── backend/ # 后端项目
|
||||
│ ├── api/ # API服务
|
||||
│ │ ├── farming/ # 养殖管理API
|
||||
│ │ ├── finance/ # 金融服务API
|
||||
│ │ ├── government/ # 政府监管API
|
||||
│ │ ├── trade/ # 交易管理API
|
||||
│ │ ├── mall/ # 商城管理API
|
||||
│ │ ├── data-platform/ # 数据中台API
|
||||
│ │ ├── ai/ # AI能力API
|
||||
│ │ ├── website/ # 官网API
|
||||
│ │ └── user-center/ # 用户中心API
|
||||
│ ├── database/ # 数据库脚本
|
||||
│ ├── utils/ # 工具函数
|
||||
│ └── services/ # 微服务模块
|
||||
├── docs/ # 文档
|
||||
└── deployment/ # 部署相关配置
|
||||
W --> X
|
||||
X --> Y
|
||||
```
|
||||
|
||||
## 7. 用户权限管理架构
|
||||
## 4. 前端架构
|
||||
|
||||
### 7.1 集中式用户管理
|
||||
- 所有用户信息统一在政府监管平台进行管理
|
||||
- 各子系统仅负责登录验证和部分权限校验
|
||||
- 实现统一的RBAC权限模型
|
||||
- 通过用户中心API进行统一认证和授权
|
||||
### 4.1 管理后台 (admin-system)
|
||||
- 基于Vue 3和Element Plus构建
|
||||
- 采用组件化开发模式
|
||||
- 使用Vue Router管理路由
|
||||
- 使用Pinia进行状态管理
|
||||
- 通过Axios与后端API通信
|
||||
|
||||
### 7.2 权限体系
|
||||
- 基于角色的访问控制(RBAC)
|
||||
- 支持多角色分配
|
||||
- 细粒度权限控制(菜单权限、操作权限、数据权限)
|
||||
- 权限继承机制
|
||||
### 4.2 用户端 (frontend)
|
||||
- 基于Nuxt 3构建,支持SSR
|
||||
- 采用模块化架构设计
|
||||
- 使用Vue Router管理路由
|
||||
- 使用Vuex进行状态管理
|
||||
- 通过Axios与后端API通信
|
||||
|
||||
### 7.3 用户角色
|
||||
1. 超级管理员
|
||||
2. 政府监管员
|
||||
3. 银行信贷员
|
||||
4. 保险专员
|
||||
5. 交易管理员
|
||||
6. 商城管理员
|
||||
7. 养殖户
|
||||
8. 普通用户
|
||||
9. 数据分析师
|
||||
10. 系统审计员
|
||||
### 4.3 微信小程序 (miniprogram)
|
||||
- 基于微信小程序原生开发
|
||||
- 采用页面+组件的结构
|
||||
- 使用微信提供的API和组件
|
||||
- 通过wx.request与后端API通信
|
||||
|
||||
## 8. 大屏可视化系统架构
|
||||
## 5. 后端架构
|
||||
|
||||
### 8.1 系统概述
|
||||
大屏可视化系统是本项目的重要组成部分,主要用于展示锡林郭勒盟智慧养殖产业的整体数据、实时监控信息和分析结果。通过直观的图表和数据可视化方式,为管理者提供全面的产业洞察。
|
||||
### 5.1 微服务架构
|
||||
系统采用微服务架构设计,将业务功能拆分为独立的服务:
|
||||
|
||||
### 8.2 技术实现
|
||||
- **前端框架**: Vue.js 3 + ECharts + 自定义可视化组件
|
||||
- **可视化库**: Apache ECharts + D3.js
|
||||
- **响应式设计**: 支持多种大屏比例(16:9, 4:3等)
|
||||
- **实时数据**: WebSocket实时数据推送
|
||||
- **性能优化**: 虚拟滚动、数据分页等技术
|
||||
#### 5.1.1 Java微服务(主要)
|
||||
1. **养殖管理服务 (farming-service:8081)**
|
||||
- 功能:养殖场管理、牲畜档案、饲养记录等
|
||||
- 技术:Spring Boot, Spring Data JPA
|
||||
|
||||
### 8.3 功能模块
|
||||
1. **产业概览**: 展示整体产业规模、产值、增长率等关键指标
|
||||
2. **养殖监控**: 实时展示各牧场的养殖情况、环境数据
|
||||
3. **金融服务**: 展示贷款、保险等金融服务数据
|
||||
4. **交易统计**: 牛只交易量、价格趋势、区域分布等数据
|
||||
5. **运输跟踪**: 牛只运输实时状态和路径展示
|
||||
6. **风险预警**: 风险事件展示和预警信息推送
|
||||
7. **生态指标**: 环保数据、可持续发展指标展示
|
||||
8. **政府监管**: 展示政府监管相关数据和政策执行效果
|
||||
2. **用户中心服务 (user-center-service:8082)**
|
||||
- 功能:用户管理、权限控制、认证授权等
|
||||
- 技术:Spring Boot, Spring Security, JWT
|
||||
|
||||
### 8.4 设计特色
|
||||
- 融入锡林郭勒盟草原绿色主题
|
||||
- 采用蒙古族文化元素的UI设计
|
||||
- 支持多维度数据钻取和交互
|
||||
- 通过大屏可视化API获取实时和历史数据
|
||||
3. **交易服务 (trade-service:8083)**
|
||||
- 功能:活牛交易、订单管理、支付处理等
|
||||
- 技术:Spring Boot, Spring Data JPA
|
||||
|
||||
## 9. 系统集成架构
|
||||
4. **金融服务 (finance-service:8084)**
|
||||
- 功能:贷款申请、保险购买、金融产品等
|
||||
- 技术:Spring Boot, Spring Data JPA
|
||||
|
||||
### 9.1 外部系统集成
|
||||
1. 银行系统对接
|
||||
2. 政府监管平台对接
|
||||
3. 第三方系统集成(LDAP/AD、OAuth2.0等)
|
||||
4. 物联网设备集成(MQTT/CoAP协议)
|
||||
5. 云服务集成(腾讯云COS、短信服务等)
|
||||
5. **数据平台服务 (data-platform-service:8085)**
|
||||
- 功能:数据统计、报表生成、数据分析等
|
||||
- 技术:Spring Boot, Spring Data JPA
|
||||
|
||||
### 9.2 数据交换机制
|
||||
- RESTful API接口
|
||||
- 消息队列异步处理
|
||||
- 文件传输
|
||||
- 数据库同步
|
||||
6. **政务服务 (government-service:8086)**
|
||||
- 功能:政策发布、监管上报、审批流程等
|
||||
- 技术:Spring Boot, Spring Data JPA
|
||||
|
||||
## 10. 性能优化策略
|
||||
7. **大屏服务 (dashboard-service:8087)**
|
||||
- 功能:数据可视化、实时监控、大屏展示等
|
||||
- 技术:Spring Boot, Spring Data JPA
|
||||
|
||||
### 10.1 前端优化
|
||||
- 代码分割和按需加载
|
||||
- 图片懒加载和压缩
|
||||
- CDN加速静态资源
|
||||
- 浏览器缓存策略
|
||||
- 虚拟滚动处理大数据量展示
|
||||
- CDN加速静态资源
|
||||
- 浏览器缓存策略
|
||||
- 虚拟滚动处理大数据量展示
|
||||
8. **商城服务 (mall-service:8088)**
|
||||
- 功能:商品管理、购物车、订单处理等
|
||||
- 技术:Spring Boot, Spring Data JPA
|
||||
|
||||
### 10.2 后端优化
|
||||
- 数据库索引优化
|
||||
- API响应缓存(Redis)
|
||||
- 数据库连接池
|
||||
- 负载均衡部署
|
||||
- 异步任务处理(RabbitMQ)
|
||||
#### 5.1.2 Node.js服务(辅助)
|
||||
1. **AI能力服务 (ai-service:3001)**
|
||||
- 功能:图像识别、智能分析等AI相关功能
|
||||
- 技术:Node.js, Express.js
|
||||
|
||||
## 11. 系统监控与日志
|
||||
2. **遗留服务 (legacy-service:3002)**
|
||||
- 功能:部分原有系统功能的兼容
|
||||
- 技术:Node.js, Express.js
|
||||
|
||||
### 11.1 监控系统
|
||||
- 应用性能监控(APM)
|
||||
- 服务器资源监控(CPU、内存、磁盘等)
|
||||
- 数据库性能监控
|
||||
- 网络监控
|
||||
- 业务指标监控
|
||||
### 5.2 核心组件
|
||||
|
||||
### 11.2 日志系统
|
||||
- 统一日志格式
|
||||
- 日志分级管理(DEBUG、INFO、WARN、ERROR)
|
||||
- 日志收集与分析(ELK Stack)
|
||||
- 日志存储策略
|
||||
- 异常日志告警
|
||||
#### 5.2.1 API网关 (gateway)
|
||||
- 技术:Spring Cloud Gateway
|
||||
- 功能:
|
||||
- 请求路由转发
|
||||
- 负载均衡
|
||||
- 认证鉴权
|
||||
- 限流熔断
|
||||
- 日志记录
|
||||
|
||||
## 12. 部署架构
|
||||
#### 5.2.2 服务注册中心 (registry)
|
||||
- 技术:Eureka Server
|
||||
- 功能:
|
||||
- 服务注册与发现
|
||||
- 服务健康检查
|
||||
- 服务状态监控
|
||||
|
||||
### 12.1 开发环境
|
||||
- 本地开发服务器
|
||||
- 热重载功能
|
||||
- 代理配置解决跨域问题
|
||||
#### 5.2.3 配置中心 (config-server)
|
||||
- 技术:Spring Cloud Config
|
||||
- 功能:
|
||||
- 集中化配置管理
|
||||
- 配置动态刷新
|
||||
- 环境隔离配置
|
||||
|
||||
### 12.2 生产环境
|
||||
- Nginx反向代理服务器
|
||||
- 负载均衡配置
|
||||
- SSL证书配置
|
||||
- 日志收集和监控系统
|
||||
- 容器化部署(Docker)
|
||||
- 自动化部署(CI/CD)
|
||||
## 6. 数据架构
|
||||
|
||||
## 13. 容灾与备份策略
|
||||
### 6.1 数据库设计
|
||||
- **主数据库**: MySQL 8.0
|
||||
- **读写分离**: 主从复制
|
||||
- **分库分表**: 根据业务模块分库
|
||||
- **索引优化**: 合理设计索引提升查询性能
|
||||
|
||||
### 13.1 数据备份
|
||||
- 定时全量备份
|
||||
- 增量备份机制
|
||||
- 备份数据异地存储
|
||||
- 备份恢复演练
|
||||
### 6.2 缓存设计
|
||||
- **缓存技术**: Redis
|
||||
- **缓存策略**:
|
||||
- 热点数据缓存
|
||||
- 会话缓存
|
||||
- 分布式锁
|
||||
- **缓存更新**: 通过消息队列实现缓存同步
|
||||
|
||||
### 13.2 系统容灾
|
||||
- 多节点部署
|
||||
- 故障自动切换
|
||||
- 数据同步机制
|
||||
- 灾难恢复预案
|
||||
### 6.3 消息队列
|
||||
- **消息中间件**: RabbitMQ
|
||||
- **应用场景**:
|
||||
- 异步处理
|
||||
- 服务解耦
|
||||
- 流量削峰
|
||||
- 日志收集
|
||||
|
||||
## 7. 安全架构
|
||||
|
||||
### 7.1 认证授权
|
||||
- **认证方式**: JWT Token
|
||||
- **授权机制**: RBAC权限模型
|
||||
- **安全传输**: HTTPS加密
|
||||
- **密码安全**: BCrypt加密存储
|
||||
|
||||
### 7.2 数据安全
|
||||
- **数据传输**: SSL/TLS加密
|
||||
- **数据存储**: 敏感信息加密
|
||||
- **访问控制**: 基于角色的访问控制
|
||||
- **审计日志**: 关键操作日志记录
|
||||
|
||||
## 8. 部署架构
|
||||
|
||||
### 8.1 容器化部署
|
||||
- **容器技术**: Docker
|
||||
- **镜像管理**: Harbor私有镜像仓库
|
||||
- **容器编排**: Kubernetes
|
||||
- **服务发现**: Kubernetes Service
|
||||
|
||||
### 8.2 负载均衡
|
||||
- **反向代理**: Nginx
|
||||
- **负载策略**: 轮询、权重、IP哈希
|
||||
- **健康检查**: 定期检查服务状态
|
||||
- **SSL终止**: Nginx处理SSL加密解密
|
||||
|
||||
### 8.3 监控告警
|
||||
- **指标监控**: Prometheus
|
||||
- **可视化**: Grafana
|
||||
- **日志收集**: ELK Stack
|
||||
- **告警机制**: 邮件、短信、微信通知
|
||||
|
||||
## 9. 性能优化策略
|
||||
|
||||
### 9.1 前端优化
|
||||
- **资源压缩**: JS/CSS压缩合并
|
||||
- **图片优化**: WebP格式、懒加载
|
||||
- **缓存策略**: HTTP缓存、浏览器缓存
|
||||
- **代码分割**: 路由懒加载、组件懒加载
|
||||
|
||||
### 9.2 后端优化
|
||||
- **数据库优化**: 索引优化、查询优化
|
||||
- **缓存优化**: 多级缓存、缓存预热
|
||||
- **接口优化**: 接口合并、数据批量处理
|
||||
- **异步处理**: 消息队列异步处理耗时操作
|
||||
|
||||
### 9.3 网络优化
|
||||
- **CDN加速**: 静态资源CDN分发
|
||||
- **压缩传输**: Gzip压缩
|
||||
- **连接复用**: HTTP/2协议
|
||||
- **边缘计算**: 靠近用户的边缘节点处理
|
||||
|
||||
## 10. 扩展性设计
|
||||
|
||||
### 10.1 水平扩展
|
||||
- **服务拆分**: 微服务架构支持独立扩展
|
||||
- **数据库扩展**: 读写分离、分库分表
|
||||
- **缓存扩展**: Redis集群
|
||||
- **消息队列扩展**: RabbitMQ集群
|
||||
|
||||
### 10.2 弹性伸缩
|
||||
- **自动扩缩容**: Kubernetes HPA
|
||||
- **资源监控**: 实时监控资源使用情况
|
||||
- **负载均衡**: 动态调整流量分配
|
||||
- **故障自愈**: 自动重启失败服务
|
||||
|
||||
## 11. 高可用设计
|
||||
|
||||
### 11.1 服务高可用
|
||||
- **多实例部署**: 关键服务多实例运行
|
||||
- **故障转移**: 服务故障自动切换
|
||||
- **健康检查**: 定期检查服务状态
|
||||
- **超时重试**: 网络异常自动重试
|
||||
|
||||
### 11.2 数据高可用
|
||||
- **数据备份**: 定期备份重要数据
|
||||
- **主从复制**: 数据库主从同步
|
||||
- **灾难恢复**: 制定数据恢复预案
|
||||
- **一致性保证**: 分布式事务处理
|
||||
|
||||
## 12. 总结
|
||||
|
||||
本系统采用现代化的微服务架构设计,通过合理的技术选型和架构规划,能够满足畜牧管理系统的业务需求,具备良好的可扩展性、可维护性和高可用性。Java技术栈作为主要后端技术,Node.js作为辅助技术,能够充分发挥各自优势,构建一个稳定、高效的畜牧管理系统。
|
||||
@@ -13,7 +13,8 @@
|
||||
- **微信小程序矩阵**: 微信小程序原生开发 + uni-app
|
||||
|
||||
### 2.2 后端技术栈
|
||||
- **API服务**: Node.js + Express.js + RESTful API
|
||||
- **主要后端技术栈**: Java 8+ + Spring Boot + Spring Cloud
|
||||
- **辅助后端技术栈**: Node.js + Express.js + RESTful API
|
||||
- **数据库**: MySQL
|
||||
- **缓存**: Redis(用于会话管理和高频数据缓存)
|
||||
- **消息队列**: RabbitMQ(用于异步任务处理)
|
||||
@@ -21,52 +22,88 @@
|
||||
- **安全**: JWT认证 + 数据加密
|
||||
- **文件存储**: 腾讯云存储
|
||||
|
||||
### 2.3 开发优先级
|
||||
### 2.3 微服务架构规划
|
||||
|
||||
#### 核心业务服务(Java)
|
||||
| 服务名称 | 端口 | 功能描述 |
|
||||
|---------|------|----------|
|
||||
| farming-service | 8081 | 养殖管理服务 |
|
||||
| user-center-service | 8082 | 用户中心服务 |
|
||||
| finance-service | 8083 | 金融服务服务 |
|
||||
| government-service | 8084 | 政府监管服务 |
|
||||
| trade-service | 8085 | 交易服务 |
|
||||
| mall-service | 8086 | 商城服务 |
|
||||
| data-platform-service | 8087 | 数据平台服务 |
|
||||
| ai-service | 8088 | AI能力服务 |
|
||||
|
||||
#### 基础设施服务(Java)
|
||||
| 服务名称 | 端口 | 功能描述 |
|
||||
|---------|------|----------|
|
||||
| gateway | 8000 | 网关服务 |
|
||||
| registry | 8761 | 服务注册中心 |
|
||||
| config-server | 8888 | 配置服务器 |
|
||||
|
||||
#### 辅助服务(Node.js)
|
||||
| 服务名称 | 端口 | 功能描述 |
|
||||
|---------|------|----------|
|
||||
| dashboard-service | 3001 | 大屏可视化服务 |
|
||||
| website-service | 3002 | 官网服务 |
|
||||
|
||||
### 2.4 开发优先级
|
||||
1. **第一阶段(2025-09-01至2025-10-31)**: 核心功能(养殖管理、银行监管)
|
||||
2. **第二阶段(2025-11-01至2025-12-31)**: 扩展功能(保险监管、政府监管)
|
||||
3. **第三阶段(2026-01-01至2026-02-28)**: 交易和商城功能
|
||||
|
||||
### 2.4 技术风险与应对
|
||||
- **风险1**: 高并发场景下的数据库性能瓶颈
|
||||
### 2.5 技术风险与应对
|
||||
- **风险1**: 微服务间通信复杂性
|
||||
- **应对**: 使用Spring Cloud Gateway统一管理服务间通信,实施服务熔断和降级机制
|
||||
- **风险2**: 数据一致性问题
|
||||
- **应对**: 引入分布式事务解决方案(如Seata)
|
||||
- **风险3**: 高并发场景下的数据库性能瓶颈
|
||||
- **应对**: 引入读写分离和分库分表策略
|
||||
- **风险2**: 第三方服务(如微信支付)的集成稳定性
|
||||
- **风险4**: 第三方服务(如微信支付)的集成稳定性
|
||||
- **应对**: 设计降级方案和本地Mock服务
|
||||
|
||||
### 2.5 开发工具
|
||||
### 2.6 开发工具
|
||||
- VS Code (推荐IDE)
|
||||
- IntelliJ IDEA (Java开发推荐IDE)
|
||||
- Git (版本控制)
|
||||
- ESLint + Prettier (代码规范)
|
||||
- Jest/Vitest (单元测试)
|
||||
- ESLint + Prettier (前端代码规范)
|
||||
- Checkstyle (Java代码规范)
|
||||
- JUnit/TestNG (Java单元测试)
|
||||
- Jest/Vitest (前端单元测试)
|
||||
|
||||
## 3. 开发阶段规划
|
||||
|
||||
### 阶段一:项目初始化与基础架构搭建 (2周)
|
||||
### 阶段一:项目初始化与基础架构搭建 (3周)
|
||||
|
||||
#### 3.1 环境配置 (2天)
|
||||
- 搭建开发环境
|
||||
#### 3.1 环境配置 (3天)
|
||||
- 搭建Java开发环境(JDK、Maven)
|
||||
- 搭建Node.js开发环境
|
||||
- 配置代码编辑器
|
||||
- 初始化Git仓库
|
||||
- 配置ESLint和Prettier
|
||||
- 配置代码规范工具
|
||||
|
||||
#### 3.2 项目脚手架搭建 (3天)
|
||||
- 官网首页静态页面搭建
|
||||
- 6个专业管理系统Vue项目初始化(养殖管理、银行监管、保险监管、政府监管、活牛交易、商城管理)
|
||||
- 大屏可视化系统初始化
|
||||
- 微信小程序项目初始化(5个小程序)
|
||||
- 后端API服务搭建
|
||||
#### 3.2 微服务架构搭建 (5天)
|
||||
- 创建微服务基础项目结构
|
||||
- 配置服务注册中心
|
||||
- 配置配置服务器
|
||||
- 配置网关服务
|
||||
- 实现服务间通信机制
|
||||
|
||||
#### 3.3 基础设施集成 (3天)
|
||||
#### 3.3 基础设施集成 (4天)
|
||||
- 数据库设计和初始化
|
||||
- Redis缓存配置
|
||||
- 腾讯云存储服务集成
|
||||
- JWT身份认证实现
|
||||
- 基础权限管理框架
|
||||
|
||||
#### 3.4 开发规范制定 (2天)
|
||||
- 制定代码规范
|
||||
- 制定前后端代码规范
|
||||
- 确定目录结构
|
||||
- 编写开发文档
|
||||
|
||||
### 阶段二:核心功能开发 (8周)
|
||||
### 阶段二:核心功能开发 (10周)
|
||||
|
||||
#### 3.5 用户认证和权限系统 (1周)
|
||||
- 用户注册/登录功能
|
||||
@@ -110,77 +147,82 @@
|
||||
- 合同管理
|
||||
- 支付流程管理
|
||||
|
||||
### 阶段三:商城和小程序开发 (4周)
|
||||
|
||||
#### 3.12 商城管理系统 (1周)
|
||||
- 商品信息管理
|
||||
- 库存管理
|
||||
- 订单处理
|
||||
- 物流跟踪
|
||||
|
||||
#### 3.13 牛肉商城小程序(含认养功能) (1周)
|
||||
#### 3.13 数据平台服务 (1周)
|
||||
- 数据采集接口
|
||||
- 数据处理和分析
|
||||
- 报表生成
|
||||
|
||||
### 阶段三:小程序开发 (4周)
|
||||
|
||||
#### 3.14 牛肉商城小程序(含认养功能) (1周)
|
||||
- 商品浏览和搜索
|
||||
- 在线下单和支付
|
||||
- 订单查询和跟踪
|
||||
- 认养功能实现
|
||||
- 售后服务申请
|
||||
|
||||
#### 3.14 养殖管理小程序 (1周)
|
||||
#### 3.15 养殖管理小程序 (1周)
|
||||
- 移动端牛只档案查看
|
||||
- 饲养记录录入
|
||||
- 健康状况上报
|
||||
- 通知消息接收
|
||||
|
||||
#### 3.15 银行和保险监管小程序 (1周)
|
||||
#### 3.16 银行和保险监管小程序 (1周)
|
||||
- 银行监管小程序功能开发
|
||||
- 保险监管小程序功能开发
|
||||
|
||||
#### 3.16 活牛交易小程序 (1周)
|
||||
#### 3.17 活牛交易小程序 (1周)
|
||||
- 活牛信息发布
|
||||
- 在线交易撮合
|
||||
- 合同查看和管理
|
||||
|
||||
### 阶段四:数据系统和特色功能 (3周)
|
||||
|
||||
#### 3.17 数据分析系统 (1周)
|
||||
- 实时数据采集(IoT设备数据接入、用户行为数据采集)
|
||||
- 大数据分析引擎(数据清洗和预处理、统计分析模型)
|
||||
- 智能预警系统(异常数据检测、风险预警机制)
|
||||
### 阶段四:特色功能和大屏可视化 (3周)
|
||||
|
||||
#### 3.18 大屏可视化系统 (1周)
|
||||
- 数据图表展示
|
||||
- 仪表盘设计
|
||||
- 报表生成和导出
|
||||
|
||||
#### 3.19 特色功能实现 (1周)
|
||||
#### 3.19 AI能力服务 (1周)
|
||||
- 图像识别(牛只识别、健康状况识别)
|
||||
- 预测分析(市场价格预测、产犊预测)
|
||||
- 智能推荐
|
||||
|
||||
#### 3.20 特色功能实现 (1周)
|
||||
- 锡林郭勒盟地域元素融入
|
||||
- 蒙古族文化特色设计
|
||||
- 安格斯牛品牌突出展示
|
||||
|
||||
### 阶段五:系统集成与测试 (2周)
|
||||
### 阶段五:系统集成与测试 (3周)
|
||||
|
||||
#### 3.20 系统集成 (3天)
|
||||
#### 3.21 系统集成 (4天)
|
||||
- 内蒙古畜牧云对接(数据同步、接口规范)
|
||||
- 金融机构API对接(银行接口、保险接口)
|
||||
- 第三方服务集成
|
||||
|
||||
#### 3.21 测试用例编写 (3天)
|
||||
#### 3.22 测试用例编写 (4天)
|
||||
- 单元测试编写(核心功能模块单元测试覆盖率不低于80%)
|
||||
- 集成测试实现
|
||||
- E2E测试配置
|
||||
|
||||
#### 3.22 Bug修复与优化 (3天)
|
||||
#### 3.23 Bug修复与优化 (4天)
|
||||
- 功能测试与Bug修复
|
||||
- 性能调优(关键操作响应时间<2秒)
|
||||
- 用户体验优化
|
||||
|
||||
#### 3.23 部署准备 (3天)
|
||||
#### 3.24 部署准备 (3天)
|
||||
- 生产环境配置
|
||||
- 构建脚本优化
|
||||
- 部署文档编写
|
||||
- Docker配置
|
||||
|
||||
#### 3.24 项目验收与交付 (1天)
|
||||
#### 3.25 项目验收与交付 (1天)
|
||||
- 用户验收测试
|
||||
- 项目文档完善
|
||||
- 代码交接
|
||||
@@ -198,13 +240,14 @@
|
||||
|
||||
### 4.2 后端开发团队
|
||||
- 后端架构师(1名):负责后端架构设计和技术选型
|
||||
- API开发工程师(3名):负责后端API服务开发
|
||||
- Java微服务开发工程师(5名):负责Java微服务开发
|
||||
- Node.js开发工程师(2名):负责Node.js服务开发
|
||||
- 数据库工程师(1名):负责数据库设计和优化
|
||||
- 运维工程师(1名):负责部署和运维工作
|
||||
|
||||
### 4.3 数据分析团队
|
||||
- 数据分析师(1名):负责数据分析和可视化
|
||||
- 算法工程师(1名):负责智能预警系统开发
|
||||
- 算法工程师(1名):负责AI能力服务开发
|
||||
|
||||
### 4.4 测试团队
|
||||
- 测试工程师(2名):负责测试用例编写和执行
|
||||
@@ -212,9 +255,10 @@
|
||||
## 5. 质量保障
|
||||
|
||||
### 5.1 代码质量控制
|
||||
- 使用ESLint和Prettier统一代码风格
|
||||
- 使用ESLint和Prettier统一前端代码风格
|
||||
- 使用Checkstyle统一Java代码风格
|
||||
- 通过Code Review确保代码质量
|
||||
- 使用TypeScript增强代码可维护性
|
||||
- 使用TypeScript增强前端代码可维护性
|
||||
|
||||
### 5.2 测试策略
|
||||
- 单元测试:核心功能模块单元测试覆盖率不低于80%
|
||||
@@ -235,6 +279,7 @@
|
||||
- 支持Docker容器化部署
|
||||
- 支持云平台部署(腾讯云)
|
||||
- 支持负载均衡部署
|
||||
- 微服务独立部署和扩缩容
|
||||
|
||||
### 6.2 监控体系
|
||||
- 系统运行状态监控
|
||||
|
||||
@@ -12,12 +12,18 @@
|
||||
### 1.2 基础路径
|
||||
`/api/v1/farming`
|
||||
|
||||
### 1.3 权限控制
|
||||
### 1.3 技术栈
|
||||
- **后端**: Java Spring Boot + Spring Cloud (farming-service, 端口: 8081)
|
||||
- **数据库**: MySQL
|
||||
- **缓存**: Redis
|
||||
- **API文档**: Swagger
|
||||
|
||||
### 1.4 权限控制
|
||||
- 公开接口(无需认证):数据查询
|
||||
- 管理接口(需要认证):数据录入和管理
|
||||
- 系统接口(高级权限):批量操作和配置
|
||||
|
||||
### 1.4 全局错误码
|
||||
### 1.5 全局错误码
|
||||
| 状态码 | 说明 |
|
||||
|--------|--------------------|
|
||||
| 400 | 请求参数无效 |
|
||||
@@ -50,7 +56,8 @@ POST /cattles
|
||||
"id": "CATTLE001",
|
||||
"ear_tag": "NM000001",
|
||||
"created_at": "2024-01-20T10:00:00Z"
|
||||
}
|
||||
},
|
||||
"timestamp": "2024-01-20T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -77,7 +84,8 @@ POST /cattles/batch
|
||||
"error": "耳标号格式错误"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"timestamp": "2024-01-20T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -104,7 +112,8 @@ POST /vaccinations
|
||||
"cattle_id": "CATTLE001",
|
||||
"vaccine_type": "口蹄疫",
|
||||
"created_at": "2024-01-20T10:30:00Z"
|
||||
}
|
||||
},
|
||||
"timestamp": "2024-01-20T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -121,7 +130,8 @@ POST /vaccinations
|
||||
- 新增: 按照API文档规范标准统一格式
|
||||
- 优化: 统一响应格式和错误处理
|
||||
- 功能: 完善养殖管理接口定义
|
||||
## 3. 版本历史
|
||||
- 新增: Java后端技术栈说明
|
||||
- 新增: timestamp字段
|
||||
|
||||
### v1.0.0 (2024-01-20)
|
||||
- 新增: 按照API文档规范标准统一格式
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 养殖助手小程序 API 文档
|
||||
# 养殖助手小程序 API 文档 (v1.1.0)
|
||||
|
||||
## 1. 接口概述
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
### 1.2 基础路径
|
||||
`/api/mini/farming`
|
||||
|
||||
### 1.3 后端技术栈
|
||||
- **主要后端**: Java Spring Boot + Spring Cloud (farming-service, 端口: 8081)
|
||||
- **辅助后端**: Node.js + Express.js
|
||||
- **数据库**: MySQL
|
||||
- **缓存**: Redis
|
||||
|
||||
## 2. 接口明细
|
||||
|
||||
### 2.1 添加饲喂记录
|
||||
@@ -30,4 +36,13 @@ POST /vaccinations
|
||||
|
||||
## 3. 数据规范
|
||||
- 时间字段格式: YYYY-MM-DD HH:mm:ss
|
||||
- 数量单位统一使用kg
|
||||
- 数量单位统一使用kg
|
||||
|
||||
## 4. 版本历史
|
||||
|
||||
### v1.1.0 (2024-01-20)
|
||||
- 新增: Java后端技术栈说明
|
||||
- 优化: 接口规范说明
|
||||
|
||||
### v1.0.0 (2024-01-20)
|
||||
- 新增: 基础接口定义
|
||||
@@ -1,4 +1,4 @@
|
||||
# 用户中心系统 API 文档 (v1.0.0)
|
||||
# 用户中心系统 API 文档 (v1.1.0)
|
||||
|
||||
## 1. 接口概述
|
||||
|
||||
@@ -8,14 +8,20 @@
|
||||
- 权限控制
|
||||
|
||||
### 1.2 基础路径
|
||||
`/api/v1/[系统名称]`
|
||||
`/api/v1/users`
|
||||
|
||||
### 1.3 权限控制
|
||||
### 1.3 技术栈
|
||||
- **后端**: Java Spring Boot + Spring Cloud (user-center-service, 端口: 8082)
|
||||
- **数据库**: MySQL
|
||||
- **缓存**: Redis
|
||||
- **API文档**: Swagger
|
||||
|
||||
### 1.4 权限控制
|
||||
- 公开接口(无需认证):数据查询
|
||||
- 管理接口(需要认证):数据管理
|
||||
- 系统接口(高级权限):配置管理
|
||||
|
||||
### 1.4 全局错误码
|
||||
### 1.5 全局错误码
|
||||
| 状态码 | 说明 |
|
||||
|--------|--------------------|
|
||||
| 400 | 请求参数无效 |
|
||||
@@ -23,7 +29,6 @@
|
||||
| 403 | 权限不足 |
|
||||
| 404 | 资源不存在 |
|
||||
| 500 | 服务器内部错误 |
|
||||
`/api/v1/users`
|
||||
|
||||
## 2. 接口明细
|
||||
|
||||
@@ -48,7 +53,8 @@ POST /register
|
||||
"username": "testuser",
|
||||
"phone": "13800138000",
|
||||
"created_at": "2024-01-20T10:00:00Z"
|
||||
}
|
||||
},
|
||||
"timestamp": "2024-01-20T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -75,7 +81,8 @@ POST /login
|
||||
"roles": ["user"]
|
||||
},
|
||||
"expires_in": 86400
|
||||
}
|
||||
},
|
||||
"timestamp": "2024-01-20T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -85,7 +92,15 @@ POST /login
|
||||
| 200 | 成功 |
|
||||
| 400 | 参数校验失败 |
|
||||
| 401 | 认证失败 |
|
||||
## 3. 版本历史
|
||||
|
||||
## 4. 版本历史
|
||||
|
||||
### v1.1.0 (2024-01-20)
|
||||
- 新增: 按照API文档规范标准统一格式
|
||||
- 优化: 统一响应格式和错误处理
|
||||
- 功能: 完善用户中心接口定义
|
||||
- 新增: Java后端技术栈说明
|
||||
- 新增: timestamp字段
|
||||
|
||||
### v1.0.0 (2024-01-20)
|
||||
- 新增: 按照API文档规范标准统一格式
|
||||
|
||||
@@ -1,157 +1,214 @@
|
||||
# 后端API服务开发计划
|
||||
# 后端API开发计划
|
||||
|
||||
## 1. 系统概述
|
||||
## 1. 概述
|
||||
|
||||
后端API服务是整个平台的数据中枢,基于Node.js和Express.js构建,提供RESTful API接口,负责处理所有业务逻辑、数据存储和第三方服务集成。
|
||||
本项目采用双技术栈架构,后端服务基于Java Spring Boot和Node.js两种技术栈构建。Java技术栈作为主要后端技术,采用微服务架构设计;Node.js技术栈作为辅助技术,用于特定业务场景。
|
||||
|
||||
## 2. 技术架构
|
||||
## 2. 技术栈架构
|
||||
|
||||
- **运行环境**: Node.js
|
||||
### 2.1 Java技术栈(主要)
|
||||
- **核心框架**: Spring Boot 2.7.x, Spring Cloud 2021.x
|
||||
- **服务注册与发现**: Eureka
|
||||
- **配置中心**: Spring Cloud Config
|
||||
- **API网关**: Spring Cloud Gateway
|
||||
- **数据库**: MySQL 8.0, Spring Data JPA
|
||||
- **缓存**: Redis
|
||||
- **消息队列**: RabbitMQ
|
||||
- **安全框架**: Spring Security + JWT
|
||||
- **构建工具**: Maven 3.8.x
|
||||
|
||||
### 2.2 Node.js技术栈(辅助)
|
||||
- **运行环境**: Node.js 16.x
|
||||
- **Web框架**: Express.js
|
||||
- **API风格**: RESTful API
|
||||
- **数据库**: MySQL
|
||||
- **ORM**: Sequelize / TypeORM
|
||||
- **认证机制**: JWT
|
||||
- **安全防护**: Helmet, CORS, Rate Limiting
|
||||
- **日志管理**: Winston
|
||||
- **测试框架**: Jest
|
||||
- **API文档**: Swagger
|
||||
- **数据库**: MySQL 8.0
|
||||
- **ORM**: Sequelize
|
||||
- **构建工具**: NPM/Yarn
|
||||
|
||||
## 3. 功能模块详细计划
|
||||
## 3. Java后端开发计划
|
||||
|
||||
### 3.1 用户认证和权限模块 (3天)
|
||||
- 第1天:
|
||||
- 用户注册/登录接口实现
|
||||
- JWT Token生成和验证机制
|
||||
- 第2天:
|
||||
- 角色权限管理接口
|
||||
- RBAC权限控制实现
|
||||
- 第3天:
|
||||
- 用户信息管理接口
|
||||
- 密码重置和安全机制
|
||||
### 3.1 已完成模块
|
||||
1. **养殖管理服务 (farming-service:8081)**
|
||||
- 功能:养殖场管理、牲畜档案、饲养记录等
|
||||
- 状态:已完成开发和部署
|
||||
- 负责人:Java开发团队
|
||||
|
||||
### 3.2 牛只档案管理模块 (2天)
|
||||
- 第1天:
|
||||
- 牛只档案增删改查接口
|
||||
- 耳标二维码管理接口
|
||||
- 第2天:
|
||||
- 牛只生命周期记录接口
|
||||
- 牛只档案查询和筛选接口
|
||||
2. **用户中心服务 (user-center-service:8082)**
|
||||
- 功能:用户管理、权限控制、认证授权等
|
||||
- 状态:已完成开发和部署
|
||||
- 负责人:Java开发团队
|
||||
|
||||
### 3.3 饲养记录管理模块 (1天)
|
||||
- 第1天:
|
||||
- 饲料库存管理接口
|
||||
- 每日投喂量记录接口
|
||||
- 饲料库存预警接口
|
||||
### 3.2 开发中模块
|
||||
1. **交易服务 (trade-service:8083)**
|
||||
- 功能:活牛交易、订单管理、支付处理等
|
||||
- 状态:开发中
|
||||
- 进度:60%
|
||||
- 预计完成时间:2024年2月15日
|
||||
- 负责人:Java开发团队
|
||||
|
||||
### 3.4 繁殖管理模块 (1天)
|
||||
- 第1天:
|
||||
- 繁殖记录管理接口
|
||||
- 基因谱系分析接口
|
||||
- 产犊预测接口
|
||||
2. **金融服务 (finance-service:8084)**
|
||||
- 功能:贷款申请、保险购买、金融产品等
|
||||
- 状态:开发中
|
||||
- 进度:40%
|
||||
- 预计完成时间:2024年2月28日
|
||||
- 负责人:Java开发团队
|
||||
|
||||
### 3.5 环境监测模块 (1天)
|
||||
- 第1天:
|
||||
- 物联网设备数据接入接口
|
||||
- 环境数据存储和查询接口
|
||||
- 异常环境告警接口
|
||||
### 3.3 待开发模块
|
||||
1. **数据平台服务 (data-platform-service:8085)**
|
||||
- 功能:数据统计、报表生成、数据分析等
|
||||
- 状态:待开发
|
||||
- 预计开始时间:2024年2月10日
|
||||
- 预计完成时间:2024年3月15日
|
||||
- 负责人:Java开发团队
|
||||
|
||||
### 3.6 贷款管理模块 (2天)
|
||||
- 第1天:
|
||||
- 贷款申请和审批接口
|
||||
- 质押物状态监控接口
|
||||
- 第2天:
|
||||
- 还款计划跟踪接口
|
||||
- 风险评估数据接口
|
||||
2. **政务服务 (government-service:8086)**
|
||||
- 功能:政策发布、监管上报、审批流程等
|
||||
- 状态:待开发
|
||||
- 预计开始时间:2024年2月20日
|
||||
- 预计完成时间:2024年3月30日
|
||||
- 负责人:Java开发团队
|
||||
|
||||
### 3.7 保险管理模块 (2天)
|
||||
- 第1天:
|
||||
- 保险投保管理接口
|
||||
- 保单状态跟踪接口
|
||||
- 第2天:
|
||||
- 理赔申请和处理接口
|
||||
- 风险评估和预警接口
|
||||
3. **大屏服务 (dashboard-service:8087)**
|
||||
- 功能:数据可视化、实时监控、大屏展示等
|
||||
- 状态:待开发
|
||||
- 预计开始时间:2024年3月1日
|
||||
- 预计完成时间:2024年4月10日
|
||||
- 负责人:Java开发团队
|
||||
|
||||
### 3.8 政府监管模块 (1天)
|
||||
- 第1天:
|
||||
- 产业数据总览接口
|
||||
- 监管数据汇总接口
|
||||
- 政策发布接口
|
||||
4. **商城服务 (mall-service:8088)**
|
||||
- 功能:商品管理、购物车、订单处理等
|
||||
- 状态:待开发
|
||||
- 预计开始时间:2024年3月5日
|
||||
- 预计完成时间:2024年4月20日
|
||||
- 负责人:Java开发团队
|
||||
|
||||
### 3.9 交易管理模块 (2天)
|
||||
- 第1天:
|
||||
- 牛只信息发布接口
|
||||
- 在线交易撮合接口
|
||||
- 第2天:
|
||||
- 合同管理接口
|
||||
- 支付流程管理接口
|
||||
## 4. Node.js后端开发计划
|
||||
|
||||
### 3.10 商城管理模块 (2天)
|
||||
- 第1天:
|
||||
- 商品信息管理接口
|
||||
- 库存管理接口
|
||||
- 第2天:
|
||||
- 订单处理接口
|
||||
- 物流跟踪接口
|
||||
### 4.1 已完成模块
|
||||
1. **AI能力服务 (ai-service:3001)**
|
||||
- 功能:图像识别、智能分析等AI相关功能
|
||||
- 状态:已完成基础框架搭建
|
||||
- 负责人:Node.js开发团队
|
||||
|
||||
### 3.11 数据分析模块 (2天)
|
||||
- 第1天:
|
||||
- 实时数据采集接口
|
||||
- 大数据分析引擎接口
|
||||
- 第2天:
|
||||
- 智能预警系统接口
|
||||
- 数据可视化接口
|
||||
### 4.2 开发中模块
|
||||
1. **部分遗留服务 (legacy-service:3002)**
|
||||
- 功能:部分原有系统功能的兼容
|
||||
- 状态:重构中
|
||||
- 进度:30%
|
||||
- 预计完成时间:2024年2月20日
|
||||
- 负责人:Node.js开发团队
|
||||
|
||||
## 4. 技术实现要点
|
||||
### 4.3 待开发模块
|
||||
1. **其他辅助服务**
|
||||
- 功能:根据业务需求开发的辅助功能
|
||||
- 状态:待规划
|
||||
- 预计开始时间:2024年3月1日
|
||||
- 预计完成时间:根据需求确定
|
||||
- 负责人:Node.js开发团队
|
||||
|
||||
- 实现基于JWT的无状态认证
|
||||
- 使用RBAC角色权限管理系统
|
||||
- HTTPS加密传输
|
||||
- API接口安全防护(限流、防注入等)
|
||||
- 数据库连接池管理
|
||||
- 缓存机制优化性能(Redis)
|
||||
- 异步任务处理(RabbitMQ)
|
||||
- 文件存储服务集成(腾讯云)
|
||||
## 5. 微服务架构规划
|
||||
|
||||
## 5. 开发阶段规划
|
||||
### 5.1 核心服务端口分配
|
||||
| 服务名称 | 端口号 | 技术栈 | 状态 |
|
||||
|---------|--------|--------|------|
|
||||
| farming-service | 8081 | Java | 已完成 |
|
||||
| user-center-service | 8082 | Java | 已完成 |
|
||||
| trade-service | 8083 | Java | 开发中 |
|
||||
| finance-service | 8084 | Java | 开发中 |
|
||||
| data-platform-service | 8085 | Java | 待开发 |
|
||||
| government-service | 8086 | Java | 待开发 |
|
||||
| dashboard-service | 8087 | Java | 待开发 |
|
||||
| mall-service | 8088 | Java | 待开发 |
|
||||
|
||||
### 5.1 阶段一:基础框架搭建 (3天)
|
||||
- 项目初始化和环境配置
|
||||
- Express.js框架搭建
|
||||
- 数据库连接配置
|
||||
- JWT认证机制实现
|
||||
- 基础中间件集成(日志、安全、错误处理等)
|
||||
### 5.2 基础设施服务端口分配
|
||||
| 服务名称 | 端口号 | 技术栈 | 状态 |
|
||||
|---------|--------|--------|------|
|
||||
| gateway | 8080 | Java | 待开发 |
|
||||
| registry | 8761 | Java | 待开发 |
|
||||
| config-server | 8888 | Java | 待开发 |
|
||||
|
||||
### 5.2 阶段二:核心功能开发 (15天)
|
||||
- 用户认证和权限模块开发
|
||||
- 养殖管理相关模块开发
|
||||
- 金融服务相关模块开发
|
||||
- 政府监管模块开发
|
||||
- 交易管理模块开发
|
||||
- 商城管理模块开发
|
||||
- 数据分析模块开发
|
||||
### 5.3 Node.js服务端口分配
|
||||
| 服务名称 | 端口号 | 技术栈 | 状态 |
|
||||
|---------|--------|--------|------|
|
||||
| ai-service | 3001 | Node.js | 已完成基础框架 |
|
||||
| legacy-service | 3002 | Node.js | 重构中 |
|
||||
|
||||
### 5.3 阶段三:系统集成与测试 (4天)
|
||||
- 第三方服务集成(银行、保险、腾讯云等)
|
||||
- API接口测试和优化
|
||||
- 性能测试和调优
|
||||
- 安全测试和漏洞修复
|
||||
- API文档完善
|
||||
## 6. 技术迁移策略
|
||||
|
||||
## 6. 质量保障措施
|
||||
### 6.1 迁移原则
|
||||
1. **渐进式迁移**: 逐步将Node.js服务迁移到Java微服务架构
|
||||
2. **业务优先**: 优先迁移核心业务服务
|
||||
3. **兼容性保证**: 确保迁移过程中不影响现有业务
|
||||
4. **数据一致性**: 保证数据迁移的完整性和一致性
|
||||
|
||||
- 单元测试覆盖率达到80%以上
|
||||
- 接口测试确保API稳定性
|
||||
- 性能测试确保支持500+并发用户
|
||||
- 安全测试防止SQL注入、XSS等攻击
|
||||
- 代码审查机制确保代码质量
|
||||
### 6.2 迁移步骤
|
||||
1. **评估阶段**: 评估现有Node.js服务的复杂度和迁移难度
|
||||
2. **设计阶段**: 设计Java微服务架构和接口规范
|
||||
3. **开发阶段**: 开发新的Java微服务
|
||||
4. **测试阶段**: 进行全面的功能测试和性能测试
|
||||
5. **部署阶段**: 逐步替换现有Node.js服务
|
||||
6. **监控阶段**: 监控新服务的运行状态和性能表现
|
||||
|
||||
## 7. 部署与运维
|
||||
## 7. 开发规范
|
||||
|
||||
- 支持Docker容器化部署
|
||||
- 支持云平台部署(腾讯云)
|
||||
- 支持负载均衡部署
|
||||
- 系统运行状态监控
|
||||
- 性能监控指标
|
||||
- 异常告警功能
|
||||
- 日志收集和分析
|
||||
### 7.1 代码规范
|
||||
1. **Java代码规范**: 遵循阿里巴巴Java开发手册
|
||||
2. **Node.js代码规范**: 遵循JavaScript Standard Style
|
||||
3. **API设计规范**: 遵循RESTful API设计原则
|
||||
4. **数据库设计规范**: 遵循数据库设计范式
|
||||
|
||||
### 7.2 文档规范
|
||||
1. **API文档**: 每个接口必须提供详细的API文档
|
||||
2. **设计文档**: 每个服务必须提供架构设计文档
|
||||
3. **部署文档**: 每个服务必须提供部署说明文档
|
||||
|
||||
### 7.3 测试规范
|
||||
1. **单元测试**: 每个模块必须提供单元测试
|
||||
2. **集成测试**: 服务间接口必须提供集成测试
|
||||
3. **性能测试**: 关键服务必须提供性能测试报告
|
||||
|
||||
## 8. 测试策略
|
||||
|
||||
### 8.1 测试类型
|
||||
1. **单元测试**: 验证每个模块的功能正确性
|
||||
2. **集成测试**: 验证服务间的接口调用
|
||||
3. **性能测试**: 验证系统的性能表现
|
||||
4. **安全测试**: 验证系统的安全性
|
||||
|
||||
### 8.2 测试工具
|
||||
1. **Java测试**: JUnit, Mockito
|
||||
2. **Node.js测试**: Jest, Mocha
|
||||
3. **API测试**: Postman, JMeter
|
||||
4. **性能测试**: JMeter, Gatling
|
||||
|
||||
## 9. 部署策略
|
||||
|
||||
### 9.1 部署环境
|
||||
1. **开发环境**: 本地开发环境
|
||||
2. **测试环境**: 内部测试环境
|
||||
3. **预生产环境**: 预生产验证环境
|
||||
4. **生产环境**: 正式生产环境
|
||||
|
||||
### 9.2 部署方式
|
||||
1. **容器化部署**: 使用Docker容器化部署
|
||||
2. **自动化部署**: 使用Jenkins实现CI/CD
|
||||
3. **蓝绿部署**: 关键服务采用蓝绿部署策略
|
||||
4. **滚动更新**: 普通服务采用滚动更新策略
|
||||
|
||||
## 10. 风险控制
|
||||
|
||||
### 10.1 技术风险
|
||||
1. **技术栈切换风险**: 开发团队需要适应新的技术栈
|
||||
2. **性能风险**: 微服务架构可能带来性能损耗
|
||||
3. **数据一致性风险**: 分布式系统中的数据一致性问题
|
||||
|
||||
### 10.2 管理风险
|
||||
1. **进度风险**: 多个服务并行开发可能影响整体进度
|
||||
2. **沟通风险**: 不同技术团队间的沟通协调问题
|
||||
3. **质量风险**: 快速开发可能影响代码质量
|
||||
|
||||
### 10.3 应对措施
|
||||
1. **技术培训**: 组织技术培训帮助团队适应新技术
|
||||
2. **性能优化**: 通过缓存、异步等手段优化性能
|
||||
3. **监控告警**: 建立完善的监控告警机制
|
||||
4. **代码审查**: 严格执行代码审查制度
|
||||
5. **定期评审**: 定期进行项目进度评审
|
||||
Reference in New Issue
Block a user