You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
14 KiB
14 KiB
项目开发历史记录
一、项目概述
本项目是一个牦牛产业数据可视化平台,采用 Vue.js 框架开发,包含多个功能模块:交易流向监测、市场环境监测、多维度市场分析、牦牛供应展示等。平台集成了 Element UI 组件库和 ECharts 数据可视化库,实现了响应式布局和交互式数据展示功能。
技术栈:
- 前端框架:Vue.js 3.x(Composition API)
- UI 组件库:Element Plus
- 图表库:ECharts
- 地图数据:GeoJSON(china.json)
- 样式方案:CSS Scoped + CSS Flexbox/Grid
- 构建工具:Vite
二、组件架构演变
2.1 初始阶段:单文件 App.vue
项目最初是一个单文件的 App.vue,包含了所有的业务逻辑和 UI 代码。随着功能增加,代码变得臃肿,难以维护。
2.2 组件拆分阶段
按照单一职责原则,将 monolithic App.vue 拆分为以下独立组件:
src/components/
├── AppHeader.vue # 顶部导航栏
├── TransactionStats.vue # 实时交易统计
├── SalesChart.vue # 销售趋势图表
├── YakPopulationChart.vue # 牦牛存栏图表
├── FlowMonitorMap.vue # 交易流向地图
├── SceneManagement.vue # 场景管理(监控+环境)
└── MarketAnalysis.vue # 多维度市场分析
三、主题风格演变
3.1 初始深绿色主题
最初尝试使用偏绿色的深色风格,模拟草原风格:
--primary-color: #22c55e;
--primary-dark: #15803d;
--background-dark: #0f172a;
--card-dark: #1e293b;
3.2 朴素灰色白色主题
用户反馈绿色不太合适,要求改回朴实一点的深色主题:
--background-color: #f8fafc;
--card-background: #ffffff;
--text-primary: #1e293b;
--text-secondary: #64748b;
--border-color: #e2e8f0;
--primary-color: #22c55e; /* 保留绿色作为强调色 */
3.3 顶部导航栏样式
将顶部导航栏从白色改为深蓝色,增强视觉层次:
.app-header {
background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
color: white;
}
四、核心功能模块实现
4.1 场景管理组件(SceneManagement.vue)
场景管理组件是平台的核心模块之一,负责展示监控视频和环境监测数据。
布局演变:
- 初始布局:简单的垂直排列
- 卡片式布局:引入 Element UI 的 el-card 组件
- 双栏布局:左侧展示环境指标,右侧展示监控视频
最终布局结构:
<el-row :gutter="20">
<!-- 左侧:环境监测指标 -->
<el-col :span="12">
<div class="indicators-left">
<div class="indicator-row">
<env-card v-for="item in firstRowIndicators" :key="item.id" :data="item" />
</div>
<div class="indicator-row">
<env-card v-for="item in secondRowIndicators" :key="item.id" :data="item" />
</div>
</div>
</el-col>
<!-- 右侧:监控视频 -->
<el-col :span="12">
<div class="monitor-right">
<div class="video-container" v-for="camera in activeCameras" :key="camera.id">
<video :src="camera.src" controls />
<div class="video-info">
<span class="camera-name">{{ camera.name }}</span>
</div>
</div>
<div class="video-nav">
<button @click="switchToPrevCamera" :disabled="currentCameraIndex === 0">←</button>
<button @click="switchToNextCamera" :disabled="currentCameraIndex === cameras.length - 1">→</button>
</div>
</div>
</el-col>
</el-row>
环境监测状态样式:
.env-card.normal {
background: #ffffff;
border: 1px solid #e2e8f0;
}
.env-card.warning {
background: #fef2f2;
border: 1px solid #fecaca;
}
.env-card.critical {
background: #fef2f2;
border: 1px solid #ef4444;
}
4.2 交易流向监测组件(FlowMonitorMap.vue)
使用 ECharts 实现地理数据可视化,展示牦牛销售网络、源地供应和红原出栏分布。
实现方案:
<script setup>
import { ref, onMounted, watch } from 'vue';
import * as echarts from 'echarts';
const props = defineProps({
mapData: {
type: Object,
default: null
}
});
const chartRef = ref(null);
let chart = null;
let chinaMapData = null;
const mapTypes = [
{ value: 'sales', label: '销售网络分布' },
{ value: 'supply', label: '源地供应分布' },
{ value: 'slaughter', label: '红原出栏分布' }
];
const activeMapType = ref('sales');
const initChart = async () => {
chart = echarts.init(chartRef.value);
const response = await fetch('/data/china.json');
chinaMapData = await response.json();
echarts.registerMap('china', chinaMapData);
updateChart();
window.addEventListener('resize', handleResize);
};
const updateChart = () => {
const data = mockData[activeMapType.value];
const option = {
backgroundColor: '#f5f7fa',
title: {
text: data.title,
subtext: data.subtitle,
left: 'center',
top: 20
},
geo: {
map: 'china',
roam: true,
center: data.center,
zoom: data.zoom,
itemStyle: {
areaColor: '#e2e8f0',
borderColor: '#94a3b8'
}
},
series: [
{
type: 'lines',
coordinateSystem: 'geo',
lineStyle: {
color: '#22c55e',
width: 2,
curveness: 0.15
},
effect: {
show: true,
period: 4,
trailLength: 0.3,
symbol: 'arrow',
symbolSize: 8
}
},
{
name: '核心',
type: 'effectScatter',
coordinateSystem: 'geo',
symbolSize: (val) => val[2] / 2,
itemStyle: {
color: '#ef4444'
},
rippleEffect: {
brushType: 'stroke',
scale: 3
}
}
]
};
chart.setOption(option, true);
};
</script>
4.3 多维度市场分析组件(MarketAnalysis.vue)
包含三个分析维度:销售结构分析、价格趋势分析、客户来源分析。
布局结构:
<el-row :gutter="20">
<el-col :span="12">
<el-card>
<template #header>
<div class="card-header">
<span>销售结构分析</span>
<el-radio-group v-model="salesViewType" size="small">
<el-radio-button value="pie">饼图</el-radio-button>
<el-radio-button value="bar">柱状图</el-radio-button>
</el-radio-group>
</div>
</template>
<div ref="salesChartRef" class="chart-container" />
</el-card>
</el-col>
<el-col :span="12">
<el-card>
<template #header>
<div class="card-header">
<span>价格趋势分析</span>
<el-radio-group v-model="pricePeriod" size="small">
<el-radio-button value="week">周</el-radio-button>
<el-radio-button value="month">月</el-radio-button>
<el-radio-button value="year">年</el-radio-button>
</el-radio-group>
</div>
</template>
<div ref="priceChartRef" class="chart-container" />
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px;">
<el-col :span="24">
<el-card>
<template #header>
<div class="card-header">
<span>客户来源分析</span>
</div>
</template>
<div ref="customerChartRef" class="chart-container" />
</el-card>
</el-col>
</el-row>
五、数据架构演变
5.1 初始阶段:组件内嵌 mock 数据
各组件内部定义模拟数据,导致数据冗余和重复请求。
const mockData = {
salesByPriceRange: [...],
priceTrend: [...],
// ...
};
5.2 JSON 文件阶段
将 mock 数据移到静态 JSON 文件:
{
"marketAnalysis": {
"salesByPriceRange": [...],
"salesByRegion": [...],
"priceTrend": [...],
"customerSource": [...]
}
}
5.3 共享数据架构阶段(最终方案)
在 App.vue 中集中获取数据,通过 props 传递给子组件:
// App.vue
<script setup>
import { ref, onMounted } from 'vue';
import SceneManagement from './components/SceneManagement.vue';
import MarketAnalysis from './components/MarketAnalysis.vue';
import FlowMonitorMap from './components/FlowMonitorMap.vue';
const sharedData = ref(null);
const fetchSharedData = async () => {
try {
const response = await fetch('/data/mockData.json');
sharedData.value = await response.json();
} catch (error) {
console.error('获取共享数据失败:', error);
}
};
onMounted(() => {
fetchSharedData();
});
</script>
<template>
<SceneManagement v-if="sharedData" :scene-data="sharedData.sceneData" />
<MarketAnalysis v-if="sharedData" :market-analysis-data="sharedData.marketAnalysis" />
<FlowMonitorMap v-if="sharedData" :map-data="sharedData.flowData" />
</template>
优势:
- 减少网络请求次数(只请求一次)
- 保证数据一致性
- 便于数据管理和维护
六、关键文件结构
zhhxjy/
├── public/
│ └── data/
│ ├── mockData.json # 共享模拟数据
│ └── china.json # 中国地图地理数据
├── src/
│ ├── components/
│ │ ├── AppHeader.vue # 顶部导航
│ │ ├── TransactionStats.vue
│ │ ├── SalesChart.vue
│ │ ├── YakPopulationChart.vue
│ │ ├── FlowMonitorMap.vue # 交易流向地图
│ │ ├── SceneManagement.vue # 场景管理
│ │ └── MarketAnalysis.vue # 市场分析
│ ├── App.vue # 主应用组件
│ ├── main.js # 应用入口
│ └── style.css # 全局样式
├── index.html
├── package.json
└── vite.config.js
七、遇到的问题及解决方案
7.1 地图数据加载失败
问题:从外部 URL 下载地图数据失败(404 错误)
解决方案:
- 使用本地 china.json 文件
- 放置在 public/data/ 目录下
- 通过
/data/china.json路径访问
7.2 视频容器高度不匹配
问题:增加视频宽度后,容器高度计算错误,导致内容溢出或留白
解决方案:
.video-wrapper {
height: 100%;
display: flex;
flex-direction: column;
}
.video-content {
flex: 1;
height: 100%;
}
.video-info-main {
flex: 1;
display: flex;
flex-direction: column;
}
7.3 右侧导航按钮消失
问题:视频宽度增加后,右侧切换按钮被隐藏
解决方案:
.monitor-right {
width: 800px; /* 增加容器宽度 */
}
.video-content {
width: 320px; /* 固定视频宽度 */
}
7.4 预警颜色不一致
问题:环境监测的预警状态使用了黄色主题,与整体风格不符
解决方案:
.env-card.warning {
background: #fef2f2;
border: 1px solid #fecaca;
}
.env-card.critical {
background: #fef2f2;
border: 1px solid #ef4444;
}
.warning-badge {
background: #ef4444;
color: white;
}
7.5 Mock 数据位置错误
问题:mockData.json 放在 src/data 目录,无法通过 HTTP 请求访问
解决方案:
- 创建 public/data/ 目录
- 将 mockData.json 移动到新目录
- 通过
/data/mockData.json路径访问
八、开发里程碑
里程碑 1:组件拆分
- 完成 App.vue 到独立组件的拆分
- 建立规范的目录结构
里程碑 2:主题定制
- 实现灰色白色主题
- 保留绿色作为强调色
- 优化顶部导航栏样式
里程碑 3:布局优化
- 实现卡片式布局
- 优化监控视频布局
- 修复各种溢出和高度问题
里程碑 4:数据架构
- 实现共享数据获取
- 减少冗余请求
- 统一数据管理
里程碑 5:地图集成
- 实现本地地图数据加载
- 完成交易流向可视化
- 添加交互控制功能
九、后续优化建议
- 性能优化:对 ECharts 图表实现懒加载
- 响应式设计:增加移动端适配
- 数据可视化:添加更多交互功能(缩放、筛选、导出)
- 错误处理:增加请求失败的重试机制和错误提示
- 代码规范:添加 TypeScript 类型支持
- 测试覆盖:增加单元测试和 E2E 测试
十、技术要点总结
| 功能 | 技术方案 | 关键代码 |
|---|---|---|
| 组件通信 | Props + Events | defineProps(), defineEmits() |
| 状态管理 | Ref + Watch | ref(), watch() |
| 图表渲染 | ECharts | echarts.init(), chart.setOption() |
| 地图渲染 | ECharts Geo | registerMap(), geo 配置 |
| 卡片布局 | Element UI | el-card, el-row, el-col |
| 数据获取 | Fetch API | fetch('/data/xxx.json') |
| 样式隔离 | Scoped CSS | <style scoped> |
| 响应式 | Flexbox | display: flex, flex: 1 |
文档生成时间:2025-12-27