docs: 更新项目文档和数据库设计,添加官网相关功能

This commit is contained in:
ylweng
2025-08-20 03:01:14 +08:00
parent 47c67ff704
commit fdc58aa3a2
58 changed files with 37635 additions and 28437 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -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": "锡林郭勒盟智慧养殖产业大屏可视化系统"
}

View File

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

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

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

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

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

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

View File

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

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

View File

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

View File

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

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

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

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

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

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

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

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

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

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

View File

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

View File

@@ -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": "锡林郭勒盟智慧养殖管理系统"
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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