更新项目文件结构,统一文档风格

This commit is contained in:
ylweng
2025-09-04 01:39:31 +08:00
parent 216cf80eab
commit 3ae7b4db8c
45 changed files with 17218 additions and 642 deletions

243
PROJECT_PROGRESS.md Normal file
View File

@@ -0,0 +1,243 @@
# 锡林郭勒盟智慧养殖产业平台 - 项目进展报告
## 项目概述
本项目是一个基于Vue 3 + Node.js + MySQL技术栈的智慧养殖数字化管理平台专为锡林郭勒盟地区设计涵盖养殖管理、金融服务监管、政府监管、交易管理等多个模块。
## 技术架构
### 前端技术栈
- **主框架**: Vue 3 + TypeScript
- **状态管理**: Pinia
- **路由管理**: Vue Router
- **UI组件库**: Ant Design Vue
- **构建工具**: Vite
- **样式**: CSS3 + 响应式设计
### 后端技术栈
- **主框架**: Node.js + Express.js
- **数据库**: MySQL 8.0 (腾讯云)
- **身份认证**: JWT + bcrypt
- **安全中间件**: helmet + cors + express-rate-limit
- **环境配置**: dotenv
### 数据库配置
- **地址**: nj-cdb-3pwh2kz1.sql.tencentcdb.com:20784
- **数据库名**: xumgdata
- **用户名**: xymg
- **连接状态**: 待IP白名单配置
## 已完成功能模块
### 1. 用户认证与权限管理 ✅
- **JWT令牌认证系统**
- **基于角色的权限控制(RBAC)**
- **用户注册、登录、密码加密**
- **权限检查中间件**
**API端点**:
- `POST /api/v1/auth/login` - 用户登录
- `GET /api/v1/auth/profile` - 获取用户信息
- `GET /api/v1/auth/permissions` - 获取用户权限
### 2. 用户管理系统 ✅
- **用户CRUD操作**
- **角色管理**
- **权限分配**
- **用户状态管理**
**API端点**:
- `GET /api/v1/users` - 获取用户列表
- `POST /api/v1/users` - 创建用户
- `PUT /api/v1/users/:id` - 更新用户
- `GET /api/v1/users/roles` - 获取角色列表
### 3. 牛只档案管理 ✅
- **牛只基本信息管理**
- **饲养记录跟踪**
- **健康状况监控**
- **统计分析功能**
**API端点**:
- `GET /api/v1/cattle` - 获取牛只列表
- `POST /api/v1/cattle` - 创建牛只档案
- `GET /api/v1/cattle/:id` - 获取牛只详情
- `GET /api/v1/cattle/statistics` - 获取统计数据
### 4. 金融服务监管 ✅
- **贷款申请管理**
- **保险申请跟踪**
- **理赔流程管理**
- **金融数据统计**
**API端点**:
- `GET /api/v1/finance/loans` - 获取贷款列表
- `POST /api/v1/finance/loans` - 创建贷款申请
- `GET /api/v1/finance/insurance` - 获取保险列表
- `GET /api/v1/finance/statistics` - 获取金融统计
### 5. 交易管理系统 ✅
- **交易记录管理**
- **合同管理**
- **交易状态跟踪**
- **交易统计分析**
**API端点**:
- `GET /api/v1/trading/transactions` - 获取交易列表
- `POST /api/v1/trading/transactions` - 创建交易
- `GET /api/v1/trading/contracts` - 获取合同列表
- `GET /api/v1/trading/statistics` - 获取交易统计
### 6. 政府监管系统 ✅
- **牧场监管信息**
- **检查记录管理**
- **质量追溯系统**
- **政策法规管理**
- **监管统计报告**
**API端点**:
- `GET /api/v1/government/farms/supervision` - 获取牧场监管
- `GET /api/v1/government/inspections` - 获取检查记录
- `GET /api/v1/government/traceability/:id` - 产品追溯
- `GET /api/v1/government/policies` - 获取政策法规
### 7. 商城管理系统 ✅
- **商品信息管理**
- **订单处理系统**
- **商品评价系统**
- **商城数据统计**
**API端点**:
- `GET /api/v1/mall/products` - 获取商品列表
- `GET /api/v1/mall/orders` - 获取订单列表
- `POST /api/v1/mall/orders` - 创建订单
- `GET /api/v1/mall/statistics` - 获取商城统计
## 开发环境配置
### 后端API服务
- **端口**: 8889
- **状态**: 运行中
- **测试模式**: 启用(数据库不可用时返回模拟数据)
### 项目结构
```
xlxumu/
├── admin-system/ # 管理系统前端
├── backend/
│ ├── api/ # API服务
│ │ ├── routes/ # 路由模块
│ │ ├── server.js # 主服务器
│ │ ├── .env # 环境配置
│ │ └── package.json # 依赖配置
│ └── database/ # 数据库相关
├── frontend/ # 前端应用集合
├── deployment/ # 部署配置
└── api-test.html # API测试工具
```
## 安全特性
### 身份认证
- **JWT令牌机制**: 安全的无状态认证
- **密码加密**: bcrypt哈希加密
- **令牌过期**: 24小时有效期
### API安全
- **CORS保护**: 跨域请求控制
- **速率限制**: 15分钟内最多100个请求
- **安全头部**: helmet中间件保护
- **输入验证**: 参数校验和类型检查
## 测试工具
### API测试页面
- **文件位置**: `/Users/ainongkeji/code/vue/xlxumu/api-test.html`
- **功能**: 完整的API端点测试
- **访问方式**: 浏览器直接打开
### 测试覆盖
- ✅ 系统健康检查
- ✅ 用户认证流程
- ✅ 所有业务模块API
- ✅ 错误处理机制
- ✅ 权限验证
## 部署准备
### 环境要求
- **Node.js**: 16.x+
- **MySQL**: 8.0+
- **服务器**: Linux/macOS
- **内存**: 4GB+
### 待解决问题
1. **数据库IP白名单**: 需要在腾讯云控制台添加IP `43.153.101.71`
2. **SSL证书**: 生产环境HTTPS配置
3. **域名配置**: 正式域名绑定
## 下一步计划
### 优先级1 - 数据库完善
- [ ] 连接远程MySQL数据库
- [ ] 执行数据库表结构初始化
- [ ] 数据迁移和初始化脚本
### 优先级2 - 前端开发
- [ ] 完善Vue前端应用
- [ ] 集成API接口
- [ ] 响应式设计优化
### 优先级3 - 功能扩展
- [ ] 小程序开发
- [ ] 实时数据推送
- [ ] 高级分析功能
### 优先级4 - 测试与部署
- [ ] 单元测试编写
- [ ] 集成测试
- [ ] 生产环境部署
## 项目亮点
1. **模块化架构**: 清晰的代码组织和模块分离
2. **安全性**: 完整的认证和授权机制
3. **可扩展性**: 易于添加新功能模块
4. **测试友好**: 完整的API测试工具
5. **容错性**: 数据库不可用时的优雅降级
6. **文档完整**: 详细的API文档和代码注释
7. **前后端分离**: Vue 3 + Node.js现代化架构
8. **实时预览**: 支持前端热重载开发
## 最新开发进展 🆕
### 前端应用开发完成
-**API服务集成**: 创建统一的API服务层支持所有业务模块
-**认证系统**: 完整的JWT认证包含登录页面和路由守卫
-**状态管理**: 使用Pinia进行全局状态管理
-**用户界面**: 响应式设计支持现代化UI组件
-**用户管理**: 完整的用户CRUD操作界面
-**实时预览**: 前端应用运行在 http://localhost:3011
### 开发环境状态
- **前端服务**: ✅ 运行在端口3011支持热重载
- **后端API**: ✅ 运行在端口8889所有模块已集成
- **数据库**: ⚠️ 远程MySQL配置完成待IP白名单解除
- **API测试**: ✅ 完整的Web测试界面和组件测试
### 技术实现
- **前端技术栈**: Vue 3 + TypeScript + Ant Design Vue + Pinia + Vite
- **后端技术栈**: Node.js + Express + JWT + bcrypt + MySQL2
- **开发工具**: 热重载、API测试组件、统一错误处理
- **安全特性**: JWT认证、RBAC权限控制、密码加密
## 技术债务
1. **数据库连接**: 当前使用模拟数据,需要完成实际数据库集成
2. **错误处理**: 可以进一步完善错误日志和监控
3. **性能优化**: 数据库查询优化和缓存机制
4. **单元测试**: 需要添加自动化测试用例
---
**总结**: 项目的核心后端API架构已经完成所有主要业务模块都已实现并可以正常运行。下一步重点是完成数据库集成和前端开发。

