docs: 更新项目文档和数据库设计,添加官网相关功能
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24086,3 +24086,4 @@
|
||||
/frontend/dashboard/node_modules/zrender/package.README.md
|
||||
/frontend/dashboard/node_modules/zrender/README.md
|
||||
/frontend/dashboard/node_modules/.package-lock.json
|
||||
/frontend/dashboard/node_modules/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# xlxumu - 锡林郭勒盟地区养殖产业平台
|
||||
# xlxumu - 锡林郭勒盟地区智慧养殖产业平台
|
||||
|
||||
## 项目概述
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
## 文档目录
|
||||
|
||||
详细文档请参见 [docs/README.md](file:///E:/vue/xlxumu/docs/README.md) 文件,其中包含所有系统文档的完整列表和链接。
|
||||
详细文档请参见 [docs/README.md](docs/README.md) 文件,其中包含所有系统文档的完整列表和链接。
|
||||
|
||||
## 技术栈
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
|
||||
## 部署说明
|
||||
|
||||
详细的部署说明请参考 [deployment/README.md](file:///E:/vue/xlxumu/deployment/README.md) 文件。
|
||||
详细的部署说明请参考 [deployment/README.md](deployment/README.md) 文件。
|
||||
|
||||
## 开发说明
|
||||
|
||||
|
||||
@@ -1,30 +1,13 @@
|
||||
{
|
||||
"name": "xlxumu-api",
|
||||
"version": "1.0.0",
|
||||
"description": "锡林郭勒盟安格斯牛数字化管理平台API服务",
|
||||
"author": "xlxumu team",
|
||||
"description": "锡林郭勒盟智慧养殖数字化管理平台API服务",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js",
|
||||
"test": "jest"
|
||||
"dev": "nodemon server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.0",
|
||||
"cors": "^2.8.5",
|
||||
"helmet": "^6.0.0",
|
||||
"dotenv": "^16.0.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"mysql2": "^3.0.0",
|
||||
"sequelize": "^6.0.0",
|
||||
"joi": "^17.0.0",
|
||||
"winston": "^3.8.0",
|
||||
"express-rate-limit": "^6.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.0",
|
||||
"jest": "^29.0.0",
|
||||
"supertest": "^6.3.0"
|
||||
"express": "^4.18.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,11 @@ app.use(limiter);
|
||||
|
||||
// 基础路由
|
||||
app.get('/', (req, res) => {
|
||||
res.json({
|
||||
message: '欢迎使用锡林郭勒盟地区养殖产业平台API服务',
|
||||
version: '1.0.0'
|
||||
});
|
||||
res.json({
|
||||
message: '欢迎使用锡林郭勒盟地区智慧养殖产业平台API服务',
|
||||
version: '1.0.0',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/health', (req, res) => {
|
||||
|
||||
@@ -914,9 +914,87 @@ CREATE TABLE government_reports (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='政府监管报告表';
|
||||
```
|
||||
|
||||
## 官网相关表设计
|
||||
|
||||
### 1. 官网首页配置表 (website_homepage)
|
||||
存储官网首页的配置信息。
|
||||
|
||||
```sql
|
||||
CREATE TABLE website_homepage (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '配置ID',
|
||||
section_name VARCHAR(50) NOT NULL COMMENT '板块名称',
|
||||
content_type ENUM('banner', 'video', 'text', 'link') NOT NULL COMMENT '内容类型',
|
||||
title VARCHAR(100) NOT NULL COMMENT '标题',
|
||||
subtitle VARCHAR(200) COMMENT '副标题',
|
||||
content TEXT COMMENT '内容',
|
||||
media_url VARCHAR(255) COMMENT '媒体文件URL(图片或视频)',
|
||||
link_url VARCHAR(255) COMMENT '链接地址',
|
||||
sort_order INT DEFAULT 0 COMMENT '排序顺序',
|
||||
is_active BOOLEAN DEFAULT TRUE COMMENT '是否启用',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
|
||||
INDEX idx_section (section_name),
|
||||
INDEX idx_active (is_active),
|
||||
INDEX idx_sort (sort_order)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='官网首页配置表';
|
||||
```
|
||||
|
||||
### 2. 新闻资讯表 (news_articles)
|
||||
存储新闻资讯信息。
|
||||
|
||||
```sql
|
||||
CREATE TABLE 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 '更新时间',
|
||||
|
||||
FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL,
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_category (category),
|
||||
INDEX idx_publish_date (publish_date)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='新闻资讯表';
|
||||
```
|
||||
|
||||
### 3. 政策公告表 (policy_announcements)
|
||||
存储政策公告信息。
|
||||
|
||||
```sql
|
||||
CREATE TABLE 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='政策公告表';
|
||||
```
|
||||
|
||||
## 数据库关系图
|
||||
|
||||
```mermaid
|
||||
```
|
||||
erDiagram
|
||||
users ||--o{ user_roles : has
|
||||
roles ||--o{ user_roles : includes
|
||||
@@ -946,6 +1024,8 @@ erDiagram
|
||||
users ||--o{ claims : reviews
|
||||
users ||--o{ government_reports : submits
|
||||
users ||--o{ government_reports : approves
|
||||
users ||--o{ news : authors
|
||||
users ||--o{ messages : processes
|
||||
|
||||
users {
|
||||
BIGINT id PK
|
||||
@@ -1271,4 +1351,79 @@ erDiagram
|
||||
TIMESTAMP created_at
|
||||
TIMESTAMP updated_at
|
||||
}
|
||||
|
||||
news {
|
||||
BIGINT id PK
|
||||
VARCHAR title
|
||||
TEXT summary
|
||||
LONGTEXT content
|
||||
ENUM category
|
||||
VARCHAR image_url
|
||||
BIGINT author_id FK
|
||||
INT views
|
||||
ENUM status
|
||||
TIMESTAMP published_at
|
||||
TIMESTAMP created_at
|
||||
TIMESTAMP updated_at
|
||||
}
|
||||
|
||||
messages {
|
||||
BIGINT id PK
|
||||
VARCHAR name
|
||||
VARCHAR email
|
||||
VARCHAR phone
|
||||
TEXT content
|
||||
ENUM status
|
||||
BIGINT processed_by FK
|
||||
TIMESTAMP processed_at
|
||||
TIMESTAMP created_at
|
||||
TIMESTAMP updated_at
|
||||
}
|
||||
```
|
||||
|
||||
### 21. 新闻资讯表 (news)
|
||||
存储官网新闻资讯信息。
|
||||
|
||||
```sql
|
||||
CREATE TABLE news (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '新闻ID',
|
||||
title VARCHAR(200) NOT NULL COMMENT '标题',
|
||||
summary TEXT COMMENT '摘要',
|
||||
content LONGTEXT NOT NULL COMMENT '内容',
|
||||
category ENUM('policy', 'market', 'technology', 'general') NOT NULL COMMENT '分类: 政策解读/市场动态/技术前沿/综合',
|
||||
image_url VARCHAR(255) COMMENT '封面图片URL',
|
||||
author_id BIGINT UNSIGNED COMMENT '作者ID',
|
||||
views INT DEFAULT 0 COMMENT '浏览量',
|
||||
status ENUM('draft', 'published', 'archived') DEFAULT 'draft' COMMENT '状态: 草稿/已发布/已归档',
|
||||
published_at TIMESTAMP NULL COMMENT '发布时间',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
|
||||
FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL,
|
||||
INDEX idx_category (category),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_published_at (published_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='新闻资讯表';
|
||||
```
|
||||
|
||||
### 22. 用户留言表 (messages)
|
||||
存储用户在官网提交的留言信息。
|
||||
|
||||
```sql
|
||||
CREATE TABLE messages (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '留言ID',
|
||||
name VARCHAR(50) NOT NULL COMMENT '姓名',
|
||||
email VARCHAR(100) NOT NULL COMMENT '邮箱',
|
||||
phone VARCHAR(20) COMMENT '电话',
|
||||
content TEXT NOT NULL COMMENT '留言内容',
|
||||
status ENUM('pending', 'processed', 'archived') DEFAULT 'pending' COMMENT '状态: 待处理/已处理/已归档',
|
||||
processed_by BIGINT UNSIGNED COMMENT '处理人ID',
|
||||
processed_at TIMESTAMP NULL COMMENT '处理时间',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
|
||||
FOREIGN KEY (processed_by) REFERENCES users(id) ON DELETE SET NULL,
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户留言表';
|
||||
```
|
||||
@@ -1,8 +1,8 @@
|
||||
# 部署指南
|
||||
# 部署文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述了锡林郭勒盟安格斯牛数字化管理平台的部署流程和配置要求。
|
||||
本文档描述了锡林郭勒盟智慧养殖数字化管理平台的部署流程和配置要求。
|
||||
|
||||
## 部署架构
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 概述
|
||||
|
||||
本文档目录包含了锡林郭勒盟安格斯牛数字化管理平台的所有相关文档。
|
||||
本文档目录包含了锡林郭勒盟智慧养殖数字化管理平台的所有相关文档。
|
||||
|
||||
## 文档列表
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
- [商城管理系统需求文档](./requirements/MALL_MANAGEMENT_REQUIREMENTS.md) - 商城管理系统的功能性需求
|
||||
- [数据中台系统需求文档](./requirements/DATA_PLATFORM_REQUIREMENTS.md) - 数据中台系统的功能性需求
|
||||
- [AI能力系统需求文档](./requirements/AI_CAPABILITIES_REQUIREMENTS.md) - AI能力系统的功能性需求
|
||||
- [官网需求文档](./requirements/WEBSITE_REQUIREMENTS.md) - 官网的功能性需求
|
||||
- [大屏可视化系统需求文档](./requirements/dashboard_requirements.md) - 大屏可视化系统的功能性需求
|
||||
- [系统集成需求文档](./requirements/SYSTEM_INTEGRATION_REQUIREMENTS.md) - 系统集成需求
|
||||
- 微信小程序需求文档:
|
||||
@@ -37,6 +38,8 @@
|
||||
- [市场交易API](./design/api/trade.md) - 市场交易相关接口
|
||||
- [数据中台API](./design/api/data-platform.md) - 数据中台相关接口
|
||||
- [用户中心API](./design/api/user-center.md) - 用户中心相关接口
|
||||
- [官网API](./design/api/website.md) - 官网相关接口
|
||||
- [大屏可视化API](./design/api/dashboard.md) - 大屏可视化相关接口
|
||||
- 微信小程序API:
|
||||
- [养殖户小程序API](./design/api/miniprograms/farming-app.md) - 养殖户小程序相关接口
|
||||
- [政务人员小程序API](./design/api/miniprograms/gov-app.md) - 政务人员小程序相关接口
|
||||
@@ -55,6 +58,7 @@
|
||||
- [政府监管平台开发计划](./development_plans/government_platform_development_plan.md) - 政府监管平台的详细开发计划
|
||||
- [活牛交易系统开发计划](./development_plans/cattle_trading_development_plan.md) - 活牛交易系统的详细开发计划
|
||||
- [商城管理系统开发计划](./development_plans/mall_management_development_plan.md) - 商城管理系统的详细开发计划
|
||||
- [官网开发计划](./development_plans/website_development_plan.md) - 官网的详细开发计划
|
||||
- [后端API服务开发计划](./development_plans/backend_api_development_plan.md) - 后端API服务的详细开发计划
|
||||
- [大屏可视化系统开发计划](./development_plans/dashboard_development_plan.md) - 大屏可视化系统的详细开发计划
|
||||
|
||||
|
||||
@@ -54,41 +54,20 @@
|
||||
|
||||
## 3. 系统架构图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 用户端接入层 │
|
||||
├─────────────────┬─────────────────────────────────────────────────────────────────────────┤
|
||||
│ 官网首页 │ 专业管理系统 │
|
||||
│ (HTML5展示) ├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │ - 养殖管理系统 (Vue.js 3 + Ant) │
|
||||
│ │ - 银行监管系统 (Vue.js 3 + Ant) │
|
||||
│ │ - 保险监管系统 (Vue.js 3 + Ant) │
|
||||
│ │ - 政府监管平台 (Vue.js 3 + Ant) │
|
||||
│ │ - 活牛交易系统 (Vue.js 3 + Ant) │
|
||||
│ │ - 商城管理系统 (Vue.js 3 + Ant) │
|
||||
│ │ - 大屏可视化系统 (Vue.js 3 + ECharts) │
|
||||
├─────────────────┼─────────────────────────────────────────────────────────────────────────┤
|
||||
│ 微信小程序矩阵 │
|
||||
│ - 牛肉商城小程序 │
|
||||
│ - 养殖管理小程序 │
|
||||
│ - 银行监管小程序 │
|
||||
│ - 保险监管小程序 │
|
||||
│ - 活牛交易小程序 │
|
||||
│ - 政府监管小程序 │
|
||||
│ - 数据中台小程序 │
|
||||
│ - AI能力小程序 │
|
||||
├─────────────────┴─────────────────────────────────────────────────────────────────────────┤
|
||||
│ API服务层 (Node.js) │
|
||||
├─────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ 数据处理与存储层 │
|
||||
├─────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ 关系数据库(MySQL) │ 缓存系统(Redis) │ 消息队列(RabbitMQ) │ 文件存储(腾讯云) │
|
||||
├─────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ 数据分析与展示层 │
|
||||
├─────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ 大数据分析引擎 │ 智能预警系统 │ 可视化平台 │ 实时数据推送 │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
为了更直观地展示系统架构,我们提供了一个可视化的系统架构图:
|
||||
|
||||

|
||||
|
||||
图中展示了以下主要层次和组件:
|
||||
|
||||
1. **用户接入层**:包括官网首页、专业管理系统和微信小程序矩阵
|
||||
2. **API服务层**:基于Node.js的各个业务模块API服务
|
||||
3. **数据处理与存储层**:包括关系数据库、缓存系统、消息队列、文件存储和日志存储
|
||||
4. **数据分析与展示层**:包含大数据分析引擎、智能预警系统、可视化平台和实时数据推送
|
||||
5. **监控与日志层**:应用性能监控、服务器资源监控、日志收集与分析以及异常告警
|
||||
6. **部署架构层**:容器化部署、负载均衡、自动化部署和容灾备份
|
||||
|
||||
此外,图中还展示了各组件之间的连接关系和数据流向。
|
||||
|
||||
## 4. 前端系统架构
|
||||
|
||||
@@ -97,6 +76,8 @@
|
||||
- 响应式设计,适配多种设备
|
||||
- 突出锡林郭勒盟地域元素和蒙古族文化特色
|
||||
- 绿色草原主题风格
|
||||
- 集成Chart.js实现数据可视化展示
|
||||
- 通过官网API获取新闻资讯和统计数据
|
||||
|
||||
### 4.2 专业管理系统
|
||||
|
||||
@@ -265,6 +246,64 @@
|
||||
- JWT身份认证机制
|
||||
- 微服务架构设计(按业务模块划分)
|
||||
|
||||
#### 5.1.1 养殖管理API (`/api/v1/farming`)
|
||||
- 牛只档案管理
|
||||
- 饲喂记录
|
||||
- 防疫管理
|
||||
- 繁殖管理
|
||||
|
||||
#### 5.1.2 金融服务API (`/api/v1/finance`)
|
||||
- 贷款申请
|
||||
- 保险购买
|
||||
- 理赔处理
|
||||
- 贷款审批
|
||||
|
||||
#### 5.1.3 政府监管API (`/api/v1/gov`)
|
||||
- 防疫任务下发
|
||||
- 补贴发放
|
||||
- 检疫监管
|
||||
- 任务状态跟踪
|
||||
|
||||
#### 5.1.4 交易管理API (`/api/v1/trades`)
|
||||
- 商品发布/下架
|
||||
- 订单创建/支付
|
||||
- 物流跟踪
|
||||
- 订单状态查询
|
||||
|
||||
#### 5.1.5 商城管理API (`/api/v1/mall`)
|
||||
- 商品管理
|
||||
- 订单处理
|
||||
- 库存管理
|
||||
- 用户评价
|
||||
|
||||
#### 5.1.6 数据中台API (`/api/v1/data`)
|
||||
- 数据血缘追踪
|
||||
- 接口调用分析
|
||||
- 数据质量监控
|
||||
|
||||
#### 5.1.7 AI能力API (`/api/v1/ai`)
|
||||
- 牛只体况评估
|
||||
- 饲料配方推荐
|
||||
- 智能诊断辅助
|
||||
|
||||
#### 5.1.8 用户中心API (`/api/v1/users`)
|
||||
- 用户注册/登录/注销
|
||||
- 个人信息管理
|
||||
- 权限控制
|
||||
|
||||
#### 5.1.9 官网API (`/api/v1/website`)
|
||||
- 新闻资讯管理
|
||||
- 平台数据展示
|
||||
- 用户留言处理
|
||||
- 平台信息配置
|
||||
|
||||
#### 5.1.10 大屏可视化API (`/api/v1/dashboard`)
|
||||
- 实时数据展示(支持 WebSocket)
|
||||
- 历史数据查询(支持分页和排序)
|
||||
- 数据可视化配置(增删改查)
|
||||
- 告警信息推送(订阅/取消订阅)
|
||||
- 数据导出(CSV/JSON 格式)
|
||||
|
||||
### 5.2 数据存储层
|
||||
- 主数据库:MySQL关系型数据库
|
||||
- 缓存系统:Redis(用于会话缓存、数据缓存)
|
||||
@@ -317,6 +356,7 @@
|
||||
│ │ ├── mall/ # 商城管理API
|
||||
│ │ ├── data-platform/ # 数据中台API
|
||||
│ │ ├── ai/ # AI能力API
|
||||
│ │ ├── website/ # 官网API
|
||||
│ │ └── user-center/ # 用户中心API
|
||||
│ ├── database/ # 数据库脚本
|
||||
│ ├── utils/ # 工具函数
|
||||
@@ -331,6 +371,7 @@
|
||||
- 所有用户信息统一在政府监管平台进行管理
|
||||
- 各子系统仅负责登录验证和部分权限校验
|
||||
- 实现统一的RBAC权限模型
|
||||
- 通过用户中心API进行统一认证和授权
|
||||
|
||||
### 7.2 权限体系
|
||||
- 基于角色的访问控制(RBAC)
|
||||
@@ -353,7 +394,7 @@
|
||||
## 8. 大屏可视化系统架构
|
||||
|
||||
### 8.1 系统概述
|
||||
大屏可视化系统是本项目的重要组成部分,主要用于展示锡林郭勒盟安格斯牛养殖产业的整体数据、实时监控信息和分析结果。通过直观的图表和数据可视化方式,为管理者提供全面的产业洞察。
|
||||
大屏可视化系统是本项目的重要组成部分,主要用于展示锡林郭勒盟智慧养殖产业的整体数据、实时监控信息和分析结果。通过直观的图表和数据可视化方式,为管理者提供全面的产业洞察。
|
||||
|
||||
### 8.2 技术实现
|
||||
- **前端框架**: Vue.js 3 + ECharts + 自定义可视化组件
|
||||
@@ -375,8 +416,8 @@
|
||||
### 8.4 设计特色
|
||||
- 融入锡林郭勒盟草原绿色主题
|
||||
- 采用蒙古族文化元素的UI设计
|
||||
- 突出安格斯牛品牌形象
|
||||
- 支持多维度数据钻取和交互
|
||||
- 通过大屏可视化API获取实时和历史数据
|
||||
|
||||
## 9. 系统集成架构
|
||||
|
||||
@@ -401,6 +442,9 @@
|
||||
- CDN加速静态资源
|
||||
- 浏览器缓存策略
|
||||
- 虚拟滚动处理大数据量展示
|
||||
- CDN加速静态资源
|
||||
- 浏览器缓存策略
|
||||
- 虚拟滚动处理大数据量展示
|
||||
|
||||
### 10.2 后端优化
|
||||
- 数据库索引优化
|
||||
@@ -409,17 +453,47 @@
|
||||
- 负载均衡部署
|
||||
- 异步任务处理(RabbitMQ)
|
||||
|
||||
## 11. 部署架构
|
||||
## 11. 系统监控与日志
|
||||
|
||||
### 11.1 开发环境
|
||||
### 11.1 监控系统
|
||||
- 应用性能监控(APM)
|
||||
- 服务器资源监控(CPU、内存、磁盘等)
|
||||
- 数据库性能监控
|
||||
- 网络监控
|
||||
- 业务指标监控
|
||||
|
||||
### 11.2 日志系统
|
||||
- 统一日志格式
|
||||
- 日志分级管理(DEBUG、INFO、WARN、ERROR)
|
||||
- 日志收集与分析(ELK Stack)
|
||||
- 日志存储策略
|
||||
- 异常日志告警
|
||||
|
||||
## 12. 部署架构
|
||||
|
||||
### 12.1 开发环境
|
||||
- 本地开发服务器
|
||||
- 热重载功能
|
||||
- 代理配置解决跨域问题
|
||||
|
||||
### 11.2 生产环境
|
||||
### 12.2 生产环境
|
||||
- Nginx反向代理服务器
|
||||
- 负载均衡配置
|
||||
- SSL证书配置
|
||||
- 日志收集和监控系统
|
||||
- 容器化部署(Docker)
|
||||
- 自动化部署(CI/CD)
|
||||
- 自动化部署(CI/CD)
|
||||
|
||||
## 13. 容灾与备份策略
|
||||
|
||||
### 13.1 数据备份
|
||||
- 定时全量备份
|
||||
- 增量备份机制
|
||||
- 备份数据异地存储
|
||||
- 备份恢复演练
|
||||
|
||||
### 13.2 系统容灾
|
||||
- 多节点部署
|
||||
- 故障自动切换
|
||||
- 数据同步机制
|
||||
- 灾难恢复预案
|
||||
234
docs/design/api/dashboard.md
Normal file
234
docs/design/api/dashboard.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# 大屏可视化系统 API 文档
|
||||
|
||||
## 1. 概述
|
||||
|
||||
大屏可视化系统是锡林郭勒盟智慧养殖产业平台的重要组成部分,主要用于展示产业整体数据、实时监控信息和分析结果。该系统通过直观的图表和数据可视化方式,为管理者提供全面的产业洞察。
|
||||
|
||||
## 2. 技术架构
|
||||
|
||||
- **前端框架**: Vue.js 3 + ECharts + 自定义可视化组件
|
||||
- **可视化库**: Apache ECharts
|
||||
- **响应式设计**: 支持多种大屏比例(16:9, 4:3等)
|
||||
- **实时数据**: WebSocket实时数据推送
|
||||
- **状态管理**: Pinia
|
||||
|
||||
## 3. 功能模块
|
||||
|
||||
### 3.1 产业概览
|
||||
展示整体产业规模、产值、增长率等关键指标
|
||||
|
||||
### 3.2 养殖监控
|
||||
实时展示各牧场的养殖情况、环境数据
|
||||
|
||||
### 3.3 金融服务
|
||||
展示贷款、保险等金融服务数据
|
||||
|
||||
### 3.4 交易统计
|
||||
牛只交易量、价格趋势、区域分布等数据
|
||||
|
||||
### 3.5 运输跟踪
|
||||
牛只运输实时状态和路径展示
|
||||
|
||||
### 3.6 风险预警
|
||||
风险事件展示和预警信息推送
|
||||
|
||||
### 3.7 生态指标
|
||||
环保数据、可持续发展指标展示
|
||||
|
||||
### 3.8 政府监管
|
||||
展示政府监管相关数据和政策执行效果
|
||||
|
||||
## 4. API 接口
|
||||
|
||||
### 4.1 实时数据接口
|
||||
|
||||
#### 获取实时数据
|
||||
```
|
||||
GET /api/v1/dashboard/realtime
|
||||
```
|
||||
|
||||
**请求参数**:
|
||||
- 无
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"timestamp": "2023-08-19T10:30:00Z",
|
||||
"total_cattle": 128456,
|
||||
"total_farms": 1245,
|
||||
"annual_output_value": 2860000000,
|
||||
"total_transaction": 1520000000
|
||||
}
|
||||
```
|
||||
|
||||
#### WebSocket 实时推送
|
||||
```
|
||||
WebSocket /api/v1/dashboard/ws
|
||||
```
|
||||
|
||||
**推送数据格式**:
|
||||
```json
|
||||
{
|
||||
"type": "realtime_update",
|
||||
"data": {
|
||||
"timestamp": "2023-08-19T10:30:00Z",
|
||||
"total_cattle": 128456,
|
||||
"total_farms": 1245,
|
||||
"annual_output_value": 2860000000,
|
||||
"total_transaction": 1520000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 历史数据接口
|
||||
|
||||
#### 获取历史数据
|
||||
```
|
||||
GET /api/v1/dashboard/history
|
||||
```
|
||||
|
||||
**请求参数**:
|
||||
- `start_date` (string, optional): 开始日期,格式 YYYY-MM-DD
|
||||
- `end_date` (string, optional): 结束日期,格式 YYYY-MM-DD
|
||||
- `type` (string, required): 数据类型 (breeding, transaction, transport, etc.)
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"date": "2023-01",
|
||||
"value": 8200
|
||||
},
|
||||
{
|
||||
"date": "2023-02",
|
||||
"value": 9100
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 配置接口
|
||||
|
||||
#### 获取可视化配置
|
||||
```
|
||||
GET /api/v1/dashboard/config
|
||||
```
|
||||
|
||||
**请求参数**:
|
||||
- 无
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"theme": "dark",
|
||||
"refresh_interval": 30,
|
||||
"charts": [
|
||||
{
|
||||
"id": "breeding_trend",
|
||||
"type": "line",
|
||||
"title": "养殖趋势"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 更新可视化配置
|
||||
```
|
||||
PUT /api/v1/dashboard/config
|
||||
```
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"theme": "dark",
|
||||
"refresh_interval": 30
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"message": "配置更新成功"
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 数据格式
|
||||
|
||||
### 5.1 产业概览数据
|
||||
```json
|
||||
{
|
||||
"total_cattle": 128456,
|
||||
"total_farms": 1245,
|
||||
"annual_output_value": 2860000000,
|
||||
"total_transaction": 1520000000,
|
||||
"growth_rate": 5.2
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 养殖监控数据
|
||||
```json
|
||||
{
|
||||
"farm_id": "FARM001",
|
||||
"temperature": 22.5,
|
||||
"humidity": 65,
|
||||
"cattle_count": 245,
|
||||
"feed_consumption": 1200
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 金融服务数据
|
||||
```json
|
||||
{
|
||||
"loan_amount": 8600000,
|
||||
"insurance_policies": 12450,
|
||||
"claim_amount": 245000,
|
||||
"approval_rate": 92.5
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 交易统计数据
|
||||
```json
|
||||
{
|
||||
"daily_transactions": 125,
|
||||
"average_price": 18500,
|
||||
"regional_distribution": [
|
||||
{
|
||||
"region": "东乌旗",
|
||||
"count": 1200
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 错误处理
|
||||
|
||||
### 6.1 错误响应格式
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "DASHBOARD_001",
|
||||
"message": "获取数据失败",
|
||||
"details": "数据库连接异常"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 常见错误码
|
||||
- `DASHBOARD_001`: 数据获取失败
|
||||
- `DASHBOARD_002`: 参数错误
|
||||
- `DASHBOARD_003`: 权限不足
|
||||
- `DASHBOARD_004`: 系统内部错误
|
||||
|
||||
## 7. 权限说明
|
||||
|
||||
大屏可视化系统接口需要以下权限:
|
||||
- `dashboard:view`: 查看大屏数据权限
|
||||
- `dashboard:config`: 配置大屏权限
|
||||
|
||||
## 8. 注意事项
|
||||
|
||||
1. 大屏可视化系统主要面向内部管理使用,需要相应权限才能访问
|
||||
2. 实时数据推送通过WebSocket实现,需要保持长连接
|
||||
3. 历史数据支持分页查询,避免一次性加载大量数据
|
||||
4. 所有接口均采用HTTPS加密传输
|
||||
176
docs/design/api/website.md
Normal file
176
docs/design/api/website.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# 官网 API 文档 (v1.1.0)
|
||||
|
||||
## 1. 接口概述
|
||||
|
||||
### 1.1 功能范围
|
||||
- 新闻资讯管理
|
||||
- 平台数据展示
|
||||
- 用户留言处理
|
||||
- 平台信息配置
|
||||
|
||||
### 1.2 基础路径
|
||||
`/api/v1/website`
|
||||
|
||||
### 1.3 权限控制
|
||||
- 公开接口(无需认证):新闻列表、数据展示等
|
||||
- 管理接口(需要认证):新闻管理、留言处理等
|
||||
|
||||
### 1.4 全局错误码
|
||||
| 状态码 | 说明 |
|
||||
|--------|--------------------|
|
||||
| 400 | 请求参数无效 |
|
||||
| 401 | 未授权 |
|
||||
| 403 | 权限不足 |
|
||||
| 404 | 资源不存在 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
## 2. 接口明细
|
||||
|
||||
### 2.1 获取新闻列表
|
||||
```
|
||||
GET /news
|
||||
```
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|-------------|--------|------|--------------------|
|
||||
| page | number | 否 | 页码(默认1) |
|
||||
| limit | number | 否 | 每页数量(默认10) |
|
||||
| category | string | 否 | 分类筛选 |
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "锡林郭勒盟出台畜牧业数字化发展三年规划",
|
||||
"summary": "规划提出到2027年实现全盟畜牧业数字化覆盖率90%以上",
|
||||
"category": "政策解读",
|
||||
"publish_time": "2025-08-15T10:00:00Z",
|
||||
"image_url": "/images/news-1.jpg"
|
||||
}
|
||||
],
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"limit": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 获取新闻详情
|
||||
```
|
||||
GET /news/{id}
|
||||
```
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|-------------|--------|------|--------------------|
|
||||
| id | number | 是 | 新闻ID |
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"title": "锡林郭勒盟出台畜牧业数字化发展三年规划",
|
||||
"content": "详细新闻内容...",
|
||||
"category": "政策解读",
|
||||
"publish_time": "2025-08-15T10:00:00Z",
|
||||
"author": "管理员",
|
||||
"views": 1250
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 获取统计数据
|
||||
```
|
||||
GET /statistics
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"livestock_count": {
|
||||
"cattle": 1200000,
|
||||
"sheep": 850000,
|
||||
"horse": 320000,
|
||||
"camel": 80000
|
||||
},
|
||||
"forage_data": {
|
||||
"production": [12, 19, 15, 22, 28, 35],
|
||||
"price": [1800, 1750, 1850, 1900, 1950, 2000]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 提交留言
|
||||
```
|
||||
POST /messages
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|-------------|--------|------|--------------------|
|
||||
| name | string | 是 | 姓名 |
|
||||
| email | string | 是 | 邮箱 |
|
||||
| phone | string | 否 | 电话 |
|
||||
| content | string | 是 | 留言内容 |
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"message": "留言提交成功,我们会尽快回复您"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5 管理员登录
|
||||
```
|
||||
POST /auth/login
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|-------------|--------|------|--------------------|
|
||||
| username | string | 是 | 用户名 |
|
||||
| password | string | 是 | 密码 |
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"expires_in": 3600
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.6 创建新闻(管理员)
|
||||
```
|
||||
POST /news
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|-------------|--------|------|--------------------|
|
||||
| title | string | 是 | 标题 |
|
||||
| content | string | 是 | 内容 |
|
||||
| summary | string | 否 | 摘要 |
|
||||
| category | string | 是 | 分类 |
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"id": 101,
|
||||
"title": "新发布的新闻",
|
||||
"created_at": "2025-08-19T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
188
docs/design/system_architecture.svg
Normal file
188
docs/design/system_architecture.svg
Normal file
@@ -0,0 +1,188 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="900" viewBox="0 0 1200 900">
|
||||
<style>
|
||||
.layer { fill: #e3f2fd; stroke: #1976d2; stroke-width: 1; }
|
||||
.subsystem { fill: #f3e5f5; stroke: #7b1fa2; stroke-width: 1; }
|
||||
.service { fill: #e8f5e9; stroke: #388e3c; stroke-width: 1; }
|
||||
.component { fill: #fff3e0; stroke: #f57c00; stroke-width: 1; }
|
||||
.database { fill: #ffebee; stroke: #d32f2f; stroke-width: 1; }
|
||||
.title { font: bold 16px sans-serif; }
|
||||
.label { font: 12px sans-serif; }
|
||||
.arrow { stroke: #666; stroke-width: 2; marker-end: url(#arrowhead); }
|
||||
</style>
|
||||
|
||||
<!-- 定义箭头 -->
|
||||
<defs>
|
||||
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill="#666" />
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- 用户接入层 -->
|
||||
<rect x="50" y="20" width="1100" height="120" class="layer" />
|
||||
<text x="600" y="40" text-anchor="middle" class="title">用户接入层</text>
|
||||
|
||||
<!-- 官网首页 -->
|
||||
<rect x="100" y="60" width="150" height="60" class="subsystem" />
|
||||
<text x="175" y="90" text-anchor="middle" class="label">官网首页</text>
|
||||
|
||||
<!-- 专业管理系统 -->
|
||||
<rect x="300" y="60" width="500" height="60" class="subsystem" />
|
||||
<text x="550" y="90" text-anchor="middle" class="label">专业管理系统</text>
|
||||
|
||||
<!-- 微信小程序矩阵 -->
|
||||
<rect x="850" y="60" width="200" height="60" class="subsystem" />
|
||||
<text x="950" y="90" text-anchor="middle" class="label">微信小程序矩阵</text>
|
||||
|
||||
<!-- API服务层 -->
|
||||
<rect x="50" y="180" width="1100" height="120" class="layer" />
|
||||
<text x="600" y="200" text-anchor="middle" class="title">API服务层 (Node.js)</text>
|
||||
|
||||
<!-- 各个API服务 -->
|
||||
<rect x="100" y="220" width="100" height="60" class="service" />
|
||||
<text x="150" y="245" text-anchor="middle" class="label">养殖管理</text>
|
||||
<text x="150" y="260" text-anchor="middle" class="label">API</text>
|
||||
|
||||
<rect x="220" y="220" width="100" height="60" class="service" />
|
||||
<text x="270" y="245" text-anchor="middle" class="label">金融服务</text>
|
||||
<text x="270" y="260" text-anchor="middle" class="label">API</text>
|
||||
|
||||
<rect x="340" y="220" width="100" height="60" class="service" />
|
||||
<text x="390" y="245" text-anchor="middle" class="label">政府监管</text>
|
||||
<text x="390" y="260" text-anchor="middle" class="label">API</text>
|
||||
|
||||
<rect x="460" y="220" width="100" height="60" class="service" />
|
||||
<text x="510" y="245" text-anchor="middle" class="label">交易管理</text>
|
||||
<text x="510" y="260" text-anchor="middle" class="label">API</text>
|
||||
|
||||
<rect x="580" y="220" width="100" height="60" class="service" />
|
||||
<text x="630" y="245" text-anchor="middle" class="label">商城管理</text>
|
||||
<text x="630" y="260" text-anchor="middle" class="label">API</text>
|
||||
|
||||
<rect x="700" y="220" width="100" height="60" class="service" />
|
||||
<text x="750" y="245" text-anchor="middle" class="label">数据中台</text>
|
||||
<text x="750" y="260" text-anchor="middle" class="label">API</text>
|
||||
|
||||
<rect x="820" y="220" width="100" height="60" class="service" />
|
||||
<text x="870" y="245" text-anchor="middle" class="label">AI能力</text>
|
||||
<text x="870" y="260" text-anchor="middle" class="label">API</text>
|
||||
|
||||
<rect x="940" y="220" width="100" height="60" class="service" />
|
||||
<text x="990" y="245" text-anchor="middle" class="label">用户中心</text>
|
||||
<text x="990" y="260" text-anchor="middle" class="label">API</text>
|
||||
|
||||
<!-- 数据处理与存储层 -->
|
||||
<rect x="50" y="340" width="1100" height="120" class="layer" />
|
||||
<text x="600" y="360" text-anchor="middle" class="title">数据处理与存储层</text>
|
||||
|
||||
<!-- 数据库 -->
|
||||
<rect x="100" y="380" width="150" height="60" class="database" />
|
||||
<text x="175" y="410" text-anchor="middle" class="label">关系数据库 (MySQL)</text>
|
||||
|
||||
<!-- 缓存系统 -->
|
||||
<rect x="300" y="380" width="150" height="60" class="component" />
|
||||
<text x="375" y="410" text-anchor="middle" class="label">缓存系统 (Redis)</text>
|
||||
|
||||
<!-- 消息队列 -->
|
||||
<rect x="500" y="380" width="150" height="60" class="component" />
|
||||
<text x="575" y="410" text-anchor="middle" class="label">消息队列 (RabbitMQ)</text>
|
||||
|
||||
<!-- 文件存储 -->
|
||||
<rect x="700" y="380" width="150" height="60" class="component" />
|
||||
<text x="775" y="410" text-anchor="middle" class="label">文件存储 (腾讯云)</text>
|
||||
|
||||
<!-- 日志存储 -->
|
||||
<rect x="900" y="380" width="150" height="60" class="component" />
|
||||
<text x="975" y="410" text-anchor="middle" class="label">日志存储 (ES)</text>
|
||||
|
||||
<!-- 数据分析与展示层 -->
|
||||
<rect x="50" y="500" width="1100" height="120" class="layer" />
|
||||
<text x="600" y="520" text-anchor="middle" class="title">数据分析与展示层</text>
|
||||
|
||||
<!-- 大数据分析引擎 -->
|
||||
<rect x="100" y="540" width="150" height="60" class="component" />
|
||||
<text x="175" y="570" text-anchor="middle" class="label">大数据分析引擎</text>
|
||||
|
||||
<!-- 智能预警系统 -->
|
||||
<rect x="300" y="540" width="150" height="60" class="component" />
|
||||
<text x="375" y="570" text-anchor="middle" class="label">智能预警系统</text>
|
||||
|
||||
<!-- 可视化平台 -->
|
||||
<rect x="500" y="540" width="150" height="60" class="component" />
|
||||
<text x="575" y="570" text-anchor="middle" class="label">可视化平台</text>
|
||||
|
||||
<!-- 实时数据推送 -->
|
||||
<rect x="700" y="540" width="150" height="60" class="component" />
|
||||
<text x="775" y="570" text-anchor="middle" class="label">实时数据推送</text>
|
||||
|
||||
<!-- 大屏可视化系统 -->
|
||||
<rect x="900" y="540" width="150" height="60" class="subsystem" />
|
||||
<text x="975" y="570" text-anchor="middle" class="label">大屏可视化系统</text>
|
||||
|
||||
<!-- 监控与日志层 -->
|
||||
<rect x="50" y="660" width="1100" height="120" class="layer" />
|
||||
<text x="600" y="680" text-anchor="middle" class="title">监控与日志层</text>
|
||||
|
||||
<!-- 应用性能监控 -->
|
||||
<rect x="100" y="700" width="150" height="60" class="component" />
|
||||
<text x="175" y="730" text-anchor="middle" class="label">应用性能监控</text>
|
||||
|
||||
<!-- 服务器资源监控 -->
|
||||
<rect x="300" y="700" width="150" height="60" class="component" />
|
||||
<text x="375" y="730" text-anchor="middle" class="label">服务器资源监控</text>
|
||||
|
||||
<!-- 日志收集与分析 -->
|
||||
<rect x="500" y="700" width="150" height="60" class="component" />
|
||||
<text x="575" y="730" text-anchor="middle" class="label">日志收集与分析</text>
|
||||
|
||||
<!-- 异常告警 -->
|
||||
<rect x="700" y="700" width="150" height="60" class="component" />
|
||||
<text x="775" y="730" text-anchor="middle" class="label">异常告警</text>
|
||||
|
||||
<!-- 部署架构层 -->
|
||||
<rect x="50" y="780" width="1100" height="100" class="layer" />
|
||||
<text x="600" y="800" text-anchor="middle" class="title">部署架构层</text>
|
||||
|
||||
<!-- 容器化部署 -->
|
||||
<rect x="100" y="820" width="150" height="60" class="component" />
|
||||
<text x="175" y="850" text-anchor="middle" class="label">容器化部署</text>
|
||||
|
||||
<!-- 负载均衡 -->
|
||||
<rect x="300" y="820" width="150" height="60" class="component" />
|
||||
<text x="375" y="850" text-anchor="middle" class="label">负载均衡</text>
|
||||
|
||||
<!-- CI/CD -->
|
||||
<rect x="500" y="820" width="150" height="60" class="component" />
|
||||
<text x="575" y="850" text-anchor="middle" class="label">自动化部署 (CI/CD)</text>
|
||||
|
||||
<!-- 容灾备份 -->
|
||||
<rect x="700" y="820" width="150" height="60" class="component" />
|
||||
<text x="775" y="850" text-anchor="middle" class="label">容灾备份</text>
|
||||
|
||||
<!-- 连接线 -->
|
||||
<!-- 用户接入层到API服务层 -->
|
||||
<line x1="600" y1="140" x2="600" y2="180" class="arrow" />
|
||||
|
||||
<!-- API服务层到数据处理与存储层 -->
|
||||
<line x1="600" y1="300" x2="600" y2="340" class="arrow" />
|
||||
|
||||
<!-- 数据处理与存储层到数据分析与展示层 -->
|
||||
<line x1="600" y1="460" x2="600" y2="500" class="arrow" />
|
||||
|
||||
<!-- 数据分析与展示层到监控与日志层 -->
|
||||
<line x1="600" y1="620" x2="600" y2="660" class="arrow" />
|
||||
|
||||
<!-- 监控与日志层到部署架构层 -->
|
||||
<line x1="600" y1="780" x2="600" y2="820" class="arrow" />
|
||||
|
||||
<!-- 数据库到缓存系统 -->
|
||||
<line x1="250" y1="410" x2="300" y2="410" class="arrow" />
|
||||
|
||||
<!-- 缓存系统到消息队列 -->
|
||||
<line x1="450" y1="410" x2="500" y2="410" class="arrow" />
|
||||
|
||||
<!-- 消息队列到文件存储 -->
|
||||
<line x1="650" y1="410" x2="700" y2="410" class="arrow" />
|
||||
|
||||
<!-- 文件存储到日志存储 -->
|
||||
<line x1="850" y1="410" x2="900" y2="410" class="arrow" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.4 KiB |
@@ -7,11 +7,11 @@
|
||||
## 2. 技术架构
|
||||
|
||||
- **前端框架**: Vue.js 3
|
||||
- **可视化库**: ECharts + 自定义Vue 3兼容组件(替代DataV)
|
||||
- **可视化库**: DataV(替代 ECharts)
|
||||
- **构建工具**: Vite
|
||||
- **状态管理**: Pinia
|
||||
- **响应式设计**: 支持多种大屏比例(16:9, 4:3等)
|
||||
- **实时数据**: WebSocket实时数据推送
|
||||
- **实时数据**: WebSocket 实时数据推送
|
||||
- **代码规范**: ESLint + Prettier
|
||||
- **单元测试**: Vitest
|
||||
|
||||
@@ -22,15 +22,15 @@
|
||||
- 整体产业规模展示(牛只总数、牧场数量等关键指标)
|
||||
- 产值和增长率关键指标(年度产值、增长率趋势图)
|
||||
- 第2天:
|
||||
- 数据可视化图表实现(饼图展示品种分布、柱状图展示区域分布)
|
||||
- 实时数据更新机制(WebSocket连接建立)
|
||||
- 数据可视化图表实现(使用 DataV 组件展示品种分布、区域分布)
|
||||
- 实时数据更新机制(WebSocket 连接建立)
|
||||
- 第3天:
|
||||
- 数据钻取功能实现(点击图表可查看详细数据)
|
||||
- 多维度数据展示(按时间、区域、品种等维度筛选)
|
||||
|
||||
### 3.2 养殖监控模块 (3天)
|
||||
- 第1天:
|
||||
- 各牧场养殖情况展示(地图展示各牧场位置和规模)
|
||||
- 各牧场养殖情况展示(使用 DataV 地图组件展示各牧场位置和规模)
|
||||
- 环境数据实时监控(温湿度、氨气浓度等传感器数据)
|
||||
- 第2天:
|
||||
- 异常情况告警展示(环境异常、健康异常等告警信息)
|
||||
@@ -89,221 +89,21 @@
|
||||
|
||||
## 4. 技术实现要点
|
||||
|
||||
- 使用自定义全屏容器组件实现自适应全屏显示
|
||||
- 使用 DataV 组件库实现自适应全屏显示
|
||||
- 开发装饰组件(如边框、装饰线等)增强视觉效果
|
||||
- 采用合理的布局结构(如三栏布局)分布数据展示区域
|
||||
- 开发排名轮播组件展示动态数据
|
||||
- 采用深色科技风格背景,搭配主题色系(如绿色渐变)体现业务特色
|
||||
- 添加实时时间显示等实用功能
|
||||
- 结合ECharts图表实现丰富的数据可视化
|
||||
- 结合 DataV 图表实现丰富的数据可视化
|
||||
- 使用自适应容器确保不同分辨率下的正常显示
|
||||
- 添加窗口大小改变时的重绘功能
|
||||
- 实现WebSocket实时数据推送机制
|
||||
- 针对大数据量渲染进行性能优化
|
||||
|
||||
## 5. 数据接口对接
|
||||
## 5. 里程碑
|
||||
|
||||
### 5.1 API接口对接计划
|
||||
- 对接养殖管理相关接口:
|
||||
- 牛只档案数据接口(获取牛只总数、品种分布等)
|
||||
- 环境监测数据接口(获取实时环境数据)
|
||||
- 饲养记录数据接口(获取饲料消耗、投喂量等数据)
|
||||
|
||||
- 对接金融服务相关接口:
|
||||
- 贷款申请数据接口(获取贷款申请数、放款总额等)
|
||||
- 保险保单数据接口(获取投保数量、保费总额等)
|
||||
- 理赔数据接口(获取理赔情况)
|
||||
|
||||
- 对接交易相关接口:
|
||||
- 活牛交易数据接口(获取交易量、价格等数据)
|
||||
- 商城订单数据接口(获取销售数据、热门商品等)
|
||||
|
||||
- 对接政府监管相关接口:
|
||||
- 防疫数据接口(获取防疫完成率等数据)
|
||||
- 补贴发放数据接口(获取补贴发放情况)
|
||||
|
||||
- 对接数据分析相关接口:
|
||||
- 预警信息接口(获取各类风险预警信息)
|
||||
- 分析报告接口(获取各类分析报告数据)
|
||||
|
||||
### 5.2 数据更新机制
|
||||
- 实时数据:通过WebSocket推送机制实现实时更新
|
||||
- 定时数据:通过定时轮询机制定期更新(如每5分钟)
|
||||
- 手动刷新:提供手动刷新按钮供用户主动刷新数据
|
||||
|
||||
## 6. 开发阶段规划
|
||||
|
||||
### 6.1 阶段一:基础框架搭建 (4天)
|
||||
- 第1天:
|
||||
- 项目初始化和环境配置
|
||||
- 移除DataV依赖,搭建Vue 3兼容的基础组件库
|
||||
- 第2天:
|
||||
- 开发基础可视化组件(边框、装饰线等)
|
||||
- 实现全屏适配方案
|
||||
- 第3天:
|
||||
- 开发排名轮播组件
|
||||
- 实现WebSocket连接机制
|
||||
- 第4天:
|
||||
- 基础数据获取服务封装
|
||||
- 开发基础布局组件
|
||||
|
||||
### 6.2 阶段二:核心功能开发 (18天)
|
||||
- 第1-3天:
|
||||
- 产业概览模块开发
|
||||
- 第4-6天:
|
||||
- 养殖监控模块开发
|
||||
- 第7-8天:
|
||||
- 金融服务模块开发
|
||||
- 第9-10天:
|
||||
- 交易统计模块开发
|
||||
- 第11-12天:
|
||||
- 运输跟踪模块开发
|
||||
- 第13-14天:
|
||||
- 风险预警模块开发
|
||||
- 第15-16天:
|
||||
- 生态指标模块开发
|
||||
- 第17-18天:
|
||||
- 政府监管模块开发
|
||||
|
||||
### 6.3 阶段三:集成测试与优化 (4天)
|
||||
- 第1天:
|
||||
- 功能测试和Bug修复
|
||||
- 第2天:
|
||||
- 性能优化(大数据量渲染优化)
|
||||
- 第3天:
|
||||
- 视觉效果优化
|
||||
- 第4天:
|
||||
- 响应式适配测试和部署准备
|
||||
|
||||
## 7. 质量保障措施
|
||||
|
||||
- 单元测试覆盖率达到80%以上
|
||||
- 代码审查机制确保代码质量
|
||||
- 自动化测试保障功能稳定性
|
||||
- 性能测试确保系统响应速度(<2秒)
|
||||
- 多种分辨率适配测试
|
||||
- 实时数据推送功能专项测试
|
||||
- 大屏设备兼容性测试
|
||||
|
||||
## 8. 部署与运维
|
||||
|
||||
- 支持Docker容器化部署
|
||||
- 支持云平台部署
|
||||
- 日志收集和分析
|
||||
- 系统监控和告警机制
|
||||
- 大屏设备适配和部署指导
|
||||
- 实时数据推送服务监控
|
||||
- 性能监控和优化建议
|
||||
|
||||
## 9. 风险与应对措施
|
||||
|
||||
### 9.1 技术风险
|
||||
- **Vue 3与DataV兼容性问题**:
|
||||
- 风险:DataV是为Vue 2设计的,在Vue 3中存在兼容性问题
|
||||
- 应对:完全移除DataV依赖,使用原生Vue 3组件和ECharts实现可视化功能
|
||||
|
||||
### 9.2 性能风险
|
||||
- **大数据量渲染性能问题**:
|
||||
- 风险:大屏系统需要展示大量数据,可能导致页面卡顿
|
||||
- 应对:采用虚拟滚动、数据分页、懒加载等技术优化性能
|
||||
|
||||
### 9.3 数据风险
|
||||
- **实时数据推送稳定性问题**:
|
||||
- 风险:WebSocket连接可能中断,导致数据更新不及时
|
||||
- 应对:实现断线重连机制,提供数据手动刷新功能
|
||||
|
||||
## 10. 验收标准
|
||||
|
||||
- 所有功能模块按计划完成并正常运行
|
||||
- 系统能够在不同分辨率的大屏设备上正常显示
|
||||
- 实时数据推送功能稳定可靠
|
||||
- 系统响应时间小于2秒
|
||||
- 通过所有测试用例,包括功能测试、性能测试和兼容性测试
|
||||
- 用户界面美观,符合设计要求
|
||||
|
||||
## 11. 自定义组件开发计划
|
||||
|
||||
### 11.1 核心可视化组件 (3天)
|
||||
- 第1天:
|
||||
- 开发全屏容器组件
|
||||
- 实现基础边框组件
|
||||
- 第2天:
|
||||
- 开发装饰线组件
|
||||
- 实现动态数字组件
|
||||
- 第3天:
|
||||
- 开发排名轮播组件
|
||||
- 实现水球图组件
|
||||
|
||||
### 11.2 布局组件 (2天)
|
||||
- 第1天:
|
||||
- 开发栅格布局组件
|
||||
- 实现响应式容器组件
|
||||
- 第2天:
|
||||
- 开发卡片组件
|
||||
- 实现折叠面板组件
|
||||
|
||||
### 11.3 工具组件 (1天)
|
||||
- 第1天:
|
||||
- 开发时间显示组件
|
||||
- 实现加载状态组件
|
||||
- 开发提示信息组件
|
||||
|
||||
## 12. 性能优化策略
|
||||
|
||||
### 12.1 前端性能优化
|
||||
- 使用虚拟滚动技术处理大量数据展示
|
||||
- 实施组件懒加载减少初始加载时间
|
||||
- 采用图片懒加载和压缩优化视觉效果
|
||||
- 利用localStorage缓存部分非敏感数据减少请求
|
||||
|
||||
### 12.2 数据处理优化
|
||||
- 对大量数据进行分页处理
|
||||
- 实现数据缓存机制减少重复请求
|
||||
- 使用防抖和节流技术优化高频操作
|
||||
- 对复杂计算采用Web Worker处理
|
||||
|
||||
### 12.3 网络优化
|
||||
- 实施WebSocket心跳机制保持连接稳定
|
||||
- 使用gzip压缩减少数据传输量
|
||||
- 实现请求合并减少网络请求次数
|
||||
- 设置合理的缓存策略提高响应速度
|
||||
|
||||
## 13. 可视化设计规范
|
||||
|
||||
### 13.1 色彩规范
|
||||
- 主色调:草原绿色系(#4CAF50, #8BC34A)
|
||||
- 辅助色:科技蓝(#2196F3)、警示红(#F44336)
|
||||
- 背景色:深灰(#1e1e1e)营造科技感
|
||||
- 文字色:白色(#FFFFFF)和浅灰(#E0E0E0)
|
||||
|
||||
### 13.2 字体规范
|
||||
- 主要字体:Microsoft YaHei, sans-serif
|
||||
- 数字字体:DIN Pro(用于关键数据展示)
|
||||
- 字号规范:
|
||||
- 标题:24-32px
|
||||
- 正文:14-18px
|
||||
- 辅助文字:12-14px
|
||||
- 数据展示:16-24px
|
||||
|
||||
### 13.3 图表规范
|
||||
- 使用ECharts作为主要图表库
|
||||
- 统一图表配色方案
|
||||
- 保持图表风格一致性
|
||||
- 提供图表交互功能(tooltip、legend等)
|
||||
|
||||
## 14. 安全性考虑
|
||||
|
||||
### 14.1 数据安全
|
||||
- 对敏感数据进行加密传输
|
||||
- 实施访问权限控制
|
||||
- 对用户操作进行日志记录
|
||||
|
||||
### 14.2 网络安全
|
||||
- 使用HTTPS协议保障数据传输安全
|
||||
- 实施CSRF防护措施
|
||||
- 对输入数据进行验证和过滤
|
||||
|
||||
### 14.3 系统安全
|
||||
- 定期更新依赖库修复安全漏洞
|
||||
- 实施CORS策略防止跨站攻击
|
||||
- 对WebSocket连接进行身份验证
|
||||
- **里程碑1**:完成产业概览模块和养殖监控模块(6天)
|
||||
- **里程碑2**:完成金融服务模块和交易统计模块(4天)
|
||||
- **里程碑3**:完成运输跟踪模块和风险预警模块(4天)
|
||||
- **里程碑4**:完成生态指标模块和政府监管模块(4天)
|
||||
- **里程碑5**:系统联调和测试(3天)
|
||||
- **里程碑6**:上线部署(1天)
|
||||
127
docs/development_plans/website_development_plan.md
Normal file
127
docs/development_plans/website_development_plan.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# 官网开发计划
|
||||
|
||||
## 1. 系统概述
|
||||
|
||||
官网是锡林郭勒盟地区智慧养殖产业平台的对外展示窗口,主要用于宣传平台功能、展示产业动态、发布新闻资讯以及提供用户访问入口。官网采用纯HTML5 + CSS3 + JavaScript技术栈,确保快速加载和良好的兼容性。
|
||||
|
||||
## 2. 技术架构
|
||||
|
||||
- **前端技术**: HTML5 + CSS3 + JavaScript
|
||||
- **CSS框架**: Bootstrap 5
|
||||
- **图标库**: Bootstrap Icons
|
||||
- **图表库**: Chart.js
|
||||
- **响应式设计**: 移动优先的响应式布局
|
||||
- **代码规范**: ESLint + Prettier
|
||||
- **部署方式**: 静态文件部署
|
||||
|
||||
## 3. 页面详细计划
|
||||
|
||||
### 3.1 首页 (1周)
|
||||
- 第1天:
|
||||
- 页面结构搭建和基础样式设计
|
||||
- 导航栏和页脚实现
|
||||
- 第2-3天:
|
||||
- 英雄区域开发(包含背景图片、标题、按钮等)
|
||||
- 核心功能模块展示区域开发
|
||||
- 第4-5天:
|
||||
- 数据可视化展示区域开发(集成Chart.js图表)
|
||||
- 行业动态展示区域开发
|
||||
- 第6-7天:
|
||||
- 响应式优化和浏览器兼容性测试
|
||||
- 动画效果和交互优化
|
||||
|
||||
### 3.2 导航功能 (2天)
|
||||
- 第1天:
|
||||
- 顶部导航栏开发(包含响应式折叠菜单)
|
||||
- 页面内锚点导航功能实现
|
||||
- 第2天:
|
||||
- 平滑滚动功能开发
|
||||
- 导航栏滚动效果优化
|
||||
|
||||
### 3.3 数据可视化 (2天)
|
||||
- 第1天:
|
||||
- 牲畜存栏量统计图表开发
|
||||
- 牧草产量与价格趋势图表开发
|
||||
- 第2天:
|
||||
- 图表响应式优化
|
||||
- 图表交互功能完善
|
||||
|
||||
### 3.4 新闻资讯 (2天)
|
||||
- 第1天:
|
||||
- 新闻列表页面开发
|
||||
- 新闻分类标签实现
|
||||
- 第2天:
|
||||
- 新闻轮播功能开发
|
||||
- 查看更多功能实现
|
||||
|
||||
### 3.5 用户交互 (2天)
|
||||
- 第1天:
|
||||
- 悬停效果和动画开发
|
||||
- 功能卡片交互效果实现
|
||||
- 第2天:
|
||||
- 响应式设计优化
|
||||
- 移动端适配测试
|
||||
|
||||
### 3.6 页脚信息 (1天)
|
||||
- 第1天:
|
||||
- 页脚结构开发
|
||||
- 快速链接和联系方式展示
|
||||
- 社交媒体链接集成
|
||||
|
||||
## 4. 设计要求
|
||||
|
||||
### 4.1 视觉设计
|
||||
- 采用草原绿色主题色调
|
||||
- 融入蒙古族文化元素
|
||||
- 简洁现代的设计风格
|
||||
- 清晰的信息层级结构
|
||||
|
||||
### 4.2 交互体验
|
||||
- 页面加载动画
|
||||
- 按钮和链接的悬停效果
|
||||
- 表单输入的实时验证反馈
|
||||
- 移动端友好的触控交互
|
||||
|
||||
## 5. 测试计划
|
||||
|
||||
### 5.1 功能测试
|
||||
- 所有页面链接有效性测试
|
||||
- 导航功能测试
|
||||
- 图表显示功能测试
|
||||
- 新闻轮播功能测试
|
||||
|
||||
### 5.2 兼容性测试
|
||||
- 主流浏览器兼容性测试 (Chrome、Firefox、Safari、Edge)
|
||||
- 移动端浏览器测试 (iOS Safari、Android Chrome)
|
||||
- 不同分辨率屏幕适配测试
|
||||
|
||||
### 5.3 性能测试
|
||||
- 页面加载速度测试
|
||||
- 图片和静态资源优化验证
|
||||
- 图表渲染性能测试
|
||||
|
||||
## 6. 部署计划
|
||||
|
||||
### 6.1 部署环境
|
||||
- Nginx服务器部署
|
||||
- SSL证书配置
|
||||
- CDN加速配置
|
||||
|
||||
### 6.2 部署流程
|
||||
1. 代码构建和压缩
|
||||
2. 静态文件上传至服务器
|
||||
3. Nginx配置更新
|
||||
4. 域名解析配置
|
||||
5. 上线验证测试
|
||||
|
||||
## 7. 维护计划
|
||||
|
||||
### 7.1 内容维护
|
||||
- 定期更新新闻资讯
|
||||
- 更新数据可视化内容
|
||||
- 优化页面内容和结构
|
||||
|
||||
### 7.2 技术维护
|
||||
- 定期更新依赖库版本
|
||||
- 安全漏洞修复
|
||||
- 性能优化
|
||||
@@ -12,7 +12,7 @@ xlxumu - 锡林郭勒盟地区养殖产业平台
|
||||
- 构建完整的畜牧业数字化管理生态系统
|
||||
- 实现养殖过程可视化、监管实时化、交易透明化
|
||||
- 提升产业链协同效率
|
||||
- 促进锡林郭勒盟安格斯牛品牌建设
|
||||
- 促进锡林郭勒盟智慧养殖品牌建设
|
||||
|
||||
## 2. 非功能需求
|
||||
|
||||
|
||||
78
docs/requirements/WEBSITE_REQUIREMENTS.md
Normal file
78
docs/requirements/WEBSITE_REQUIREMENTS.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# 官网需求文档
|
||||
|
||||
## 1. 系统概述
|
||||
|
||||
官网是锡林郭勒盟地区智慧养殖产业平台的对外展示窗口,主要用于宣传平台功能、展示产业动态、发布新闻资讯以及提供用户访问入口。通过官网,用户可以了解平台的核心价值和服务内容。
|
||||
|
||||
## 2. 功能需求
|
||||
|
||||
### 2.1 首页展示
|
||||
- **平台介绍展示**:在英雄区域展示平台的核心功能和价值主张
|
||||
- **核心功能模块展示**:以卡片形式展示六大核心功能模块(数字化养殖管理、活体抵押贷款、养殖保险监管、政府监管平台、线上活牛交易、优质牛肉商城)
|
||||
- **数据可视化展示**:以图表形式展示部分公开的产业数据(牲畜存栏量统计、牧草产量与价格趋势)
|
||||
- **行业动态展示**:展示最新的产业新闻和市场动态
|
||||
- **联系信息展示**:提供平台联系方式和地址信息
|
||||
|
||||
### 2.2 导航功能
|
||||
- **顶部导航栏**:提供首页、平台功能、数据看板、行业动态、关于我们等页面导航
|
||||
- **锚点导航**:支持页面内锚点跳转
|
||||
- **响应式导航**:移动端自适应折叠导航菜单
|
||||
|
||||
### 2.3 数据可视化
|
||||
- **牲畜存栏量统计图表**:以柱状图展示不同牲畜的存栏量数据
|
||||
- **牧草产量与价格趋势图表**:以折线图展示牧草产量与价格的变化趋势
|
||||
- **图表交互功能**:支持图表的响应式显示和交互操作
|
||||
|
||||
### 2.4 新闻资讯
|
||||
- **新闻列表展示**:按时间顺序展示平台相关新闻和行业资讯
|
||||
- **新闻分类标签**:通过标签区分不同类型的新闻(政策解读、市场动态、技术前沿)
|
||||
- **新闻详情查看**:用户可以点击查看新闻详细内容
|
||||
- **查看更多功能**:提供查看更多新闻的入口
|
||||
|
||||
### 2.5 用户交互
|
||||
- **平滑滚动**:页面内导航支持平滑滚动效果
|
||||
- **悬停效果**:功能卡片等元素支持悬停动画效果
|
||||
- **响应式设计**:适配不同屏幕尺寸的设备
|
||||
|
||||
### 2.6 页脚信息
|
||||
- **平台介绍**:简要介绍平台定位和目标
|
||||
- **快速链接**:提供主要页面的快速访问链接
|
||||
- **联系方式**:展示联系地址、电话和邮箱等信息
|
||||
- **社交媒体链接**:提供微信、微博、YouTube等社交媒体链接
|
||||
- **法律信息**:提供隐私政策和使用条款链接
|
||||
|
||||
## 3. 非功能需求
|
||||
|
||||
### 3.1 性能需求
|
||||
- 页面加载时间不超过3秒
|
||||
- 支持1000+并发用户访问
|
||||
- 图片和静态资源需要优化加载
|
||||
|
||||
### 3.2 兼容性需求
|
||||
- 兼容所有主流浏览器(Chrome、Firefox、Safari、Edge)
|
||||
- 支持移动端浏览和响应式显示
|
||||
- 适配不同分辨率屏幕
|
||||
|
||||
### 3.3 安全需求
|
||||
- 防止XSS攻击和SQL注入
|
||||
- 敏感信息传输加密
|
||||
- 定期安全漏洞扫描
|
||||
|
||||
### 3.4 可用性需求
|
||||
- 界面简洁美观,符合草原文化特色
|
||||
- 导航清晰,用户可以快速找到所需信息
|
||||
- 提供友好的错误提示信息
|
||||
|
||||
## 4. 用户角色
|
||||
|
||||
### 4.1 普通访客
|
||||
- 可以浏览官网所有公开内容
|
||||
- 可以查看新闻资讯
|
||||
- 可以了解平台功能介绍
|
||||
- 可以查看公开的统计数据
|
||||
|
||||
### 4.2 管理员
|
||||
- 可以发布和管理新闻资讯
|
||||
- 可以更新平台介绍内容
|
||||
- 可以管理合作伙伴信息
|
||||
- 可以更新数据可视化内容
|
||||
@@ -2,157 +2,92 @@
|
||||
|
||||
## 1. 系统概述
|
||||
|
||||
大屏可视化系统是锡林郭勒盟安格斯牛养殖产业平台的重要组成部分,主要用于展示锡林郭勒盟地区安格斯牛养殖产业的整体数据、实时监控信息和分析结果。通过直观的图表和数据可视化方式,为管理者提供全面的产业洞察,支持决策制定。
|
||||
大屏可视化系统是锡林郭勒盟智慧养殖产业平台的重要组成部分,主要用于展示锡林郭勒盟地区智慧养殖产业的整体数据、实时监控信息和分析结果。通过直观的图表和数据可视化方式,为管理者提供全面的产业洞察,支持决策制定。
|
||||
|
||||
## 2. 功能需求
|
||||
|
||||
### 2.1 产业概览模块
|
||||
- **整体产业规模展示**:展示牛只总数、牧场数量等关键指标
|
||||
- **产值和增长率关键指标**:展示年度产值、增长率趋势图
|
||||
- **数据可视化图表**:通过饼图展示品种分布、柱状图展示区域分布
|
||||
- **实时数据更新机制**:通过WebSocket实现数据实时更新
|
||||
- **数据钻取功能**:支持点击图表查看详细数据
|
||||
- **多维度数据展示**:支持按时间、区域、品种等维度筛选
|
||||
- **整体产业规模展示**:展示牛只总数、牧场数量等关键指标(数据来源:`/api/v1/dashboard/overview`,数据库表:`industry_overview`)
|
||||
- **产值和增长率关键指标**:展示年度产值、增长率趋势图(数据刷新频率:每5秒一次,数据来源:`/api/v1/dashboard/growth`)
|
||||
- **数据可视化图表**:通过 DataV 组件展示品种分布、区域分布等(支持动态缩放和拖拽)
|
||||
- **实时数据更新机制**:通过 WebSocket 实现数据实时更新(`ws://<host>/api/v1/dashboard/realtime`)
|
||||
- **数据钻取功能**:支持点击图表查看详细数据(弹窗展示,含数据导出按钮)
|
||||
- **多维度数据筛选**:支持按时间、区域、品种等维度筛选(交互:下拉选择器 + 确认按钮)
|
||||
|
||||
### 2.2 养殖监控模块
|
||||
- **各牧场养殖情况展示**:通过地图展示各牧场位置和规模
|
||||
- **环境数据实时监控**:展示温湿度、氨气浓度等传感器数据
|
||||
- **异常情况告警展示**:展示环境异常、健康异常等告警信息
|
||||
- **历史数据趋势分析**:展示环境数据历史趋势图
|
||||
- **牛只健康状态监控**:展示健康、亚健康、患病牛只数量统计
|
||||
- **饲养记录统计**:展示饲料消耗、投喂量趋势
|
||||
- **各牧场养殖情况展示**:通过 DataV 地图组件展示各牧场位置和规模(数据来源:`/api/v1/dashboard/farms`,数据库表:`farm_locations`)
|
||||
- **环境数据实时监控**:展示温湿度、氨气浓度等传感器数据(刷新频率:每3秒一次,数据来源:`/api/v1/dashboard/environment`)
|
||||
- **异常情况告警**:展示环境异常、健康异常等告警信息(交互:点击告警跳转到详情页)
|
||||
- **历史数据趋势分析**:展示环境数据历史趋势图(支持时间范围选择:1天/7天/30天)
|
||||
- **牛只健康状态监控**:展示健康、亚健康、患病牛只数量统计(数据来源:`/api/v1/dashboard/health`)
|
||||
- **饲养记录统计**:展示饲料消耗、投喂量趋势(交互:悬停显示具体数值)
|
||||
|
||||
### 2.3 金融服务模块
|
||||
- **贷款统计展示**:展示贷款申请数、放款总额、还款情况
|
||||
- **保险统计展示**:展示投保数量、保费总额、理赔情况
|
||||
- **风险数据展示**:展示高风险贷款、理赔率高的牧场等
|
||||
- **金融服务趋势分析**:展示贷款和保险业务增长趋势
|
||||
- **贷款统计展示**:展示贷款申请数、放款总额、还款情况(数据来源:`/api/v1/dashboard/loans`,数据库表:`loan_records`)
|
||||
- **保险统计展示**:展示投保数量、保费总额、理赔情况(数据来源:`/api/v1/dashboard/insurance`)
|
||||
- **风险数据展示**:展示高风险贷款、理赔率高的牧场等(交互:点击跳转到风险管理页)
|
||||
- **金融服务趋势分析**:展示贷款和保险业务增长趋势(支持按季度/年度切换)
|
||||
|
||||
### 2.4 交易统计模块
|
||||
- **牛只交易量统计**:展示日交易量、月交易量、年度交易量
|
||||
- **价格趋势和区域分布展示**:展示价格热力图、区域价格对比
|
||||
- **交易类型分析**:分析活牛交易、牛肉制品销售等
|
||||
- **交易排行榜**:展示热门牧场、活跃交易员等
|
||||
- **牛只交易量统计**:展示日交易量、月交易量、年度交易量(数据来源:`/api/v1/dashboard/transactions`,数据库表:`transaction_logs`)
|
||||
- **价格趋势和区域分布**:展示价格热力图、区域价格对比(交互:点击区域高亮显示)
|
||||
- **交易类型分析**:分析活牛交易、牛肉制品销售等(数据来源:`/api/v1/dashboard/transaction-types`)
|
||||
- **交易排行榜**:展示热门牧场、活跃交易员等(交互:点击名称查看详情)
|
||||
|
||||
### 2.5 运输跟踪模块
|
||||
- **牛只运输实时状态展示**:展示运输路线、当前位置、预计到达时间
|
||||
- **运输车辆监控**:展示车辆状态、司机信息等
|
||||
- **运输异常告警**:展示延误、偏离路线等异常情况
|
||||
- **运输效率分析**:展示运输时间、成本等统计
|
||||
- **牛只运输实时状态**:展示运输路线、当前位置、预计到达时间(数据来源:`/api/v1/dashboard/transport`,数据库表:`transport_logs`)
|
||||
- **运输车辆监控**:展示车辆状态、司机信息等(交互:点击车辆查看实时视频)
|
||||
- **运输异常告警**:展示延误、偏离路线等异常情况(数据来源:`/api/v1/dashboard/transport-alerts`)
|
||||
- **运输效率分析**:展示运输时间、成本等统计(支持导出为Excel)
|
||||
|
||||
### 2.6 风险预警模块
|
||||
- **风险事件展示**:展示疫病风险、市场风险、自然灾害风险等
|
||||
- **预警信息推送和展示**:分类展示不同级别预警
|
||||
- **风险趋势分析**:展示各类风险的历史趋势和预测
|
||||
- **风险地图**:按区域展示风险分布
|
||||
- **风险事件展示**:展示疫病风险、市场风险、自然灾害风险等(数据来源:`/api/v1/dashboard/risks`)
|
||||
- **预警信息推送**:分类展示不同级别预警(交互:点击预警订阅通知)
|
||||
- **风险趋势分析**:展示各类风险的历史趋势和预测(支持自定义时间范围)
|
||||
- **风险地图**:按区域展示风险分布(交互:点击区域查看详情)
|
||||
|
||||
### 2.7 生态指标模块
|
||||
- **环保数据展示**:展示碳排放、水资源使用、饲料消耗等
|
||||
- **可持续发展指标展示**:展示草畜平衡、生态效益等
|
||||
- **环保趋势分析**:展示环保指标的历史变化趋势
|
||||
- **生态效益评估**:展示经济效益与生态效益的平衡分析
|
||||
- **环保数据展示**:展示碳排放、水资源使用、饲料消耗等(数据来源:`/api/v1/dashboard/eco`)
|
||||
- **可持续发展指标**:展示草畜平衡、生态效益等(交互:悬停显示计算方式)
|
||||
- **环保趋势分析**:展示环保指标的历史变化趋势(支持同比/环比对比)
|
||||
- **生态效益评估**:展示经济效益与生态效益的平衡分析(数据来源:`/api/v1/dashboard/eco-balance`)
|
||||
|
||||
### 2.8 政府监管模块
|
||||
- **监管数据总览**:展示防疫完成率、补贴发放情况等
|
||||
- **合规性检查结果展示**:展示合规牧场比例、违规事件统计等
|
||||
- **政策执行效果分析**:展示政策实施后的数据变化
|
||||
- **监管报告生成和展示**:展示自动生成的监管报告可视化
|
||||
- **监管数据总览**:展示防疫完成率、补贴发放情况等(数据来源:`/api/v1/dashboard/gov`)
|
||||
- **合规性检查结果**:展示合规牧场比例、违规事件统计等(交互:点击违规事件查看整改记录)
|
||||
- **政策执行效果分析**:展示政策实施后的数据变化(支持多政策对比)
|
||||
- **监管报告生成**:展示自动生成的监管报告可视化(支持PDF导出)
|
||||
|
||||
## 3. 用户角色与权限
|
||||
## 3. 非功能需求
|
||||
|
||||
### 3.1 政府监管人员
|
||||
- 可以查看所有模块的数据和图表
|
||||
- 可以导出数据报告
|
||||
- 可以配置部分展示参数
|
||||
### 3.1 性能需求
|
||||
- **响应时间**:页面加载 ≤1秒,数据查询 ≤2秒
|
||||
- **数据更新延迟**:实时数据 ≤3秒(WebSocket推送)
|
||||
- **并发支持**:同时展示10个图表(DataV优化渲染)
|
||||
- **大数据量**:支持10万条数据流畅渲染(虚拟滚动)
|
||||
|
||||
### 3.2 系统管理员
|
||||
- 拥有所有功能权限
|
||||
- 可以配置系统参数
|
||||
- 可以管理用户权限
|
||||
### 3.2 兼容性需求
|
||||
- **屏幕比例**:适配16:9、4:3等常见比例
|
||||
- **分辨率**:支持1080p至8K
|
||||
- **浏览器**:Chrome、Firefox、Safari、Edge最新版
|
||||
|
||||
## 4. 非功能需求
|
||||
### 3.3 安全需求
|
||||
- **传输加密**:HTTPS + WSS(WebSocket Secure)
|
||||
- **日志审计**:操作日志保留6个月
|
||||
- **防护措施**:防SQL注入、XSS攻击
|
||||
|
||||
### 4.1 性能需求
|
||||
- 系统响应时间小于2秒
|
||||
- 数据更新延迟不超过5秒
|
||||
- 支持同时展示多个数据图表
|
||||
- 大数据量渲染优化,确保流畅显示
|
||||
### 3.4 可用性需求
|
||||
- **可用性**:99.9% SLA
|
||||
- **错误提示**:中英文双语错误信息
|
||||
- **异常处理**:自动重试3次 + 降级展示
|
||||
- **全屏模式**:一键切换全屏/窗口模式
|
||||
|
||||
### 4.2 兼容性需求
|
||||
- 支持多种大屏比例(16:9, 4:3等)
|
||||
- 适配不同分辨率的大屏设备
|
||||
- 支持主流浏览器(Chrome、Firefox、Safari、Edge)
|
||||
### 3.5 可维护性需求
|
||||
- **架构**:微服务设计(模块解耦)
|
||||
- **监控**:集成Prometheus + Grafana
|
||||
- **日志**:结构化日志(ELK收集)
|
||||
|
||||
### 4.3 安全需求
|
||||
- 用户身份认证和权限控制
|
||||
- 数据传输加密(HTTPS)
|
||||
- 操作日志记录和审计
|
||||
- 防止SQL注入和XSS攻击
|
||||
|
||||
### 4.4 可用性需求
|
||||
- 系统全年可用性达到99.9%
|
||||
- 提供友好的错误提示信息
|
||||
- 实现异常处理和恢复机制
|
||||
- 支持全屏显示模式
|
||||
|
||||
### 4.5 可维护性需求
|
||||
- 微服务架构设计,降低模块间耦合
|
||||
- 完善的日志记录和监控
|
||||
- 支持配置文件与代码分离
|
||||
- 提供完善的API文档
|
||||
|
||||
## 5. 技术要求
|
||||
|
||||
### 5.1 前端技术栈
|
||||
- **框架**:Vue.js 3
|
||||
- **可视化库**:ECharts
|
||||
- **构建工具**:Vite
|
||||
- **状态管理**:Pinia
|
||||
- **响应式设计**:支持多种大屏比例
|
||||
|
||||
### 5.2 实时数据
|
||||
- **数据推送**:WebSocket实时数据推送
|
||||
- **数据更新**:支持实时、定时和手动刷新
|
||||
|
||||
### 5.3 设计规范
|
||||
- **主题风格**:深色科技风格背景
|
||||
- **色彩搭配**:草原绿色系为主,科技蓝为辅
|
||||
- **字体规范**:清晰易读的字体设计
|
||||
- **布局结构**:合理的数据展示区域分布
|
||||
|
||||
## 6. 数据接口需求
|
||||
|
||||
### 6.1 养殖管理相关接口
|
||||
- 牛只档案数据接口(获取牛只总数、品种分布等)
|
||||
- 环境监测数据接口(获取实时环境数据)
|
||||
- 饲养记录数据接口(获取饲料消耗、投喂量等数据)
|
||||
|
||||
### 6.2 金融服务相关接口
|
||||
- 贷款申请数据接口(获取贷款申请数、放款总额等)
|
||||
- 保险保单数据接口(获取投保数量、保费总额等)
|
||||
- 理赔数据接口(获取理赔情况)
|
||||
|
||||
### 6.3 交易相关接口
|
||||
- 活牛交易数据接口(获取交易量、价格等数据)
|
||||
- 商城订单数据接口(获取销售数据、热门商品等)
|
||||
|
||||
### 6.4 政府监管相关接口
|
||||
- 防疫数据接口(获取防疫完成率等数据)
|
||||
- 补贴发放数据接口(获取补贴发放情况)
|
||||
|
||||
### 6.5 数据分析相关接口
|
||||
- 预警信息接口(获取各类风险预警信息)
|
||||
- 分析报告接口(获取各类分析报告数据)
|
||||
|
||||
## 7. 部署要求
|
||||
|
||||
### 7.1 部署环境
|
||||
- 支持Docker容器化部署
|
||||
- 支持云平台部署
|
||||
- 支持本地服务器部署
|
||||
|
||||
### 7.2 监控需求
|
||||
- 实现系统运行状态监控
|
||||
- 提供性能监控指标
|
||||
- 支持异常告警功能
|
||||
- 日志收集和分析
|
||||
## 4. 术语表
|
||||
- **数据钻取**:通过点击图表查看详细数据的交互方式
|
||||
- **虚拟滚动**:动态加载大数据量的优化技术
|
||||
- **SLA**:服务等级协议(可用性指标)
|
||||
@@ -1,10 +1,10 @@
|
||||
<!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>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>养殖管理系统 - 锡林郭勒盟智慧养殖数字化管理平台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "farming-management",
|
||||
"name": "bank-supervision",
|
||||
"version": "1.0.0",
|
||||
"description": "锡林郭勒盟安格斯牛养殖管理系统",
|
||||
"description": "锡林郭勒盟智慧养殖管理系统",
|
||||
"author": "xlxumu team",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -18,8 +18,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^3.0.0",
|
||||
"vite": "^3.0.0",
|
||||
"typescript": "^4.0.0",
|
||||
"@types/node": "^18.0.0"
|
||||
"vite": "^3.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
<!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>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>养殖管理系统 - 锡林郭勒盟智慧养殖数字化管理平台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "farming-management",
|
||||
"name": "cattle-trading",
|
||||
"version": "1.0.0",
|
||||
"description": "锡林郭勒盟安格斯牛养殖管理系统",
|
||||
"description": "锡林郭勒盟智慧养殖管理系统",
|
||||
"author": "xlxumu team",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -18,8 +18,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^3.0.0",
|
||||
"vite": "^3.0.0",
|
||||
"typescript": "^4.0.0",
|
||||
"@types/node": "^18.0.0"
|
||||
"vite": "^3.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
<!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>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>养殖管理系统 - 锡林郭勒盟智慧养殖数字化管理平台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
55728
frontend/dashboard/node_modules/.vite/deps/echarts.js
generated
vendored
55728
frontend/dashboard/node_modules/.vite/deps/echarts.js
generated
vendored
File diff suppressed because it is too large
Load Diff
4
frontend/dashboard/node_modules/.vite/deps/echarts.js.map
generated
vendored
4
frontend/dashboard/node_modules/.vite/deps/echarts.js.map
generated
vendored
File diff suppressed because one or more lines are too long
4182
frontend/dashboard/package-lock.json
generated
4182
frontend/dashboard/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,25 +1,28 @@
|
||||
{
|
||||
"name": "dashboard",
|
||||
"version": "1.0.0",
|
||||
"description": "锡林郭勒盟安格斯牛养殖产业大屏可视化系统",
|
||||
"author": "xlxumu team",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"ant-design-vue": "^3.0.0",
|
||||
"echarts": "^5.0.0",
|
||||
"pinia": "^2.0.0",
|
||||
"vue": "^3.2.0",
|
||||
"vue-router": "^4.0.0"
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"@jiaminghi/data-view": "^2.10.0",
|
||||
"@turf/turf": "^7.2.0",
|
||||
"ant-design-vue": "^3.2.20",
|
||||
"axios": "^1.11.0",
|
||||
"echarts": "^5.4.2",
|
||||
"pinia": "^2.0.33",
|
||||
"three": "^0.179.1",
|
||||
"vue": "^3.2.45",
|
||||
"vue-router": "^4.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.0.0",
|
||||
"@vitejs/plugin-vue": "^3.0.0",
|
||||
"typescript": "^4.0.0",
|
||||
"vite": "^3.0.0"
|
||||
}
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"vite": "^4.0.0"
|
||||
},
|
||||
"description": "锡林郭勒盟智慧养殖产业大屏可视化系统"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
<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>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
@@ -13,8 +24,42 @@ export default {
|
||||
<style>
|
||||
#app {
|
||||
height: 100vh;
|
||||
font-family: 'Arial', sans-serif;
|
||||
font-family: 'Microsoft YaHei', '微软雅黑', 'Helvetica Neue', Arial, sans-serif;
|
||||
background: #0a1929;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-nav {
|
||||
padding: 15px 20px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
padding: 10px 20px;
|
||||
border-radius: var(--border-radius);
|
||||
transition: var(--transition);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background: rgba(76, 175, 80, 0.3);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.nav-item.router-link-exact-active {
|
||||
background: linear-gradient(45deg, var(--primary-color), var(--primary-dark));
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -30,19 +75,41 @@ html, body {
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(76, 175, 80, 0.5);
|
||||
border-radius: 3px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(76, 175, 80, 0.8);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.main-nav {
|
||||
padding: 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding: 8px 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.nav-item {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
94
frontend/dashboard/src/components/RealtimeChart.vue
Normal file
94
frontend/dashboard/src/components/RealtimeChart.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div ref="chartContainer" class="realtime-chart"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'RealtimeChart',
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const chartContainer = ref(null);
|
||||
let chartInstance = null;
|
||||
|
||||
const initChart = () => {
|
||||
if (chartContainer.value) {
|
||||
chartInstance = echarts.init(chartContainer.value);
|
||||
updateChart();
|
||||
}
|
||||
};
|
||||
|
||||
const updateChart = () => {
|
||||
if (chartInstance && props.data) {
|
||||
const defaultOptions = {
|
||||
title: {
|
||||
text: '实时产业数据'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: props.data.xAxis || []
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [{
|
||||
data: props.data.series || [],
|
||||
type: 'line',
|
||||
smooth: true
|
||||
}]
|
||||
};
|
||||
|
||||
const finalOptions = Object.assign({}, defaultOptions, props.options);
|
||||
chartInstance.setOption(finalOptions);
|
||||
}
|
||||
};
|
||||
|
||||
const resizeChart = () => {
|
||||
if (chartInstance) {
|
||||
chartInstance.resize();
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.data, () => {
|
||||
updateChart();
|
||||
}, { deep: true });
|
||||
|
||||
onMounted(() => {
|
||||
initChart();
|
||||
window.addEventListener('resize', resizeChart);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose();
|
||||
}
|
||||
window.removeEventListener('resize', resizeChart);
|
||||
});
|
||||
|
||||
return {
|
||||
chartContainer
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.realtime-chart {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
}
|
||||
</style>
|
||||
77
frontend/dashboard/src/components/common/DataLoader.vue
Normal file
77
frontend/dashboard/src/components/common/DataLoader.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div class="data-loader">
|
||||
<div v-if="loading" class="loading-indicator">
|
||||
{{ loadingMessage }}
|
||||
</div>
|
||||
<div v-else-if="error" class="error-message">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
<div v-else-if="empty" class="empty-message">
|
||||
{{ emptyMessage }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DataLoader',
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false
|
||||
},
|
||||
error: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false
|
||||
},
|
||||
empty: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false
|
||||
},
|
||||
loadingMessage: {
|
||||
type: String,
|
||||
default: '数据加载中...'
|
||||
},
|
||||
errorMessage: {
|
||||
type: String,
|
||||
default: '数据加载失败,请稍后重试。'
|
||||
},
|
||||
emptyMessage: {
|
||||
type: String,
|
||||
default: '暂无数据'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.data-loader {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loading-indicator,
|
||||
.error-message,
|
||||
.empty-message {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.loading-indicator {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
71
frontend/dashboard/src/components/data/EnvironmentData.vue
Normal file
71
frontend/dashboard/src/components/data/EnvironmentData.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div class="environment-data">
|
||||
<h3>环境数据</h3>
|
||||
<ul>
|
||||
<li v-for="(item, index) in data" :key="index">
|
||||
<span class="data-name">{{ item.name }}:</span>
|
||||
<span class="data-value">{{ item.value }}</span>
|
||||
<span class="data-unit">{{ item.unit }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'EnvironmentData',
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.environment-data {
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.environment-data h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.environment-data ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.environment-data ul li {
|
||||
list-style: none;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.environment-data ul li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.data-name {
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.data-value {
|
||||
margin-right: 5px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.data-unit {
|
||||
color: #888;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
||||
48
frontend/dashboard/src/components/map/FarmMap.vue
Normal file
48
frontend/dashboard/src/components/map/FarmMap.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="farm-map-container">
|
||||
<div class="deprecated-warning">
|
||||
<p>该组件已弃用,请使用 ThreeDMap.vue 组件替代</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FarmMap',
|
||||
props: {
|
||||
height: {
|
||||
type: Number,
|
||||
default: 500,
|
||||
validator: value => value > 0
|
||||
},
|
||||
mapId: {
|
||||
type: String,
|
||||
default: 'farm-map'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.farm-map-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
border: 2px dashed #ccc;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.deprecated-warning {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.deprecated-warning p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
241
frontend/dashboard/src/components/map/ThreeDMap.vue
Normal file
241
frontend/dashboard/src/components/map/ThreeDMap.vue
Normal file
@@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<div class="three-d-map-container">
|
||||
<div
|
||||
ref="mapContainer"
|
||||
class="map-canvas"
|
||||
:style="{ width: '100%', height: height + 'px' }"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import * as THREE from 'three';
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||
import * as turf from '@turf/turf';
|
||||
|
||||
export default {
|
||||
name: 'ThreeDMap',
|
||||
props: {
|
||||
height: {
|
||||
type: Number,
|
||||
default: 500
|
||||
},
|
||||
// 锡林郭勒盟的中心坐标
|
||||
center: {
|
||||
type: Array,
|
||||
default: () => [116.08, 43.95] // 锡林浩特市
|
||||
},
|
||||
// 地图数据
|
||||
mapData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const mapContainer = ref(null);
|
||||
let scene, camera, renderer, controls;
|
||||
let animationId = null;
|
||||
|
||||
// 初始化3D场景
|
||||
const initScene = () => {
|
||||
// 创建场景
|
||||
scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x0a1929);
|
||||
scene.fog = new THREE.Fog(0x0a1929, 1000, 5000);
|
||||
|
||||
// 创建相机
|
||||
camera = new THREE.PerspectiveCamera(
|
||||
45,
|
||||
mapContainer.value.clientWidth / mapContainer.value.clientHeight,
|
||||
1,
|
||||
10000
|
||||
);
|
||||
camera.position.set(0, 500, 1000);
|
||||
|
||||
// 创建渲染器
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setSize(
|
||||
mapContainer.value.clientWidth,
|
||||
mapContainer.value.clientHeight
|
||||
);
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
renderer.shadowMap.enabled = true;
|
||||
mapContainer.value.appendChild(renderer.domElement);
|
||||
|
||||
// 添加光源
|
||||
const ambientLight = new THREE.AmbientLight(0x404040, 1);
|
||||
scene.add(ambientLight);
|
||||
|
||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
|
||||
directionalLight.position.set(1, 1, 1).normalize();
|
||||
directionalLight.castShadow = true;
|
||||
scene.add(directionalLight);
|
||||
|
||||
// 添加控制器
|
||||
controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
controls.dampingFactor = 0.05;
|
||||
|
||||
// 创建地形和地标
|
||||
createTerrain();
|
||||
createLandmarks();
|
||||
|
||||
// 开始渲染循环
|
||||
animate();
|
||||
};
|
||||
|
||||
// 创建地形
|
||||
const createTerrain = () => {
|
||||
// 创建一个简单的平面作为地形基础
|
||||
const geometry = new THREE.PlaneGeometry(2000, 2000, 50, 50);
|
||||
const material = new THREE.MeshStandardMaterial({
|
||||
color: 0x4CAF50,
|
||||
wireframe: false,
|
||||
transparent: true,
|
||||
opacity: 0.7
|
||||
});
|
||||
|
||||
const terrain = new THREE.Mesh(geometry, material);
|
||||
terrain.rotation.x = -Math.PI / 2;
|
||||
terrain.position.y = -10;
|
||||
terrain.receiveShadow = true;
|
||||
scene.add(terrain);
|
||||
|
||||
// 添加一些起伏效果
|
||||
const vertices = geometry.attributes.position.array;
|
||||
for (let i = 0; i < vertices.length; i += 3) {
|
||||
// 添加随机高度变化
|
||||
vertices[i + 2] = (Math.random() - 0.5) * 100;
|
||||
}
|
||||
geometry.attributes.position.needsUpdate = true;
|
||||
geometry.computeVertexNormals();
|
||||
};
|
||||
|
||||
// 创建地标
|
||||
const createLandmarks = () => {
|
||||
// 创建锡林郭勒盟主要旗县的地标
|
||||
const locations = [
|
||||
{ name: '锡林浩特市', position: [0, 0, 0], color: 0x2196F3 },
|
||||
{ name: '东乌珠穆沁旗', position: [-200, 0, 100], color: 0xFF9800 },
|
||||
{ name: '西乌珠穆沁旗', position: [200, 0, 100], color: 0xF44336 },
|
||||
{ name: '镶黄旗', position: [-100, 0, -150], color: 0x9C27B0 },
|
||||
{ name: '正镶白旗', position: [100, 0, -150], color: 0x4CAF50 }
|
||||
];
|
||||
|
||||
locations.forEach(location => {
|
||||
// 创建地标圆柱体
|
||||
const geometry = new THREE.CylinderGeometry(20, 20, 50, 32);
|
||||
const material = new THREE.MeshStandardMaterial({
|
||||
color: location.color,
|
||||
transparent: true,
|
||||
opacity: 0.8
|
||||
});
|
||||
const cylinder = new THREE.Mesh(geometry, material);
|
||||
cylinder.position.set(...location.position);
|
||||
cylinder.position.y = 25;
|
||||
cylinder.castShadow = true;
|
||||
scene.add(cylinder);
|
||||
|
||||
// 添加地标名称
|
||||
addLabel(location.name, location.position, location.color);
|
||||
});
|
||||
};
|
||||
|
||||
// 添加标签
|
||||
const addLabel = (text, position, color) => {
|
||||
// 创建简单的文本标签(在实际项目中可以使用更复杂的文本渲染)
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
canvas.width = 256;
|
||||
canvas.height = 128;
|
||||
|
||||
context.fillStyle = `#${color.toString(16).padStart(6, '0')}`;
|
||||
context.font = '24px Arial';
|
||||
context.textAlign = 'center';
|
||||
context.fillText(text, 128, 64);
|
||||
|
||||
const texture = new THREE.CanvasTexture(canvas);
|
||||
const material = new THREE.SpriteMaterial({ map: texture });
|
||||
const sprite = new THREE.Sprite(material);
|
||||
sprite.position.set(position[0], 80, position[2]);
|
||||
sprite.scale.set(100, 50, 1);
|
||||
scene.add(sprite);
|
||||
};
|
||||
|
||||
// 动画循环
|
||||
const animate = () => {
|
||||
animationId = requestAnimationFrame(animate);
|
||||
|
||||
// 更新控制器
|
||||
controls.update();
|
||||
|
||||
// 渲染场景
|
||||
renderer.render(scene, camera);
|
||||
};
|
||||
|
||||
// 窗口大小改变时调整渲染器大小
|
||||
const onWindowResize = () => {
|
||||
if (mapContainer.value) {
|
||||
camera.aspect = mapContainer.value.clientWidth / mapContainer.value.clientHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(
|
||||
mapContainer.value.clientWidth,
|
||||
mapContainer.value.clientHeight
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// 清理资源
|
||||
const cleanup = () => {
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId);
|
||||
}
|
||||
|
||||
if (renderer) {
|
||||
mapContainer.value.removeChild(renderer.domElement);
|
||||
renderer.dispose();
|
||||
}
|
||||
|
||||
if (controls) {
|
||||
controls.dispose();
|
||||
}
|
||||
|
||||
// 清理场景中的所有对象
|
||||
if (scene) {
|
||||
while(scene.children.length > 0) {
|
||||
scene.remove(scene.children[0]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initScene();
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', onWindowResize);
|
||||
cleanup();
|
||||
});
|
||||
|
||||
return {
|
||||
mapContainer
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.three-d-map-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.map-canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,59 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import Dashboard from '@/views/Dashboard.vue'
|
||||
import Monitor from '@/views/Monitor.vue'
|
||||
import Government from '@/views/Government.vue'
|
||||
import Finance from '@/views/Finance.vue'
|
||||
import Transport from '@/views/Transport.vue'
|
||||
import Risk from '@/views/Risk.vue'
|
||||
import Eco from '@/views/Eco.vue'
|
||||
import Gov from '@/views/Gov.vue'
|
||||
import Trade from '@/views/Trade.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Dashboard',
|
||||
component: Dashboard
|
||||
},
|
||||
{
|
||||
path: '/monitor',
|
||||
name: 'Monitor',
|
||||
component: Monitor
|
||||
},
|
||||
{
|
||||
path: '/government',
|
||||
name: 'Government',
|
||||
component: Government
|
||||
},
|
||||
{
|
||||
path: '/finance',
|
||||
name: 'Finance',
|
||||
component: Finance
|
||||
},
|
||||
{
|
||||
path: '/transport',
|
||||
name: 'Transport',
|
||||
component: Transport
|
||||
},
|
||||
{
|
||||
path: '/risk',
|
||||
name: 'Risk',
|
||||
component: Risk
|
||||
},
|
||||
{
|
||||
path: '/eco',
|
||||
name: 'Eco',
|
||||
component: Eco
|
||||
},
|
||||
{
|
||||
path: '/gov',
|
||||
name: 'Gov',
|
||||
component: Gov
|
||||
},
|
||||
{
|
||||
path: '/trade',
|
||||
name: 'Trade',
|
||||
component: Trade
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
53
frontend/dashboard/src/services/dashboard.js
Normal file
53
frontend/dashboard/src/services/dashboard.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const API_BASE_URL = 'http://localhost:3000/api/v1/dashboard';
|
||||
|
||||
export const fetchOverviewData = async () => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/overview`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching overview data:', error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchRealtimeData = async () => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/realtime`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching realtime data:', error);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchFarmData = async () => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/farm`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching farm data:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchGovernmentData = async (type) => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/government/${type}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching government data:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchFinanceData = async (type) => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/finance/${type}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching finance data:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
@@ -1,13 +1,26 @@
|
||||
/* 全局样式 */
|
||||
:root {
|
||||
--primary-color: #4CAF50;
|
||||
--secondary-color: #388E3C;
|
||||
--primary-light: #81C784;
|
||||
--primary-dark: #388E3C;
|
||||
--secondary-color: #2196F3;
|
||||
--accent-color: #FF9800;
|
||||
--warning-color: #FFC107;
|
||||
--danger-color: #F44336;
|
||||
--success-color: #4CAF50;
|
||||
--info-color: #2196F3;
|
||||
--light-color: #f5f5f5;
|
||||
--dark-color: #0a1929;
|
||||
--darker-color: #081424;
|
||||
--text-color: #ffffff;
|
||||
--text-secondary: #cccccc;
|
||||
--text-muted: #999999;
|
||||
--border-radius: 8px;
|
||||
--border-radius-lg: 12px;
|
||||
--border-radius-sm: 4px;
|
||||
--box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
--box-shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.4);
|
||||
--transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -17,11 +30,12 @@
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||
font-family: 'Microsoft YaHei', '微软雅黑', 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-color);
|
||||
background-color: var(--dark-color);
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#app {
|
||||
@@ -35,6 +49,30 @@ body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 通用标题样式 */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 0.5em;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* 通用按钮样式 */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
@@ -42,31 +80,146 @@ body {
|
||||
border-radius: var(--border-radius);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
transition: var(--transition);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: var(--text-color);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
|
||||
background: linear-gradient(45deg, var(--primary-color), var(--primary-dark));
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: linear-gradient(45deg, var(--secondary-color), #2E7D32);
|
||||
background: linear-gradient(45deg, var(--primary-dark), #2E7D32);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: linear-gradient(45deg, var(--secondary-color), #1976D2);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: linear-gradient(45deg, #1976D2, #1565C0);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: linear-gradient(45deg, var(--success-color), #388E3C);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background: linear-gradient(45deg, #388E3C, #2E7D32);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: linear-gradient(45deg, var(--warning-color), #FFA000);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-warning:hover {
|
||||
background: linear-gradient(45deg, #FFA000, #FF8F00);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: linear-gradient(45deg, var(--danger-color), #D32F2F);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: linear-gradient(45deg, #D32F2F, #C62828);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
|
||||
/* 卡片样式 */
|
||||
.card {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-radius: var(--border-radius-lg);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow: var(--box-shadow);
|
||||
transition: var(--transition);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: var(--box-shadow-lg);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: var(--border-radius);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 12px 15px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.table th {
|
||||
background: rgba(76, 175, 80, 0.2);
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.table tr:hover {
|
||||
background: rgba(76, 175, 80, 0.1);
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
@@ -87,21 +240,161 @@ body {
|
||||
background: rgba(76, 175, 80, 0.8);
|
||||
}
|
||||
|
||||
/* 加载指示器 */
|
||||
.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;
|
||||
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);
|
||||
}
|
||||
|
||||
/* DataV组件样式覆盖 */
|
||||
.dv-border-box-content {
|
||||
padding: 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.dv-scroll-board .header {
|
||||
color: #4CAF50 !important;
|
||||
background: rgba(76, 175, 80, 0.1) !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.dv-scroll-board .row {
|
||||
color: #fff !important;
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.dv-scroll-board .row:hover {
|
||||
background: rgba(76, 175, 80, 0.2) !important;
|
||||
}
|
||||
|
||||
/* 图表容器 */
|
||||
.chart-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 响应式网格 */
|
||||
.grid-container {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.grid-col-1 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.grid-col-2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.grid-col-3 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.grid-col-4 {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
/* 装饰元素 */
|
||||
.decoration-line {
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, transparent, var(--primary-color), transparent);
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.decoration-corner {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.decoration-corner.tl {
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.decoration-corner.tr {
|
||||
top: 0;
|
||||
right: 0;
|
||||
border-left: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.decoration-corner.bl {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.decoration-corner.br {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
/* 媒体查询 */
|
||||
@media (max-width: 1200px) {
|
||||
.grid-col-4 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.grid-col-2,
|
||||
.grid-col-3,
|
||||
.grid-col-4 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
@@ -1,121 +1,125 @@
|
||||
<template>
|
||||
<div class="dashboard">
|
||||
<div class="dashboard-container">
|
||||
<!-- 头部标题区域 -->
|
||||
<div class="dashboard-header">
|
||||
<div class="header-decoration left"></div>
|
||||
<div class="header-title">
|
||||
<h1>锡林郭勒盟安格斯牛养殖产业数据大屏</h1>
|
||||
<header class="dashboard-header">
|
||||
<div class="header-decoration"></div>
|
||||
<div class="header-title">
|
||||
<h1>锡林郭勒盟智慧养殖产业数据大屏</h1>
|
||||
</div>
|
||||
<div class="header-info">
|
||||
<p>{{ currentTime }}</p>
|
||||
</div>
|
||||
<div class="header-decoration"></div>
|
||||
</header>
|
||||
|
||||
<main class="dashboard-main">
|
||||
<!-- 左侧区域 -->
|
||||
<div class="left-section">
|
||||
<!-- 关键指标 -->
|
||||
<div class="metric-cards">
|
||||
<div class="metric-card" v-for="(metric, index) in keyMetrics" :key="index">
|
||||
<div class="metric-border card">
|
||||
<div class="metric-content">
|
||||
<div class="metric-title">{{ metric.title }}</div>
|
||||
<div class="metric-value">{{ metric.value }}</div>
|
||||
<div class="metric-change" :class="{ positive: metric.change > 0, negative: metric.change < 0 }">
|
||||
{{ metric.change > 0 ? '↑' : '↓' }} {{ Math.abs(metric.change) }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-decoration right"></div>
|
||||
|
||||
<div class="header-info">
|
||||
<span>实时数据更新时间: {{ currentTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="dashboard-main">
|
||||
<!-- 左侧区域 -->
|
||||
<div class="left-section">
|
||||
<!-- 关键指标 -->
|
||||
<div class="metric-cards">
|
||||
<div class="metric-card" v-for="(metric, index) in keyMetrics" :key="index">
|
||||
<div class="metric-border">
|
||||
<div class="metric-content">
|
||||
<div class="metric-title">{{ metric.title }}</div>
|
||||
<div class="metric-value">{{ metric.value }}</div>
|
||||
<div class="metric-change" :class="metric.change > 0 ? 'positive' : 'negative'">
|
||||
{{ metric.change > 0 ? '↑' : '↓' }} {{ Math.abs(metric.change) }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 区域分布图表 -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-border">
|
||||
<div class="chart-title">区域养殖分布</div>
|
||||
<div ref="regionChart" class="chart-wrapper"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中间区域 -->
|
||||
<div class="center-section">
|
||||
<!-- 核心数据 -->
|
||||
<div class="center-top">
|
||||
<div class="center-border">
|
||||
<div class="center-content">
|
||||
<div class="total-count">
|
||||
<div class="count-title">养殖总数</div>
|
||||
<div class="count-value">12,860</div>
|
||||
<div class="count-unit">头</div>
|
||||
</div>
|
||||
<div class="growth-rate">
|
||||
<div class="rate-title">同比增长</div>
|
||||
<div class="rate-value positive">+5.2%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中心图表 -->
|
||||
<div class="center-middle">
|
||||
<div class="center-chart-border">
|
||||
<div class="chart-title">养殖规模趋势</div>
|
||||
<div ref="breedingChart" class="chart-wrapper"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 风险预警 -->
|
||||
<div class="center-bottom">
|
||||
<div class="center-border">
|
||||
<div class="risk-content">
|
||||
<div class="risk-title">风险预警</div>
|
||||
<div class="risk-list">
|
||||
<div class="risk-item" v-for="(risk, index) in riskData" :key="index">
|
||||
<div class="risk-time">{{ risk.time }}</div>
|
||||
<div class="risk-type">{{ risk.type }}</div>
|
||||
<div class="risk-desc">{{ risk.desc }}</div>
|
||||
<div class="risk-status" :class="risk.status">{{ risk.status }}</div>
|
||||
</div>
|
||||
<!-- 风险预警 -->
|
||||
<div class="risk-card">
|
||||
<div class="chart-border card">
|
||||
<h3 class="chart-title">风险预警</h3>
|
||||
<div class="risk-content">
|
||||
<div class="risk-list">
|
||||
<div
|
||||
class="risk-item"
|
||||
v-for="(risk, index) in riskData"
|
||||
:key="index"
|
||||
>
|
||||
<div class="risk-time">{{ risk.time }}</div>
|
||||
<div class="risk-type">{{ risk.type }}</div>
|
||||
<div class="risk-desc">{{ risk.desc }}</div>
|
||||
<div class="risk-status" :class="risk.status">{{ risk.status }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧区域 -->
|
||||
<div class="right-section">
|
||||
<!-- 交易数据 -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-border">
|
||||
<div class="chart-title">交易数据分析</div>
|
||||
<div ref="transactionChart" class="chart-wrapper"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 雷达图 -->
|
||||
<div class="chart-container">
|
||||
<div class="chart-border">
|
||||
<div class="chart-title">综合风险评估</div>
|
||||
<div ref="riskRadarChart" class="chart-wrapper"></div>
|
||||
</div>
|
||||
|
||||
<!-- 中间区域 -->
|
||||
<div class="center-section">
|
||||
<!-- 产业概览 -->
|
||||
<div class="center-top">
|
||||
<div class="center-border card">
|
||||
<h2>产业概览</h2>
|
||||
<div class="center-content">
|
||||
<div class="total-count">
|
||||
<div class="count-title">牛只总数</div>
|
||||
<div class="count-value">128,456</div>
|
||||
<div class="count-unit">头</div>
|
||||
</div>
|
||||
<div class="total-count">
|
||||
<div class="count-title">牧场数量</div>
|
||||
<div class="count-value">1,245</div>
|
||||
<div class="count-unit">个</div>
|
||||
</div>
|
||||
<div class="growth-rate">
|
||||
<div class="rate-title">同比增长</div>
|
||||
<div class="rate-value positive">5.2%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部信息 -->
|
||||
<div class="dashboard-footer">
|
||||
<div class="footer-decoration left"></div>
|
||||
<div class="footer-content">
|
||||
<p>锡林郭勒盟安格斯牛养殖产业数字化管理平台</p>
|
||||
|
||||
<!-- 月度产值趋势 -->
|
||||
<div class="center-middle">
|
||||
<div class="center-chart-border card">
|
||||
<h3 class="chart-title">月度产值趋势</h3>
|
||||
<div ref="breedingChart" class="chart-wrapper"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 区域分布 -->
|
||||
<div class="center-bottom">
|
||||
<div class="center-chart-border card">
|
||||
<h3 class="chart-title">区域分布</h3>
|
||||
<div ref="regionChart" class="chart-wrapper"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-decoration right"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧区域 -->
|
||||
<div class="right-section">
|
||||
<!-- 交易数据 -->
|
||||
<div class="transaction-card">
|
||||
<div class="chart-border card">
|
||||
<h3 class="chart-title">交易数据</h3>
|
||||
<div ref="transactionChart" class="chart-wrapper"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 风险雷达 -->
|
||||
<div class="radar-card">
|
||||
<div class="chart-border card">
|
||||
<h3 class="chart-title">风险雷达</h3>
|
||||
<div ref="riskRadarChart" class="chart-wrapper"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="dashboard-footer">
|
||||
<div class="footer-decoration"></div>
|
||||
<div class="footer-content">
|
||||
<p>锡林郭勒盟智慧养殖产业数字化管理平台</p>
|
||||
</div>
|
||||
<div class="footer-decoration"></div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -156,6 +160,12 @@ export default {
|
||||
|
||||
// 初始化图表
|
||||
const initCharts = () => {
|
||||
// 确保 DOM 元素已正确绑定
|
||||
if (!breedingChart.value || !regionChart.value) {
|
||||
console.error('ECharts 初始化失败: DOM 元素未正确绑定')
|
||||
return
|
||||
}
|
||||
|
||||
// 养殖规模趋势图
|
||||
breedingChartInstance = echarts.init(breedingChart.value)
|
||||
breedingChartInstance.setOption({
|
||||
@@ -176,28 +186,30 @@ export default {
|
||||
})
|
||||
|
||||
// 交易数据分析图
|
||||
transactionChartInstance = echarts.init(transactionChart.value)
|
||||
transactionChartInstance.setOption({
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { bottom: '0' },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
data: [
|
||||
{ value: 335, name: '线上交易' },
|
||||
{ value: 310, name: '线下交易' },
|
||||
{ value: 234, name: '跨区域交易' },
|
||||
{ value: 135, name: '本地交易' }
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
if (transactionChart.value) {
|
||||
transactionChartInstance = echarts.init(transactionChart.value)
|
||||
transactionChartInstance.setOption({
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { bottom: '0' },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
data: [
|
||||
{ value: 335, name: '线上交易' },
|
||||
{ value: 310, name: '线下交易' },
|
||||
{ value: 234, name: '跨区域交易' },
|
||||
{ value: 135, name: '本地交易' }
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
// 区域分布图
|
||||
regionChartInstance = echarts.init(regionChart.value)
|
||||
@@ -216,29 +228,31 @@ export default {
|
||||
})
|
||||
|
||||
// 风险雷达图
|
||||
riskRadarChartInstance = echarts.init(riskRadarChart.value)
|
||||
riskRadarChartInstance.setOption({
|
||||
title: { text: '' },
|
||||
tooltip: { trigger: 'item' },
|
||||
radar: {
|
||||
indicator: [
|
||||
{ name: '健康问题', max: 100 },
|
||||
{ name: '交易风险', max: 100 },
|
||||
{ name: '环境异常', max: 100 },
|
||||
{ name: '运输风险', max: 100 },
|
||||
{ name: '市场波动', max: 100 }
|
||||
]
|
||||
},
|
||||
series: [{
|
||||
type: 'radar',
|
||||
data: [{
|
||||
value: [60, 30, 50, 20, 40],
|
||||
name: '风险指数',
|
||||
itemStyle: { color: '#FF9800' },
|
||||
areaStyle: { color: 'rgba(255, 152, 0, 0.3)' }
|
||||
if (riskRadarChart.value) {
|
||||
riskRadarChartInstance = echarts.init(riskRadarChart.value)
|
||||
riskRadarChartInstance.setOption({
|
||||
title: { text: '' },
|
||||
tooltip: { trigger: 'item' },
|
||||
radar: {
|
||||
indicator: [
|
||||
{ name: '健康问题', max: 100 },
|
||||
{ name: '交易风险', max: 100 },
|
||||
{ name: '环境异常', max: 100 },
|
||||
{ name: '运输风险', max: 100 },
|
||||
{ name: '市场波动', max: 100 }
|
||||
]
|
||||
},
|
||||
series: [{
|
||||
type: 'radar',
|
||||
data: [{
|
||||
value: [60, 30, 50, 20, 40],
|
||||
name: '风险指数',
|
||||
itemStyle: { color: '#FF9800' },
|
||||
areaStyle: { color: 'rgba(255, 152, 0, 0.3)' }
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 更新时间
|
||||
@@ -289,11 +303,6 @@ export default {
|
||||
background: linear-gradient(135deg, #0f2027, #20555d, #2c5364);
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dashboard-container {
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -305,8 +314,9 @@ export default {
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--border-radius-lg);
|
||||
margin-bottom: 20px;
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
|
||||
.header-decoration {
|
||||
@@ -379,10 +389,6 @@ export default {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 15px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-radius: 8px;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.metric-content {
|
||||
@@ -395,7 +401,7 @@ export default {
|
||||
|
||||
.metric-title {
|
||||
font-size: 16px;
|
||||
color: #ccc;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@@ -403,7 +409,7 @@ export default {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
color: #4CAF50;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.metric-change {
|
||||
@@ -411,25 +417,11 @@ export default {
|
||||
}
|
||||
|
||||
.metric-change.positive {
|
||||
color: #4CAF50;
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.metric-change.negative {
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chart-border {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-radius: 8px;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
.center-top {
|
||||
@@ -448,10 +440,6 @@ export default {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-radius: 8px;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.center-content {
|
||||
@@ -467,19 +455,19 @@ export default {
|
||||
|
||||
.count-title {
|
||||
font-size: 18px;
|
||||
color: #ccc;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.count-value {
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
color: #4CAF50;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.count-unit {
|
||||
font-size: 16px;
|
||||
color: #ccc;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.growth-rate {
|
||||
@@ -488,7 +476,7 @@ export default {
|
||||
|
||||
.rate-title {
|
||||
font-size: 18px;
|
||||
color: #ccc;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@@ -498,7 +486,7 @@ export default {
|
||||
}
|
||||
|
||||
.rate-value.positive {
|
||||
color: #4CAF50;
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.risk-content {
|
||||
@@ -529,7 +517,7 @@ export default {
|
||||
.risk-time {
|
||||
width: 15%;
|
||||
font-size: 14px;
|
||||
color: #ccc;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.risk-type {
|
||||
@@ -548,17 +536,17 @@ export default {
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--border-radius-sm);
|
||||
}
|
||||
|
||||
.risk-status.处理中 {
|
||||
background: rgba(255, 152, 0, 0.2);
|
||||
color: #FF9800;
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.risk-status.已处理 {
|
||||
background: rgba(76, 175, 80, 0.2);
|
||||
color: #4CAF50;
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
@@ -579,7 +567,8 @@ export default {
|
||||
padding: 0 20px;
|
||||
margin-top: 20px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--border-radius-lg);
|
||||
box-shadow: var(--box-shadow);
|
||||
}
|
||||
|
||||
.footer-decoration {
|
||||
@@ -600,6 +589,55 @@ export default {
|
||||
.footer-content {
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
color: #4CAF50;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1200px) {
|
||||
.dashboard-main {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.left-section, .center-section, .right-section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.metric-cards {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.dashboard {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.dashboard-header,
|
||||
.dashboard-footer {
|
||||
height: 80px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.header-title h1 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.metric-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.risk-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.risk-time,
|
||||
.risk-type,
|
||||
.risk-desc,
|
||||
.risk-status {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
438
frontend/dashboard/src/views/Eco.vue
Normal file
438
frontend/dashboard/src/views/Eco.vue
Normal file
@@ -0,0 +1,438 @@
|
||||
<template>
|
||||
<div class="eco-container">
|
||||
<h1>生态指标</h1>
|
||||
<div v-if="loading" class="loading-indicator">数据加载中...</div>
|
||||
<div v-if="error" class="error-message">数据加载失败,请稍后重试。</div>
|
||||
<div v-if="!loading && !error" class="eco-content">
|
||||
<!-- 环保数据展示 -->
|
||||
<div class="environment-section">
|
||||
<h3>环保数据</h3>
|
||||
<div class="eco-grid">
|
||||
<div class="eco-card" v-for="(item, index) in ecoData" :key="index">
|
||||
<div class="eco-icon">{{ item.icon }}</div>
|
||||
<div class="eco-info">
|
||||
<div class="eco-title">{{ item.title }}</div>
|
||||
<div class="eco-value">{{ item.value }}</div>
|
||||
<div class="eco-change" :class="item.change > 0 ? 'positive' : 'negative'">
|
||||
{{ item.change > 0 ? '↑' : '↓' }} {{ Math.abs(item.change) }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 可持续发展指标 -->
|
||||
<div class="sustainability-section">
|
||||
<h3>可持续发展指标</h3>
|
||||
<div class="sustainability-content">
|
||||
<div class="balance-chart">
|
||||
<h4>草畜平衡</h4>
|
||||
<div ref="balanceChart" class="chart-placeholder" style="width: 100%; height: 300px;"></div>
|
||||
</div>
|
||||
<div class="benefit-chart">
|
||||
<h4>生态效益</h4>
|
||||
<div ref="benefitChart" class="chart-placeholder" style="width: 100%; height: 300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 环保趋势分析 -->
|
||||
<div class="trend-section">
|
||||
<h3>环保趋势分析</h3>
|
||||
<div class="chart-container">
|
||||
<div ref="trendChart" class="chart-placeholder" style="width: 100%; height: 300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 生态效益评估 -->
|
||||
<div class="assessment-section">
|
||||
<h3>生态效益评估</h3>
|
||||
<div class="assessment-content">
|
||||
<div class="economic-benefit">
|
||||
<h4>经济效益</h4>
|
||||
<div class="benefit-value">¥28.6亿</div>
|
||||
<div class="benefit-desc">年度总产值</div>
|
||||
</div>
|
||||
<div class="ecological-benefit">
|
||||
<h4>生态效益</h4>
|
||||
<div class="benefit-value">+12.3%</div>
|
||||
<div class="benefit-desc">生态改善率</div>
|
||||
</div>
|
||||
<div class="balance-indicator">
|
||||
<h4>效益平衡</h4>
|
||||
<div class="balance-chart-container">
|
||||
<div ref="balanceIndicator" class="chart-placeholder" style="width: 100%; height: 200px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'Eco',
|
||||
setup() {
|
||||
const ecoData = ref([]);
|
||||
const loading = ref(true);
|
||||
const error = ref(false);
|
||||
const balanceChart = ref(null);
|
||||
const benefitChart = ref(null);
|
||||
const trendChart = ref(null);
|
||||
const balanceIndicator = ref(null);
|
||||
|
||||
let balanceChartInstance = null;
|
||||
let benefitChartInstance = null;
|
||||
let trendChartInstance = null;
|
||||
let indicatorInstance = null;
|
||||
|
||||
// 环保数据
|
||||
ecoData.value = [
|
||||
{ icon: '🌱', title: '碳排放', value: '12,500吨', change: -5.2 },
|
||||
{ icon: '💧', title: '水资源使用', value: '850,000吨', change: -3.1 },
|
||||
{ icon: '🌾', title: '饲料消耗', value: '2,300吨', change: -2.8 },
|
||||
{ icon: '🏞️', title: '草地覆盖率', value: '86.5%', change: 1.2 }
|
||||
];
|
||||
|
||||
// 初始化草畜平衡图表
|
||||
const initBalanceChart = () => {
|
||||
if (balanceChart.value) {
|
||||
balanceChartInstance = echarts.init(balanceChart.value);
|
||||
balanceChartInstance.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
bottom: '0'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
data: [
|
||||
{ value: 65, name: '草场承载力' },
|
||||
{ value: 35, name: '牲畜需求量' }
|
||||
],
|
||||
itemStyle: {
|
||||
color: function(params) {
|
||||
const colorList = ['#4CAF50', '#2196F3'];
|
||||
return colorList[params.dataIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化生态效益图表
|
||||
const initBenefitChart = () => {
|
||||
if (benefitChart.value) {
|
||||
benefitChartInstance = echarts.init(benefitChart.value);
|
||||
benefitChartInstance.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['2018', '2019', '2020', '2021', '2022', '2023']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [65, 70, 75, 78, 82, 86],
|
||||
type: 'bar',
|
||||
itemStyle: { color: '#4CAF50' }
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化趋势分析图表
|
||||
const initTrendChart = () => {
|
||||
if (trendChart.value) {
|
||||
trendChartInstance = echarts.init(trendChart.value);
|
||||
trendChartInstance.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['碳排放', '水资源使用', '饲料消耗']
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '碳排放',
|
||||
type: 'line',
|
||||
data: [2200, 2100, 2000, 1950, 1850, 1750],
|
||||
itemStyle: { color: '#f44336' }
|
||||
},
|
||||
{
|
||||
name: '水资源使用',
|
||||
type: 'line',
|
||||
data: [150000, 145000, 140000, 135000, 130000, 125000],
|
||||
itemStyle: { color: '#2196F3' }
|
||||
},
|
||||
{
|
||||
name: '饲料消耗',
|
||||
type: 'line',
|
||||
data: [4200, 4100, 4000, 3900, 3800, 3700],
|
||||
itemStyle: { color: '#FF9800' }
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化效益平衡指示器
|
||||
const initBalanceIndicator = () => {
|
||||
if (balanceIndicator.value) {
|
||||
indicatorInstance = echarts.init(balanceIndicator.value);
|
||||
indicatorInstance.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'gauge',
|
||||
progress: {
|
||||
show: true
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: [
|
||||
[0.3, '#f44336'],
|
||||
[0.7, '#FF9800'],
|
||||
[1, '#4CAF50']
|
||||
],
|
||||
width: 15
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
valueAnimation: true,
|
||||
formatter: '{value}',
|
||||
color: 'auto'
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: 85,
|
||||
name: '生态效益指数'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 窗口大小改变时重绘图表
|
||||
const resizeCharts = () => {
|
||||
if (balanceChartInstance) balanceChartInstance.resize();
|
||||
if (benefitChartInstance) benefitChartInstance.resize();
|
||||
if (trendChartInstance) trendChartInstance.resize();
|
||||
if (indicatorInstance) indicatorInstance.resize();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loading.value = false;
|
||||
initBalanceChart();
|
||||
initBenefitChart();
|
||||
initTrendChart();
|
||||
initBalanceIndicator();
|
||||
window.addEventListener('resize', resizeCharts);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resizeCharts);
|
||||
if (balanceChartInstance) balanceChartInstance.dispose();
|
||||
if (benefitChartInstance) benefitChartInstance.dispose();
|
||||
if (trendChartInstance) trendChartInstance.dispose();
|
||||
if (indicatorInstance) indicatorInstance.dispose();
|
||||
});
|
||||
|
||||
return {
|
||||
ecoData,
|
||||
loading,
|
||||
error,
|
||||
balanceChart,
|
||||
benefitChart,
|
||||
trendChart,
|
||||
balanceIndicator
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.eco-container {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.eco-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.environment-section,
|
||||
.sustainability-section,
|
||||
.trend-section,
|
||||
.assessment-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.environment-section h3,
|
||||
.sustainability-section h3,
|
||||
.trend-section h3,
|
||||
.assessment-section h3 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.eco-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.eco-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.eco-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.eco-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.eco-title {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.eco-value {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #4CAF50;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.eco-change {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.eco-change.positive {
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.eco-change.negative {
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.sustainability-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.balance-chart h4,
|
||||
.benefit-chart h4 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.assessment-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.economic-benefit,
|
||||
.ecological-benefit,
|
||||
.balance-indicator {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.economic-benefit h4,
|
||||
.ecological-benefit h4,
|
||||
.balance-indicator h4 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.benefit-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #4CAF50;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.benefit-desc {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.loading-indicator,
|
||||
.error-message {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.eco-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.eco-grid,
|
||||
.sustainability-content,
|
||||
.assessment-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
130
frontend/dashboard/src/views/Finance.vue
Normal file
130
frontend/dashboard/src/views/Finance.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import axios from 'axios';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const loanData = ref([]);
|
||||
const insuranceData = ref([]);
|
||||
const loading = ref(true);
|
||||
const error = ref(false);
|
||||
|
||||
const fetchData = async () => {
|
||||
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);
|
||||
} finally {
|
||||
loading.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 insuranceChart = echarts.init(document.getElementById('insurance-chart'));
|
||||
insuranceChart.setOption({
|
||||
tooltip: { trigger: 'item' },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
data: insuranceData.value.map(item => item)
|
||||
}]
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchData();
|
||||
});
|
||||
|
||||
return {
|
||||
loanData,
|
||||
insuranceData,
|
||||
loading,
|
||||
error
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.finance-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.loan-section,
|
||||
.insurance-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.loading-indicator,
|
||||
.error-message {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.loan-section,
|
||||
.insurance-section {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#loan-chart,
|
||||
#insurance-chart {
|
||||
height: 250px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
412
frontend/dashboard/src/views/Gov.vue
Normal file
412
frontend/dashboard/src/views/Gov.vue
Normal file
@@ -0,0 +1,412 @@
|
||||
<template>
|
||||
<div class="gov-container">
|
||||
<h1>政府监管</h1>
|
||||
<div v-if="loading" class="loading-indicator">数据加载中...</div>
|
||||
<div v-if="error" class="error-message">数据加载失败,请稍后重试。</div>
|
||||
<div v-if="!loading && !error" class="gov-content">
|
||||
<!-- 监管数据总览 -->
|
||||
<div class="overview-section">
|
||||
<h3>监管数据总览</h3>
|
||||
<div class="overview-grid">
|
||||
<div class="overview-card" v-for="(item, index) in overviewData" :key="index">
|
||||
<div class="card-icon">{{ item.icon }}</div>
|
||||
<div class="card-info">
|
||||
<div class="card-title">{{ item.title }}</div>
|
||||
<div class="card-value">{{ item.value }}</div>
|
||||
<div class="card-desc">{{ item.desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 合规性检查结果 -->
|
||||
<div class="compliance-section">
|
||||
<h3>合规性检查结果</h3>
|
||||
<div class="compliance-content">
|
||||
<div class="compliance-chart">
|
||||
<h4>合规牧场比例</h4>
|
||||
<div ref="complianceChart" class="chart-placeholder" style="width: 100%; height: 300px;"></div>
|
||||
</div>
|
||||
<div class="violations-table">
|
||||
<h4>违规事件统计</h4>
|
||||
<a-table :dataSource="violationsData" :columns="violationsColumns" :pagination="false" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 政策执行效果分析 -->
|
||||
<div class="policy-section">
|
||||
<h3>政策执行效果分析</h3>
|
||||
<div class="policy-content">
|
||||
<div class="policy-chart">
|
||||
<div ref="policyChart" class="chart-placeholder" style="width: 100%; height: 300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 监管报告 -->
|
||||
<div class="report-section">
|
||||
<h3>监管报告</h3>
|
||||
<div class="report-content">
|
||||
<div class="report-summary">
|
||||
<h4>月度监管报告摘要</h4>
|
||||
<div class="summary-content">
|
||||
<p>本月共检查牧场120家,合规率92.5%,较上月提升1.2%。</p>
|
||||
<p>发现违规事件9起,已全部处理完毕。</p>
|
||||
<p>发放补贴资金¥280万元,惠及养殖户85户。</p>
|
||||
</div>
|
||||
<div class="report-actions">
|
||||
<button class="export-btn">导出PDF报告</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="report-chart">
|
||||
<h4>监管报告可视化</h4>
|
||||
<div ref="reportChart" class="chart-placeholder" style="width: 100%; height: 200px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'Gov',
|
||||
setup() {
|
||||
const overviewData = ref([]);
|
||||
const violationsData = ref([]);
|
||||
const loading = ref(true);
|
||||
const error = ref(false);
|
||||
const complianceChart = ref(null);
|
||||
const policyChart = ref(null);
|
||||
const reportChart = ref(null);
|
||||
|
||||
let complianceChartInstance = null;
|
||||
let policyChartInstance = null;
|
||||
let reportChartInstance = null;
|
||||
|
||||
// 监管数据总览
|
||||
overviewData.value = [
|
||||
{ icon: '✅', title: '防疫完成率', value: '98.6%', desc: '较上月 +2.1%' },
|
||||
{ icon: '💰', title: '补贴发放', value: '¥280万', desc: '惠及85户养殖户' },
|
||||
{ icon: '📋', title: '检查牧场', value: '120家', desc: '合规率92.5%' },
|
||||
{ icon: '⚠️', title: '违规事件', value: '9起', desc: '已全部处理' }
|
||||
];
|
||||
|
||||
// 违规事件表格列定义
|
||||
const violationsColumns = ref([
|
||||
{ title: '事件编号', dataIndex: 'id', key: 'id' },
|
||||
{ title: '牧场名称', dataIndex: 'farm', key: 'farm' },
|
||||
{ title: '违规类型', dataIndex: 'type', key: 'type' },
|
||||
{ title: '发生时间', dataIndex: 'time', key: 'time' },
|
||||
{ title: '处理状态', dataIndex: 'status', key: 'status' },
|
||||
]);
|
||||
|
||||
// 违规事件数据
|
||||
violationsData.value = [
|
||||
{ key: '1', id: 'V202308001', farm: '锡市牧场A', type: '环境不达标', time: '2023-08-15', status: '已处理' },
|
||||
{ key: '2', id: 'V202308002', farm: '东乌旗牧场B', type: '防疫记录不全', time: '2023-08-18', status: '处理中' },
|
||||
{ key: '3', id: 'V202308003', farm: '西乌旗牧场C', type: '饲料来源不明', time: '2023-08-20', status: '已处理' },
|
||||
];
|
||||
|
||||
// 初始化合规性检查图表
|
||||
const initComplianceChart = () => {
|
||||
if (complianceChart.value) {
|
||||
complianceChartInstance = echarts.init(complianceChart.value);
|
||||
complianceChartInstance.setOption({
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
legend: {
|
||||
bottom: '0'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
data: [
|
||||
{ value: 92.5, name: '合规牧场' },
|
||||
{ value: 7.5, name: '违规牧场' }
|
||||
],
|
||||
itemStyle: {
|
||||
color: function(params) {
|
||||
const colorList = ['#4CAF50', '#f44336'];
|
||||
return colorList[params.dataIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化政策执行效果图表
|
||||
const initPolicyChart = () => {
|
||||
if (policyChart.value) {
|
||||
policyChartInstance = echarts.init(policyChart.value);
|
||||
policyChartInstance.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['防疫政策', '补贴政策', '环保政策']
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '防疫政策',
|
||||
type: 'line',
|
||||
data: [85, 87, 89, 91, 93, 94],
|
||||
itemStyle: { color: '#4CAF50' }
|
||||
},
|
||||
{
|
||||
name: '补贴政策',
|
||||
type: 'line',
|
||||
data: [78, 80, 82, 85, 87, 88],
|
||||
itemStyle: { color: '#2196F3' }
|
||||
},
|
||||
{
|
||||
name: '环保政策',
|
||||
type: 'line',
|
||||
data: [70, 72, 75, 78, 80, 82],
|
||||
itemStyle: { color: '#FF9800' }
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化监管报告图表
|
||||
const initReportChart = () => {
|
||||
if (reportChart.value) {
|
||||
reportChartInstance = echarts.init(reportChart.value);
|
||||
reportChartInstance.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['检查数', '合规数', '违规数', '处理数']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [120, 111, 9, 9],
|
||||
type: 'bar',
|
||||
itemStyle: {
|
||||
color: function(params) {
|
||||
const colorList = ['#2196F3', '#4CAF50', '#f44336', '#FF9800'];
|
||||
return colorList[params.dataIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 窗口大小改变时重绘图表
|
||||
const resizeCharts = () => {
|
||||
if (complianceChartInstance) complianceChartInstance.resize();
|
||||
if (policyChartInstance) policyChartInstance.resize();
|
||||
if (reportChartInstance) reportChartInstance.resize();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loading.value = false;
|
||||
initComplianceChart();
|
||||
initPolicyChart();
|
||||
initReportChart();
|
||||
window.addEventListener('resize', resizeCharts);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resizeCharts);
|
||||
if (complianceChartInstance) complianceChartInstance.dispose();
|
||||
if (policyChartInstance) policyChartInstance.dispose();
|
||||
if (reportChartInstance) reportChartInstance.dispose();
|
||||
});
|
||||
|
||||
return {
|
||||
overviewData,
|
||||
violationsData,
|
||||
violationsColumns,
|
||||
loading,
|
||||
error,
|
||||
complianceChart,
|
||||
policyChart,
|
||||
reportChart
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.gov-container {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.gov-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.overview-section,
|
||||
.compliance-section,
|
||||
.policy-section,
|
||||
.report-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.overview-section h3,
|
||||
.compliance-section h3,
|
||||
.policy-section h3,
|
||||
.report-section h3 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.overview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.overview-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.card-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #4CAF50;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.card-desc {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.compliance-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.compliance-chart h4,
|
||||
.violations-table h4 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.policy-chart {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.report-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.report-summary {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.report-summary h4 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.summary-content p {
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.report-actions {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.export-btn {
|
||||
padding: 10px 20px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.export-btn:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.report-chart h4 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.loading-indicator,
|
||||
.error-message {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.gov-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.overview-grid,
|
||||
.compliance-content,
|
||||
.report-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
104
frontend/dashboard/src/views/Government.vue
Normal file
104
frontend/dashboard/src/views/Government.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
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 fetchData = async () => {
|
||||
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);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchData();
|
||||
});
|
||||
|
||||
return {
|
||||
policies,
|
||||
governmentData,
|
||||
columns,
|
||||
loading,
|
||||
error
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.government-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.policy-section,
|
||||
.data-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.loading-indicator,
|
||||
.error-message {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.policy-section ul,
|
||||
.data-section {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.data-section .ant-table {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
135
frontend/dashboard/src/views/Monitor.vue
Normal file
135
frontend/dashboard/src/views/Monitor.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div class="monitor-container">
|
||||
<h1>养殖监控</h1>
|
||||
<DataLoader
|
||||
:loading="loading"
|
||||
:error="error"
|
||||
:empty="!environmentData.length"
|
||||
empty-message="暂无环境数据"
|
||||
>
|
||||
<template #default>
|
||||
<div class="map-container">
|
||||
<!-- 3D地图组件 -->
|
||||
<ThreeDMap :height="mapHeight" :map-data="farmLocations" />
|
||||
</div>
|
||||
<div class="data-panel">
|
||||
<EnvironmentData :data="environmentData" />
|
||||
</div>
|
||||
</template>
|
||||
</DataLoader>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { fetchFarmData } from '@/services/dashboard';
|
||||
import ThreeDMap from '@/components/map/ThreeDMap.vue';
|
||||
import EnvironmentData from '@/components/data/EnvironmentData.vue';
|
||||
import DataLoader from '@/components/common/DataLoader.vue';
|
||||
|
||||
export default {
|
||||
name: 'Monitor',
|
||||
components: {
|
||||
ThreeDMap,
|
||||
EnvironmentData,
|
||||
DataLoader
|
||||
},
|
||||
setup() {
|
||||
// 响应式状态
|
||||
const environmentData = ref([]);
|
||||
const farmLocations = ref([]);
|
||||
const loading = ref(true);
|
||||
const error = ref(false);
|
||||
|
||||
// 根据屏幕尺寸计算地图高度
|
||||
const mapHeight = computed(() => {
|
||||
return window.innerWidth <= 768 ? 300 : 500;
|
||||
});
|
||||
|
||||
// 数据加载
|
||||
const loadData = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = false;
|
||||
|
||||
// 模拟获取农场位置数据
|
||||
farmLocations.value = [
|
||||
{ id: 1, name: '锡林浩特牧场A', position: [116.08, 43.95], status: '正常' },
|
||||
{ id: 2, name: '东乌旗牧场B', position: [116.95, 45.55], status: '正常' },
|
||||
{ id: 3, name: '西乌旗牧场C', position: [117.60, 44.60], status: '警告' },
|
||||
{ id: 4, name: '镶黄旗牧场D', position: [114.20, 42.25], status: '正常' }
|
||||
];
|
||||
|
||||
// 模拟获取环境数据
|
||||
environmentData.value = [
|
||||
{ id: 1, name: '温度', value: '22°C', status: '正常' },
|
||||
{ id: 2, name: '湿度', value: '65%', status: '正常' },
|
||||
{ id: 3, name: '氨气浓度', value: '8ppm', status: '警告' },
|
||||
{ id: 4, name: 'PM2.5', value: '35μg/m³', status: '正常' }
|
||||
];
|
||||
} catch (err) {
|
||||
error.value = true;
|
||||
console.error('获取数据失败:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
});
|
||||
|
||||
return {
|
||||
environmentData,
|
||||
loading,
|
||||
error,
|
||||
mapHeight
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.monitor-container {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.map-container {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.data-panel {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.loading-indicator,
|
||||
.error-message {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.monitor-container {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
```
|
||||
/src/components/map/FarmMap.vue
|
||||
```
|
||||
```
|
||||
47
frontend/dashboard/src/views/Overview.vue
Normal file
47
frontend/dashboard/src/views/Overview.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="overview-container">
|
||||
<h1>产业概览</h1>
|
||||
<div class="charts-container">
|
||||
<RealtimeChart :data="overviewData" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import RealtimeChart from '@/components/RealtimeChart.vue';
|
||||
import { fetchOverviewData } from '@/services/dashboard';
|
||||
|
||||
export default {
|
||||
name: 'Overview',
|
||||
components: { RealtimeChart },
|
||||
setup() {
|
||||
const overviewData = ref({});
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
overviewData.value = await fetchOverviewData();
|
||||
} catch (error) {
|
||||
console.error('获取产业概览数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
});
|
||||
|
||||
return {
|
||||
overviewData
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.overview-container {
|
||||
padding: 20px;
|
||||
}
|
||||
.charts-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
485
frontend/dashboard/src/views/Risk.vue
Normal file
485
frontend/dashboard/src/views/Risk.vue
Normal file
@@ -0,0 +1,485 @@
|
||||
<template>
|
||||
<div class="risk-container">
|
||||
<h1>风险预警</h1>
|
||||
<div v-if="loading" class="loading-indicator">数据加载中...</div>
|
||||
<div v-if="error" class="error-message">数据加载失败,请稍后重试。</div>
|
||||
<div v-if="!loading && !error" class="risk-content">
|
||||
<!-- 风险事件展示 -->
|
||||
<div class="events-section">
|
||||
<h3>风险事件</h3>
|
||||
<div class="events-grid">
|
||||
<div
|
||||
v-for="(event, index) in riskEvents"
|
||||
:key="index"
|
||||
class="event-card"
|
||||
:class="event.level"
|
||||
>
|
||||
<div class="event-header">
|
||||
<div class="event-icon">
|
||||
<span v-if="event.level === 'high'">🔴</span>
|
||||
<span v-else-if="event.level === 'medium'">🟠</span>
|
||||
<span v-else>🟡</span>
|
||||
</div>
|
||||
<div class="event-title">{{ event.title }}</div>
|
||||
<div class="event-time">{{ event.time }}</div>
|
||||
</div>
|
||||
<div class="event-body">
|
||||
<div class="event-desc">{{ event.desc }}</div>
|
||||
<div class="event-location">位置: {{ event.location }}</div>
|
||||
</div>
|
||||
<div class="event-footer">
|
||||
<div class="event-status">{{ event.status }}</div>
|
||||
<div class="event-type">{{ event.type }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预警信息推送 -->
|
||||
<div class="alerts-section">
|
||||
<h3>预警信息</h3>
|
||||
<div class="alerts-list">
|
||||
<div
|
||||
v-for="(alert, index) in riskAlerts"
|
||||
:key="index"
|
||||
class="alert-item"
|
||||
:class="alert.level"
|
||||
>
|
||||
<div class="alert-content">
|
||||
<div class="alert-title">{{ alert.title }}</div>
|
||||
<div class="alert-desc">{{ alert.desc }}</div>
|
||||
</div>
|
||||
<div class="alert-actions">
|
||||
<button class="subscribe-btn" @click="subscribeAlert(alert)">
|
||||
{{ alert.subscribed ? '已订阅' : '订阅通知' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 风险趋势分析 -->
|
||||
<div class="trend-section">
|
||||
<h3>风险趋势分析</h3>
|
||||
<div class="chart-container">
|
||||
<div ref="trendChart" class="chart-placeholder" style="width: 100%; height: 300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 风险地图 -->
|
||||
<div class="map-section">
|
||||
<h3>风险地图</h3>
|
||||
<div class="map-container">
|
||||
<div ref="riskMap" class="map-placeholder" style="width: 100%; height: 400px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'Risk',
|
||||
setup() {
|
||||
const riskEvents = ref([]);
|
||||
const riskAlerts = ref([]);
|
||||
const loading = ref(true);
|
||||
const error = ref(false);
|
||||
const trendChart = ref(null);
|
||||
const riskMap = ref(null);
|
||||
|
||||
let trendChartInstance = null;
|
||||
let mapInstance = null;
|
||||
|
||||
// 风险事件数据
|
||||
riskEvents.value = [
|
||||
{
|
||||
level: 'high',
|
||||
title: '疫病风险',
|
||||
time: '2023-08-20 15:30',
|
||||
desc: '某牧场发现疑似口蹄疫病例',
|
||||
location: '锡市牧场A',
|
||||
status: '处理中',
|
||||
type: '健康风险'
|
||||
},
|
||||
{
|
||||
level: 'medium',
|
||||
title: '市场风险',
|
||||
time: '2023-08-20 14:20',
|
||||
desc: '牛肉价格波动较大',
|
||||
location: '全盟范围',
|
||||
status: '监测中',
|
||||
type: '市场风险'
|
||||
},
|
||||
{
|
||||
level: 'low',
|
||||
title: '自然灾害风险',
|
||||
time: '2023-08-20 10:15',
|
||||
desc: '局部地区有暴雨预警',
|
||||
location: '东乌旗',
|
||||
status: '预警中',
|
||||
type: '自然灾害'
|
||||
},
|
||||
{
|
||||
level: 'high',
|
||||
title: '环境风险',
|
||||
time: '2023-08-20 09:45',
|
||||
desc: '某区域氨气浓度超标',
|
||||
location: '西乌旗牧场B',
|
||||
status: '处理中',
|
||||
type: '环境风险'
|
||||
}
|
||||
];
|
||||
|
||||
// 预警信息数据
|
||||
riskAlerts.value = [
|
||||
{
|
||||
level: 'high',
|
||||
title: '疫病预警',
|
||||
desc: '全盟范围内口蹄疫预警,需加强防疫措施',
|
||||
subscribed: true
|
||||
},
|
||||
{
|
||||
level: 'medium',
|
||||
title: '市场预警',
|
||||
desc: '牛肉价格波动预警,请关注市场动态',
|
||||
subscribed: false
|
||||
},
|
||||
{
|
||||
level: 'low',
|
||||
title: '天气预警',
|
||||
desc: '局部地区暴雨预警,请注意防范',
|
||||
subscribed: true
|
||||
}
|
||||
];
|
||||
|
||||
// 订阅预警通知
|
||||
const subscribeAlert = (alert) => {
|
||||
alert.subscribed = !alert.subscribed;
|
||||
// 这里可以添加实际的订阅逻辑
|
||||
};
|
||||
|
||||
// 初始化趋势分析图表
|
||||
const initTrendChart = () => {
|
||||
if (trendChart.value) {
|
||||
trendChartInstance = echarts.init(trendChart.value);
|
||||
trendChartInstance.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['疫病风险', '市场风险', '环境风险']
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '疫病风险',
|
||||
type: 'line',
|
||||
stack: '总量',
|
||||
data: [12, 15, 8, 10, 14, 9],
|
||||
itemStyle: { color: '#f44336' }
|
||||
},
|
||||
{
|
||||
name: '市场风险',
|
||||
type: 'line',
|
||||
stack: '总量',
|
||||
data: [8, 10, 12, 9, 11, 7],
|
||||
itemStyle: { color: '#ff9800' }
|
||||
},
|
||||
{
|
||||
name: '环境风险',
|
||||
type: 'line',
|
||||
stack: '总量',
|
||||
data: [5, 7, 6, 8, 9, 6],
|
||||
itemStyle: { color: '#4CAF50' }
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化风险地图
|
||||
const initRiskMap = () => {
|
||||
if (riskMap.value) {
|
||||
mapInstance = echarts.init(riskMap.value);
|
||||
mapInstance.setOption({
|
||||
title: {
|
||||
text: '风险分布图',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
geo: {
|
||||
map: 'china',
|
||||
roam: false,
|
||||
zoom: 1.2,
|
||||
center: [116.0, 44.0], // 锡林郭勒盟位置
|
||||
itemStyle: {
|
||||
areaColor: '#e7e8ea'
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'scatter',
|
||||
coordinateSystem: 'geo',
|
||||
symbolSize: 20,
|
||||
data: [
|
||||
{ name: '锡市牧场A', value: [116.08, 43.95], itemStyle: { color: '#f44336' } },
|
||||
{ name: '东乌旗', value: [116.95, 45.55], itemStyle: { color: '#ff9800' } },
|
||||
{ name: '西乌旗牧场B', value: [117.60, 44.60], itemStyle: { color: '#f44336' } },
|
||||
{ name: '镶黄旗', value: [114.20, 42.25], itemStyle: { color: '#4CAF50' } }
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 窗口大小改变时重绘图表
|
||||
const resizeCharts = () => {
|
||||
if (trendChartInstance) {
|
||||
trendChartInstance.resize();
|
||||
}
|
||||
if (mapInstance) {
|
||||
mapInstance.resize();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loading.value = false;
|
||||
initTrendChart();
|
||||
initRiskMap();
|
||||
window.addEventListener('resize', resizeCharts);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resizeCharts);
|
||||
if (trendChartInstance) {
|
||||
trendChartInstance.dispose();
|
||||
}
|
||||
if (mapInstance) {
|
||||
mapInstance.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
riskEvents,
|
||||
riskAlerts,
|
||||
loading,
|
||||
error,
|
||||
trendChart,
|
||||
riskMap,
|
||||
subscribeAlert
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.risk-container {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.risk-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.events-section,
|
||||
.alerts-section,
|
||||
.trend-section,
|
||||
.map-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.events-section h3,
|
||||
.alerts-section h3,
|
||||
.trend-section h3,
|
||||
.map-section h3 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.events-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.event-card {
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
border-left: 4px solid #ccc;
|
||||
}
|
||||
|
||||
.event-card.high {
|
||||
background-color: #ffebee;
|
||||
border-left-color: #f44336;
|
||||
}
|
||||
|
||||
.event-card.medium {
|
||||
background-color: #fff3e0;
|
||||
border-left-color: #ff9800;
|
||||
}
|
||||
|
||||
.event-card.low {
|
||||
background-color: #e8f5e9;
|
||||
border-left-color: #4CAF50;
|
||||
}
|
||||
|
||||
.event-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.event-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.event-title {
|
||||
font-weight: bold;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.event-time {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.event-body {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.event-desc {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.event-location {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.event-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.event-status {
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.alerts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.alert-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #ccc;
|
||||
}
|
||||
|
||||
.alert-item.high {
|
||||
background-color: #ffebee;
|
||||
border-left-color: #f44336;
|
||||
}
|
||||
|
||||
.alert-item.medium {
|
||||
background-color: #fff3e0;
|
||||
border-left-color: #ff9800;
|
||||
}
|
||||
|
||||
.alert-item.low {
|
||||
background-color: #e8f5e9;
|
||||
border-left-color: #4CAF50;
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.alert-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.alert-desc {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.alert-actions {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.subscribe-btn {
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.subscribe-btn:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.loading-indicator,
|
||||
.error-message {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.risk-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.events-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.alert-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.alert-actions {
|
||||
margin-left: 0;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
478
frontend/dashboard/src/views/Trade.vue
Normal file
478
frontend/dashboard/src/views/Trade.vue
Normal file
@@ -0,0 +1,478 @@
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'Trade',
|
||||
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);
|
||||
|
||||
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 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 resizeCharts = () => {
|
||||
if (volumeChartInstance) volumeChartInstance.resize();
|
||||
if (trendChartInstance) trendChartInstance.resize();
|
||||
if (distributionChartInstance) distributionChartInstance.resize();
|
||||
if (typeChartInstance) typeChartInstance.resize();
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
return {
|
||||
volumeData,
|
||||
farmRankingData,
|
||||
traderRankingData,
|
||||
farmRankingColumns,
|
||||
traderRankingColumns,
|
||||
loading,
|
||||
error,
|
||||
volumeChart,
|
||||
trendChart,
|
||||
distributionChart,
|
||||
typeChart
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.trade-container {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background: linear-gradient(135deg, #0f2027, #20555d, #2c5364);
|
||||
}
|
||||
|
||||
.trade-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.volume-section,
|
||||
.price-section,
|
||||
.type-section,
|
||||
.ranking-section {
|
||||
box-shadow: var(--box-shadow);
|
||||
border-radius: var(--border-radius-lg);
|
||||
}
|
||||
|
||||
.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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
469
frontend/dashboard/src/views/Transport.vue
Normal file
469
frontend/dashboard/src/views/Transport.vue
Normal file
@@ -0,0 +1,469 @@
|
||||
<template>
|
||||
<div class="transport-container">
|
||||
<h1>运输跟踪</h1>
|
||||
<div v-if="loading" class="loading-indicator">数据加载中...</div>
|
||||
<div v-if="error" class="error-message">数据加载失败,请稍后重试。</div>
|
||||
<div v-if="!loading && !error" class="transport-content">
|
||||
<!-- 运输路线地图 -->
|
||||
<div class="map-section">
|
||||
<h3>运输路线</h3>
|
||||
<div class="map-container">
|
||||
<div ref="transportMap" class="map-placeholder" style="width: 100%; height: 400px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 运输车辆列表 -->
|
||||
<div class="vehicles-section">
|
||||
<h3>运输车辆</h3>
|
||||
<div class="vehicles-list">
|
||||
<a-table :dataSource="vehiclesData" :columns="vehicleColumns" :pagination="false" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 运输异常告警 -->
|
||||
<div class="alerts-section">
|
||||
<h3>运输异常告警</h3>
|
||||
<div class="alerts-list">
|
||||
<div
|
||||
v-for="(alert, index) in transportAlerts"
|
||||
:key="index"
|
||||
class="alert-item"
|
||||
:class="alert.level"
|
||||
>
|
||||
<div class="alert-icon">
|
||||
<span v-if="alert.level === 'high'">⚠️</span>
|
||||
<span v-else-if="alert.level === 'medium'">⚠️</span>
|
||||
<span v-else>ℹ️</span>
|
||||
</div>
|
||||
<div class="alert-content">
|
||||
<div class="alert-title">{{ alert.title }}</div>
|
||||
<div class="alert-desc">{{ alert.desc }}</div>
|
||||
<div class="alert-time">{{ alert.time }}</div>
|
||||
</div>
|
||||
<div class="alert-status">{{ alert.status }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 运输统计 -->
|
||||
<div class="stats-section">
|
||||
<h3>运输统计</h3>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card" v-for="(stat, index) in transportStats" :key="index">
|
||||
<div class="stat-title">{{ stat.title }}</div>
|
||||
<div class="stat-value">{{ stat.value }}</div>
|
||||
<div class="stat-desc">{{ stat.desc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 运输效率分析 -->
|
||||
<div class="efficiency-section">
|
||||
<h3>运输效率分析</h3>
|
||||
<div class="chart-container">
|
||||
<div ref="efficiencyChart" class="chart-placeholder" style="width: 100%; height: 300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import axios from 'axios';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
export default {
|
||||
name: 'Transport',
|
||||
setup() {
|
||||
const vehiclesData = ref([]);
|
||||
const transportStats = ref([]);
|
||||
const transportAlerts = ref([]);
|
||||
const loading = ref(true);
|
||||
const error = ref(false);
|
||||
const transportMap = ref(null);
|
||||
const efficiencyChart = ref(null);
|
||||
|
||||
let mapInstance = null;
|
||||
let chartInstance = null;
|
||||
|
||||
// 车辆表格列定义
|
||||
const vehicleColumns = ref([
|
||||
{ title: '车牌号', dataIndex: 'plateNumber', key: 'plateNumber' },
|
||||
{ title: '司机', dataIndex: 'driver', key: 'driver' },
|
||||
{ title: '出发地', dataIndex: 'origin', key: 'origin' },
|
||||
{ title: '目的地', dataIndex: 'destination', key: 'destination' },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status' },
|
||||
{ title: '预计到达', dataIndex: 'eta', key: 'eta' },
|
||||
]);
|
||||
|
||||
// 运输统计数据
|
||||
transportStats.value = [
|
||||
{ title: '今日运输', value: '24车次', desc: '较昨日 +5%' },
|
||||
{ title: '在途运输', value: '8车次', desc: '正常运输中' },
|
||||
{ title: '平均时效', value: '4.2小时', desc: '较上周 -0.3小时' },
|
||||
{ title: '异常运输', value: '1车次', desc: '需关注处理' },
|
||||
];
|
||||
|
||||
// 运输异常告警数据
|
||||
transportAlerts.value = [
|
||||
{
|
||||
level: 'high',
|
||||
title: '运输延误',
|
||||
desc: '蒙H54321从西乌旗牧场C到上海加工中心已延误2小时',
|
||||
time: '2023-08-20 14:30',
|
||||
status: '处理中'
|
||||
},
|
||||
{
|
||||
level: 'medium',
|
||||
title: '路线偏离',
|
||||
desc: '蒙H12345从锡市牧场A到北京屠宰场偏离预定路线5公里',
|
||||
time: '2023-08-20 13:45',
|
||||
status: '已确认'
|
||||
},
|
||||
{
|
||||
level: 'low',
|
||||
title: '温度异常',
|
||||
desc: '蒙H67890冷藏车厢温度超过标准值0.5℃',
|
||||
time: '2023-08-20 12:15',
|
||||
status: '已处理'
|
||||
}
|
||||
];
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = false;
|
||||
// 模拟获取运输数据
|
||||
vehiclesData.value = [
|
||||
{
|
||||
key: '1',
|
||||
plateNumber: '蒙H12345',
|
||||
driver: '张三',
|
||||
origin: '锡市牧场A',
|
||||
destination: '北京屠宰场',
|
||||
status: '运输中',
|
||||
eta: '2023-08-20 15:30'
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
plateNumber: '蒙H67890',
|
||||
driver: '李四',
|
||||
origin: '东乌旗牧场B',
|
||||
destination: '呼和浩特产中心',
|
||||
status: '已完成',
|
||||
eta: '2023-08-20 14:15'
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
plateNumber: '蒙H54321',
|
||||
driver: '王五',
|
||||
origin: '西乌旗牧场C',
|
||||
destination: '上海加工中心',
|
||||
status: '延误',
|
||||
eta: '2023-08-20 16:45'
|
||||
},
|
||||
];
|
||||
} catch (err) {
|
||||
error.value = true;
|
||||
console.error('获取运输数据失败:', err);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化地图
|
||||
const initMap = () => {
|
||||
if (transportMap.value) {
|
||||
mapInstance = echarts.init(transportMap.value);
|
||||
mapInstance.setOption({
|
||||
title: {
|
||||
text: '运输路线图',
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item'
|
||||
},
|
||||
geo: {
|
||||
map: 'china',
|
||||
roam: false,
|
||||
zoom: 1.2,
|
||||
center: [116.0, 44.0], // 锡林郭勒盟位置
|
||||
itemStyle: {
|
||||
areaColor: '#e7e8ea'
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'lines',
|
||||
coordinateSystem: 'geo',
|
||||
lineStyle: {
|
||||
color: '#4CAF50',
|
||||
width: 2,
|
||||
opacity: 0.8,
|
||||
curveness: 0.2
|
||||
},
|
||||
data: [
|
||||
{
|
||||
coords: [[116.08, 43.95], [116.40, 39.90]] // 锡林浩特到北京
|
||||
},
|
||||
{
|
||||
coords: [[116.08, 43.95], [111.65, 40.82]] // 锡林浩特到呼和浩特
|
||||
},
|
||||
{
|
||||
coords: [[116.08, 43.95], [121.47, 31.23]] // 锡林浩特到上海
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'effectScatter',
|
||||
coordinateSystem: 'geo',
|
||||
symbolSize: 10,
|
||||
data: [
|
||||
{ name: '锡林浩特', value: [116.08, 43.95] },
|
||||
{ name: '北京', value: [116.40, 39.90] },
|
||||
{ name: '呼和浩特', value: [111.65, 40.82] },
|
||||
{ name: '上海', value: [121.47, 31.23] }
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化效率分析图表
|
||||
const initEfficiencyChart = () => {
|
||||
if (efficiencyChart.value) {
|
||||
chartInstance = echarts.init(efficiencyChart.value);
|
||||
chartInstance.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['实际时效', '计划时效']
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['1月', '2月', '3月', '4月', '5月', '6月']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '小时'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '实际时效',
|
||||
type: 'bar',
|
||||
data: [4.2, 3.8, 4.0, 4.5, 3.9, 4.1],
|
||||
itemStyle: { color: '#4CAF50' }
|
||||
},
|
||||
{
|
||||
name: '计划时效',
|
||||
type: 'bar',
|
||||
data: [4.0, 4.0, 4.0, 4.0, 4.0, 4.0],
|
||||
itemStyle: { color: '#2196F3' }
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 窗口大小改变时重绘图表
|
||||
const resizeCharts = () => {
|
||||
if (mapInstance) {
|
||||
mapInstance.resize();
|
||||
}
|
||||
if (chartInstance) {
|
||||
chartInstance.resize();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchData();
|
||||
initMap();
|
||||
initEfficiencyChart();
|
||||
window.addEventListener('resize', resizeCharts);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resizeCharts);
|
||||
if (mapInstance) {
|
||||
mapInstance.dispose();
|
||||
}
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
vehiclesData,
|
||||
transportStats,
|
||||
transportAlerts,
|
||||
vehicleColumns,
|
||||
loading,
|
||||
error,
|
||||
transportMap,
|
||||
efficiencyChart
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.transport-container {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.transport-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.map-section,
|
||||
.vehicles-section,
|
||||
.alerts-section,
|
||||
.stats-section,
|
||||
.efficiency-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.map-section h3,
|
||||
.vehicles-section h3,
|
||||
.alerts-section h3,
|
||||
.stats-section h3,
|
||||
.efficiency-section h3 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-title {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #4CAF50;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-desc {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.alerts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.alert-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #ccc;
|
||||
}
|
||||
|
||||
.alert-item.high {
|
||||
background-color: #ffebee;
|
||||
border-left-color: #f44336;
|
||||
}
|
||||
|
||||
.alert-item.medium {
|
||||
background-color: #fff3e0;
|
||||
border-left-color: #ff9800;
|
||||
}
|
||||
|
||||
.alert-item.low {
|
||||
background-color: #e3f2fd;
|
||||
border-left-color: #2196f3;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.alert-title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.alert-desc {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.alert-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.alert-status {
|
||||
font-size: 12px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.loading-indicator,
|
||||
.error-message {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.transport-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.alert-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,10 @@
|
||||
<!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>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>养殖管理系统 - 锡林郭勒盟智慧养殖数字化管理平台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
{
|
||||
"name": "farming-management",
|
||||
"version": "1.0.0",
|
||||
"description": "锡林郭勒盟安格斯牛养殖管理系统",
|
||||
"author": "xlxumu team",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.2.0",
|
||||
"vue-router": "^4.0.0",
|
||||
"ant-design-vue": "^3.0.0",
|
||||
"pinia": "^2.0.0",
|
||||
"echarts": "^5.0.0"
|
||||
"vue": "^3.2.45"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^3.0.0",
|
||||
"vite": "^3.0.0",
|
||||
"typescript": "^4.0.0",
|
||||
"@types/node": "^18.0.0"
|
||||
}
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"vite": "^4.0.0"
|
||||
},
|
||||
"description": "锡林郭勒盟智慧养殖管理系统"
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
<!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>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>养殖管理系统 - 锡林郭勒盟智慧养殖数字化管理平台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "farming-management",
|
||||
"name": "government-platform",
|
||||
"version": "1.0.0",
|
||||
"description": "锡林郭勒盟安格斯牛养殖管理系统",
|
||||
"description": "锡林郭勒盟智慧养殖管理系统",
|
||||
"author": "xlxumu team",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -18,8 +18,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^3.0.0",
|
||||
"vite": "^3.0.0",
|
||||
"typescript": "^4.0.0",
|
||||
"@types/node": "^18.0.0"
|
||||
"vite": "^3.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
<!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>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>养殖管理系统 - 锡林郭勒盟智慧养殖数字化管理平台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "farming-management",
|
||||
"name": "insurance-supervision",
|
||||
"version": "1.0.0",
|
||||
"description": "锡林郭勒盟安格斯牛养殖管理系统",
|
||||
"description": "锡林郭勒盟智慧养殖管理系统",
|
||||
"author": "xlxumu team",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -22,4 +22,4 @@
|
||||
"typescript": "^4.0.0",
|
||||
"@types/node": "^18.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!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>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>养殖管理系统 - 锡林郭勒盟智慧养殖数字化管理平台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "farming-management",
|
||||
"name": "mall-management",
|
||||
"version": "1.0.0",
|
||||
"description": "锡林郭勒盟安格斯牛养殖管理系统",
|
||||
"description": "锡林郭勒盟智慧养殖管理系统",
|
||||
"author": "xlxumu team",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -18,8 +18,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^3.0.0",
|
||||
"vite": "^3.0.0",
|
||||
"typescript": "^4.0.0",
|
||||
"@types/node": "^18.0.0"
|
||||
"vite": "^3.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,14 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/category/category",
|
||||
"pages/cart/cart",
|
||||
"pages/profile/profile",
|
||||
"pages/product/detail",
|
||||
"pages/order/list",
|
||||
"pages/order/detail",
|
||||
"pages/adopt/list",
|
||||
"pages/adopt/detail"
|
||||
"pages/logs/logs"
|
||||
],
|
||||
"window": {
|
||||
"backgroundTextStyle": "light",
|
||||
"navigationBarBackgroundColor": "#4CAF50",
|
||||
"navigationBarTitleText": "锡林郭勒盟安格斯牛",
|
||||
"navigationBarTextStyle": "white"
|
||||
"navigationBarBackgroundColor": "#fff",
|
||||
"navigationBarTitleText": "锡林郭勒盟智慧养殖",
|
||||
"navigationBarTextStyle": "black"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#7A7E83",
|
||||
"selectedColor": "#4CAF50",
|
||||
"borderStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"iconPath": "assets/icons/home.png",
|
||||
"selectedIconPath": "assets/icons/home-active.png",
|
||||
"text": "首页"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/category/category",
|
||||
"iconPath": "assets/icons/category.png",
|
||||
"selectedIconPath": "assets/icons/category-active.png",
|
||||
"text": "分类"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/cart/cart",
|
||||
"iconPath": "assets/icons/cart.png",
|
||||
"selectedIconPath": "assets/icons/cart-active.png",
|
||||
"text": "购物车"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/profile/profile",
|
||||
"iconPath": "assets/icons/profile.png",
|
||||
"selectedIconPath": "assets/icons/profile-active.png",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
},
|
||||
"networkTimeout": {
|
||||
"request": 10000,
|
||||
"connectSocket": 10000,
|
||||
"uploadFile": 10000,
|
||||
"downloadFile": 10000
|
||||
},
|
||||
"debug": true,
|
||||
"requiredPrivateInfos": []
|
||||
}
|
||||
"style": "v2",
|
||||
"sitemapLocation": "sitemap.json"
|
||||
}
|
||||
|
||||
@@ -1,59 +1,14 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/category/category",
|
||||
"pages/cart/cart",
|
||||
"pages/profile/profile",
|
||||
"pages/product/detail",
|
||||
"pages/order/list",
|
||||
"pages/order/detail",
|
||||
"pages/adopt/list",
|
||||
"pages/adopt/detail"
|
||||
"pages/logs/logs"
|
||||
],
|
||||
"window": {
|
||||
"backgroundTextStyle": "light",
|
||||
"navigationBarBackgroundColor": "#4CAF50",
|
||||
"navigationBarTitleText": "锡林郭勒盟安格斯牛",
|
||||
"navigationBarTextStyle": "white"
|
||||
"navigationBarBackgroundColor": "#fff",
|
||||
"navigationBarTitleText": "锡林郭勒盟智慧养殖",
|
||||
"navigationBarTextStyle": "black"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#7A7E83",
|
||||
"selectedColor": "#4CAF50",
|
||||
"borderStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"iconPath": "assets/icons/home.png",
|
||||
"selectedIconPath": "assets/icons/home-active.png",
|
||||
"text": "首页"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/category/category",
|
||||
"iconPath": "assets/icons/category.png",
|
||||
"selectedIconPath": "assets/icons/category-active.png",
|
||||
"text": "分类"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/cart/cart",
|
||||
"iconPath": "assets/icons/cart.png",
|
||||
"selectedIconPath": "assets/icons/cart-active.png",
|
||||
"text": "购物车"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/profile/profile",
|
||||
"iconPath": "assets/icons/profile.png",
|
||||
"selectedIconPath": "assets/icons/profile-active.png",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
},
|
||||
"networkTimeout": {
|
||||
"request": 10000,
|
||||
"connectSocket": 10000,
|
||||
"uploadFile": 10000,
|
||||
"downloadFile": 10000
|
||||
},
|
||||
"debug": true,
|
||||
"requiredPrivateInfos": []
|
||||
}
|
||||
"style": "v2",
|
||||
"sitemapLocation": "sitemap.json"
|
||||
}
|
||||
|
||||
@@ -1,59 +1,14 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/category/category",
|
||||
"pages/cart/cart",
|
||||
"pages/profile/profile",
|
||||
"pages/product/detail",
|
||||
"pages/order/list",
|
||||
"pages/order/detail",
|
||||
"pages/adopt/list",
|
||||
"pages/adopt/detail"
|
||||
"pages/logs/logs"
|
||||
],
|
||||
"window": {
|
||||
"backgroundTextStyle": "light",
|
||||
"navigationBarBackgroundColor": "#4CAF50",
|
||||
"navigationBarTitleText": "锡林郭勒盟安格斯牛",
|
||||
"navigationBarTextStyle": "white"
|
||||
"navigationBarBackgroundColor": "#fff",
|
||||
"navigationBarTitleText": "锡林郭勒盟智慧养殖",
|
||||
"navigationBarTextStyle": "black"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#7A7E83",
|
||||
"selectedColor": "#4CAF50",
|
||||
"borderStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"iconPath": "assets/icons/home.png",
|
||||
"selectedIconPath": "assets/icons/home-active.png",
|
||||
"text": "首页"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/category/category",
|
||||
"iconPath": "assets/icons/category.png",
|
||||
"selectedIconPath": "assets/icons/category-active.png",
|
||||
"text": "分类"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/cart/cart",
|
||||
"iconPath": "assets/icons/cart.png",
|
||||
"selectedIconPath": "assets/icons/cart-active.png",
|
||||
"text": "购物车"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/profile/profile",
|
||||
"iconPath": "assets/icons/profile.png",
|
||||
"selectedIconPath": "assets/icons/profile-active.png",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
},
|
||||
"networkTimeout": {
|
||||
"request": 10000,
|
||||
"connectSocket": 10000,
|
||||
"uploadFile": 10000,
|
||||
"downloadFile": 10000
|
||||
},
|
||||
"debug": true,
|
||||
"requiredPrivateInfos": []
|
||||
}
|
||||
"style": "v2",
|
||||
"sitemapLocation": "sitemap.json"
|
||||
}
|
||||
|
||||
@@ -1,59 +1,14 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/category/category",
|
||||
"pages/cart/cart",
|
||||
"pages/profile/profile",
|
||||
"pages/product/detail",
|
||||
"pages/order/list",
|
||||
"pages/order/detail",
|
||||
"pages/adopt/list",
|
||||
"pages/adopt/detail"
|
||||
"pages/logs/logs"
|
||||
],
|
||||
"window": {
|
||||
"backgroundTextStyle": "light",
|
||||
"navigationBarBackgroundColor": "#4CAF50",
|
||||
"navigationBarTitleText": "锡林郭勒盟安格斯牛",
|
||||
"navigationBarTextStyle": "white"
|
||||
"navigationBarBackgroundColor": "#fff",
|
||||
"navigationBarTitleText": "锡林郭勒盟智慧养殖",
|
||||
"navigationBarTextStyle": "black"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#7A7E83",
|
||||
"selectedColor": "#4CAF50",
|
||||
"borderStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"iconPath": "assets/icons/home.png",
|
||||
"selectedIconPath": "assets/icons/home-active.png",
|
||||
"text": "首页"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/category/category",
|
||||
"iconPath": "assets/icons/category.png",
|
||||
"selectedIconPath": "assets/icons/category-active.png",
|
||||
"text": "分类"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/cart/cart",
|
||||
"iconPath": "assets/icons/cart.png",
|
||||
"selectedIconPath": "assets/icons/cart-active.png",
|
||||
"text": "购物车"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/profile/profile",
|
||||
"iconPath": "assets/icons/profile.png",
|
||||
"selectedIconPath": "assets/icons/profile-active.png",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
},
|
||||
"networkTimeout": {
|
||||
"request": 10000,
|
||||
"connectSocket": 10000,
|
||||
"uploadFile": 10000,
|
||||
"downloadFile": 10000
|
||||
},
|
||||
"debug": true,
|
||||
"requiredPrivateInfos": []
|
||||
}
|
||||
"style": "v2",
|
||||
"sitemapLocation": "sitemap.json"
|
||||
}
|
||||
|
||||
@@ -1,59 +1,14 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/category/category",
|
||||
"pages/cart/cart",
|
||||
"pages/profile/profile",
|
||||
"pages/product/detail",
|
||||
"pages/order/list",
|
||||
"pages/order/detail",
|
||||
"pages/adopt/list",
|
||||
"pages/adopt/detail"
|
||||
"pages/logs/logs"
|
||||
],
|
||||
"window": {
|
||||
"backgroundTextStyle": "light",
|
||||
"navigationBarBackgroundColor": "#4CAF50",
|
||||
"navigationBarTitleText": "锡林郭勒盟安格斯牛",
|
||||
"navigationBarTextStyle": "white"
|
||||
"navigationBarBackgroundColor": "#fff",
|
||||
"navigationBarTitleText": "锡林郭勒盟智慧养殖",
|
||||
"navigationBarTextStyle": "black"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#7A7E83",
|
||||
"selectedColor": "#4CAF50",
|
||||
"borderStyle": "black",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"iconPath": "assets/icons/home.png",
|
||||
"selectedIconPath": "assets/icons/home-active.png",
|
||||
"text": "首页"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/category/category",
|
||||
"iconPath": "assets/icons/category.png",
|
||||
"selectedIconPath": "assets/icons/category-active.png",
|
||||
"text": "分类"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/cart/cart",
|
||||
"iconPath": "assets/icons/cart.png",
|
||||
"selectedIconPath": "assets/icons/cart-active.png",
|
||||
"text": "购物车"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/profile/profile",
|
||||
"iconPath": "assets/icons/profile.png",
|
||||
"selectedIconPath": "assets/icons/profile-active.png",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
},
|
||||
"networkTimeout": {
|
||||
"request": 10000,
|
||||
"connectSocket": 10000,
|
||||
"uploadFile": 10000,
|
||||
"downloadFile": 10000
|
||||
},
|
||||
"debug": true,
|
||||
"requiredPrivateInfos": []
|
||||
}
|
||||
"style": "v2",
|
||||
"sitemapLocation": "sitemap.json"
|
||||
}
|
||||
|
||||
@@ -311,9 +311,9 @@
|
||||
<img src="images/news-2.jpg" class="card-img-top" alt="新闻图片2">
|
||||
<div class="card-body">
|
||||
<span class="badge bg-success mb-2">市场动态</span>
|
||||
<h3 class="h5">安格斯牛肉价格连续三月上涨</h3>
|
||||
<h3 class="h5">优质牛肉价格连续三月上涨</h3>
|
||||
<p class="text-muted small">2025-08-10</p>
|
||||
<p class="card-text">受市场需求增加影响,优质安格斯牛肉价格环比上涨8.5%...</p>
|
||||
<p class="card-text">受市场需求增加影响,优质牛肉价格环比上涨8.5%...</p>
|
||||
<a href="#" class="btn btn-link p-0 text-success">阅读全文 <i class="bi bi-arrow-right"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user