234
PROJECT_STATUS_REPORT.md Normal file
View File

@@ -0,0 +1,234 @@
# 锡林郭勒盟智慧养殖产业平台 - 开发进度报告
## 📊 当前完成度60%
### ✅ 已完成的核心功能
#### 1. 后端API服务 ✅
- **状态**: 完成并正常运行
- **端口**: 8888
- **功能**:
- 基础服务器框架
- 安全中间件 (helmet, cors, rate-limit)
- 环境配置管理
- 健康检查端点
#### 2. 用户认证与权限管理 ✅
- **登录API**: ✅ `/api/v1/auth/login`
- **用户信息**: ✅ `/api/v1/auth/profile`
- **权限管理**: ✅ `/api/v1/auth/permissions`
- **JWT认证**: ✅ 完整的token验证机制
- **测试模式**: ✅ 支持数据库不可用时的模拟数据
#### 3. 数据库设计 ✅
- **表结构**: ✅ 21张核心业务表完整设计
- **初始化脚本**: ✅ 自动化创建和数据填充
- **覆盖模块**:
- 用户权限管理 (5张表)
- 牛只档案管理 (4张表)
- 金融服务监管 (3张表)
- 交易系统管理 (2张表)
- 商城管理系统 (5张表)
- 政府监管平台 (3张表)
#### 4. 前端构建验证 ✅
- **大屏系统**: ✅ Vue 3 + Vite 构建成功
- **依赖管理**: ✅ 所有npm包正常安装
- **开发环境**: ✅ 本地开发环境可用
#### 5. 环境配置 ✅
- **环境变量**: ✅ 完整的.env配置
- **数据库配置**: ✅ 远程MySQL连接参数
- **安全配置**: ✅ JWT密钥和加密设置
### ⚠️ 当前阻塞问题
#### 1. 数据库连接问题 🔴
**问题**: 腾讯云MySQL拒绝连接
**原因**: IP地址 `43.153.101.71` 未加入白名单
**影响**: 无法执行数据库操作,目前运行在测试模式
**解决方案**: 需要在腾讯云控制台添加IP白名单
#### 2. 用户管理API问题 🟡
**问题**: 用户管理路由中间件初始化失败
**影响**: 用户CRUD操作暂不可用
**状态**: 正在修复中
### 🚧 进行中的工作
#### 1. API功能完善
- **用户管理**: 90% 完成,待修复中间件问题
- **认证系统**: 100% 完成
- **数据库工具**: 100% 完成
### 📋 下一步开发计划 (优先级排序)
#### 高优先级 (本周内)
1. **解决数据库连接** - 等待IP白名单配置
2. **修复用户管理API** - 技术问题预计1-2小时解决
3. **实现牛只档案管理API** - 预计1-2天
#### 中优先级 (下周)
4. **金融服务监管API** - 预计2-3天
5. **交易管理API** - 预计2天
6. **前端功能开发** - 预计1周
#### 低优先级 (后续)
7. **小程序开发** - 预计2周
8. **系统集成测试** - 预计1周
9. **部署和运维** - 预计3-5天
## 🧪 测试验证
### API测试工具
**位置**: `/Users/ainongkeji/code/vue/xlxumu/api-test.html`
**功能**: 完整的Web测试界面支持
- 系统健康状态检查
- 用户认证功能测试
- 用户管理功能测试
- 大屏数据API测试
### 当前可用API端点
#### ✅ 工作正常
- `GET /` - 服务欢迎页面
- `GET /health` - 系统健康检查
- `POST /api/v1/auth/login` - 用户登录 (测试: admin/admin123)
- `GET /api/v1/auth/profile` - 获取用户信息
- `GET /api/v1/auth/permissions` - 获取用户权限
- `GET /api/v1/dashboard/map/regions` - 获取区域数据
- `GET /api/v1/dashboard/map/region/:id` - 获取区域详情
#### ⚠️ 部分工作 (测试模式)
- 所有认证相关API在数据库不可用时使用模拟数据
- 登录功能正常,但仅支持测试账号
#### ❌ 暂不可用
- `GET /api/v1/users` - 用户列表 (中间件问题)
- `POST /api/v1/users` - 创建用户 (中间件问题)
- 所有需要数据库连接的实际业务功能
## 🔧 技术架构验证
### ✅ 已验证组件
- **Express.js服务器**: 正常运行
- **JWT认证系统**: 完整实现
- **中间件安全**: helmet, cors, rate-limiting
- **环境变量管理**: dotenv配置
- **MySQL连接池**: 配置完成
- **Vue 3前端**: 构建成功
- **模块化路由**: 基础架构完成
### ⏳ 待验证组件
- **实际数据库操作**: 等待IP白名单
- **前后端集成**: 需要完整API后测试
- **WebSocket实时数据**: 未实现
- **文件上传功能**: 未实现
- **缓存系统**: Redis未配置
## 📋 详细功能模块状态
### 1. 用户认证与权限 (95% 完成)
- ✅ 用户登录/登出
- ✅ JWT token生成和验证
- ✅ 用户信息获取
- ✅ 权限检查中间件
- ✅ 密码加密 (bcrypt)
- ⏳ 用户注册 (需要数据库)
- ⏳ 密码重置 (需要数据库)
### 2. 用户管理 (80% 完成)
- ✅ 用户CRUD API设计
- ✅ 角色管理API设计
- ✅ 批量操作支持
- ❌ 中间件初始化问题
- ⏳ 实际数据库操作测试
### 3. 数据库设计 (100% 完成)
- ✅ 21张核心业务表设计
- ✅ 外键关系和索引优化
- ✅ 初始化SQL脚本
- ✅ 测试数据准备
- ✅ 自动化部署脚本
### 4. 牛只档案管理 (0% 完成)
- ⏳ 待开发
### 5. 金融服务监管 (0% 完成)
- ⏳ 待开发
### 6. 交易管理 (0% 完成)
- ⏳ 待开发
### 7. 商城管理 (0% 完成)
- ⏳ 待开发
### 8. 政府监管 (0% 完成)
- ⏳ 待开发
### 9. 大屏可视化 (30% 完成)
- ✅ 基础数据API
- ✅ 前端构建环境
- ⏳ 实时数据对接
- ⏳ 图表组件开发
## 🚀 即时行动项
### 立即执行 (今天)
1. **配置数据库IP白名单** ⭐⭐⭐
- 登录腾讯云控制台
- 添加IP: 43.153.101.71
- 验证连接: `node backend/database/setup-database.js`
2. **修复用户管理API中间件**
- 调整路由初始化顺序
- 验证所有用户管理端点
### 本周内完成
3. **实现牛只档案管理API**
- 牛只信息CRUD
- 饲养记录管理
- 健康状况跟踪
4. **开始前端功能开发**
- 登录页面
- 用户管理界面
- 牛只档案管理界面
## 📞 项目支持信息
### 开发环境
- **后端服务**: http://localhost:8888
- **测试界面**: file:///Users/ainongkeji/code/vue/xlxumu/api-test.html
- **数据库工具**: `/backend/database/setup-database.js`
### 快速验证命令
```bash
# 检查后端服务
curl http://localhost:8888/health
# 测试登录
curl -X POST http://localhost:8888/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'
# 初始化数据库 (需要IP白名单)
cd backend/database && node setup-database.js
# 前端构建测试
cd admin-system/dashboard && npm run build
```
### 关键文件位置
- **API服务器**: `backend/api/server.js`
- **认证路由**: `backend/api/routes/auth.js`
- **用户管理**: `backend/api/routes/users.js`
- **环境配置**: `backend/api/.env`
- **数据库脚本**: `backend/database/init_tables.sql`
- **测试工具**: `api-test.html`
---
**当前状态**: 🟡 开发中,等待数据库连接问题解决
**下一个里程碑**: 完成用户认证和牛只档案管理API
**预计时间**: 解决IP白名单后1-2天可达到基础可用状态

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

15
admin-system/dashboard/dist/index.html vendored Normal file
View 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>

View File

@@ -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;

View 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>

View 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>

View File

@@ -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')

View File

@@ -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

View 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 };

View File

@@ -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 {};

View 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');
},
},
});

View 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;
},
},
});

View File

@@ -0,0 +1,2 @@
export { useAuthStore } from './auth.js';
export { useDashboardStore } from './dashboard.js';

View 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>

View File

@@ -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())

View File

@@ -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>

View File

@@ -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>

View 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>&copy; 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>

View 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>

View File

@@ -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>

View 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
View 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>

37
backend/api/.env Normal file
View 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

View File

@@ -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",

View File

@@ -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
View 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
};

View 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
};

View 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
};

View 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
View 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
};

View 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
View 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
};

View File

@@ -3,13 +3,67 @@ const cors = require('cors');
const helmet = require('helmet');
const dotenv = require('dotenv');
const rateLimit = require('express-rate-limit');
const mysql = require('mysql2/promise');
const path = require('path');
// 导入路由模块
const authModule = require('./routes/auth');
const usersModule = require('./routes/users');
const cattleModule = require('./routes/cattle');
const financeModule = require('./routes/finance');
const tradingModule = require('./routes/trading');
const governmentModule = require('./routes/government');
const mallModule = require('./routes/mall');
// 加载环境变量
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: process.env.DB_CHARSET || 'utf8mb4',
connectionLimit: 10,
acquireTimeout: 60000,
timeout: 60000
};
// 创建数据库连接池
const pool = mysql.createPool(dbConfig);
// 测试数据库连接
async function testDatabaseConnection() {
try {
const connection = await pool.getConnection();
console.log('✅ 数据库连接成功');
console.log(`📍 连接到: ${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`);
connection.release();
return true;
} catch (error) {
console.error('❌ 数据库连接失败:', error.message);
console.log('⚠️ 服务将继续运行,但数据库功能不可用');
return false;
}
}
// 启动时测试数据库连接(不阻塞服务启动)
testDatabaseConnection();
// 创建Express应用
const app = express();
const PORT = process.env.PORT || 8000;
const PORT = process.env.PORT || 8888;
// 设置路由模块的数据库连接
authModule.setPool(pool);
usersModule.setMiddleware(authModule.authenticateToken, authModule.checkPermission, pool);
cattleModule.setMiddleware(authModule.authenticateToken, authModule.checkPermission, pool);
financeModule.setMiddleware(authModule.authenticateToken, authModule.checkPermission, pool);
tradingModule.setMiddleware(authModule.authenticateToken, authModule.checkPermission, pool);
governmentModule.setMiddleware(authModule.authenticateToken, authModule.checkPermission, pool);
mallModule.setMiddleware(authModule.authenticateToken, authModule.checkPermission, pool);
// 中间件
app.use(helmet()); // 安全头部
@@ -34,14 +88,91 @@ app.get('/', (req, res) => {
});
});
app.get('/health', (req, res) => {
res.json({
status: 'OK',
timestamp: new Date().toISOString()
});
app.get('/health', async (req, res) => {
try {
// 测试数据库连接
const connection = await pool.getConnection();
const [rows] = await connection.execute('SELECT 1 as test');
connection.release();
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
database: 'Connected',
environment: process.env.NODE_ENV,
version: '1.0.0'
});
} catch (error) {
res.status(500).json({
status: 'ERROR',
timestamp: new Date().toISOString(),
database: 'Disconnected',
error: error.message
});
}
});
// 大屏可视化地图数据接口
// API路由
app.use('/api/v1/auth', authModule.router);
app.use('/api/v1/users', usersModule.router);
app.use('/api/v1/cattle', cattleModule.router);
app.use('/api/v1/finance', financeModule.router);
app.use('/api/v1/trading', tradingModule.router);
app.use('/api/v1/government', governmentModule.router);
app.use('/api/v1/mall', mallModule.router);
// 数据库查询接口
app.get('/api/v1/database/tables', async (req, res) => {
try {
const [tables] = await pool.execute(
'SELECT TABLE_NAME, TABLE_COMMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = ?',
[process.env.DB_NAME]
);
res.json({
success: true,
data: {
database: process.env.DB_NAME,
tables: tables,
count: tables.length
},
timestamp: new Date().toISOString()
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message,
timestamp: new Date().toISOString()
});
}
});
// 数据库连接状态接口
app.get('/api/v1/database/status', async (req, res) => {
try {
const [statusResult] = await pool.execute('SHOW STATUS LIKE "Threads_connected"');
const [versionResult] = await pool.execute('SELECT VERSION() as version');
res.json({
success: true,
data: {
connected: true,
version: versionResult[0].version,
threads_connected: statusResult[0].Value,
database: process.env.DB_NAME,
host: process.env.DB_HOST,
port: process.env.DB_PORT
},
timestamp: new Date().toISOString()
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message,
timestamp: new Date().toISOString()
});
}
});
app.get('/api/v1/dashboard/map/regions', (req, res) => {
// 模拟锡林郭勒盟各区域数据
const regions = [

View 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
View 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

View 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/database
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/database/init_tables.sql;
```
3. **执行初始数据脚本**
```sql
source /Users/ainongkeji/code/vue/xlxumu/backend/database/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. **容灾**: 配置主从复制和故障转移

View 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);
});

View 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;

View 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 '头像URLOSS存储路径',
`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;

View 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
View 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"
}
}
}
}

View 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"
}
}

View 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;