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.
535 lines
14 KiB
535 lines
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 初始深绿色主题
|
|
|
|
最初尝试使用偏绿色的深色风格,模拟草原风格:
|
|
|
|
```css
|
|
--primary-color: #22c55e;
|
|
--primary-dark: #15803d;
|
|
--background-dark: #0f172a;
|
|
--card-dark: #1e293b;
|
|
```
|
|
|
|
### 3.2 朴素灰色白色主题
|
|
|
|
用户反馈绿色不太合适,要求改回朴实一点的深色主题:
|
|
|
|
```css
|
|
--background-color: #f8fafc;
|
|
--card-background: #ffffff;
|
|
--text-primary: #1e293b;
|
|
--text-secondary: #64748b;
|
|
--border-color: #e2e8f0;
|
|
--primary-color: #22c55e; /* 保留绿色作为强调色 */
|
|
```
|
|
|
|
### 3.3 顶部导航栏样式
|
|
|
|
将顶部导航栏从白色改为深蓝色,增强视觉层次:
|
|
|
|
```css
|
|
.app-header {
|
|
background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
|
|
color: white;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 四、核心功能模块实现
|
|
|
|
### 4.1 场景管理组件(SceneManagement.vue)
|
|
|
|
场景管理组件是平台的核心模块之一,负责展示监控视频和环境监测数据。
|
|
|
|
**布局演变:**
|
|
|
|
1. **初始布局**:简单的垂直排列
|
|
2. **卡片式布局**:引入 Element UI 的 el-card 组件
|
|
3. **双栏布局**:左侧展示环境指标,右侧展示监控视频
|
|
|
|
**最终布局结构:**
|
|
|
|
```html
|
|
<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>
|
|
```
|
|
|
|
**环境监测状态样式:**
|
|
|
|
```css
|
|
.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 实现地理数据可视化,展示牦牛销售网络、源地供应和红原出栏分布。
|
|
|
|
**实现方案:**
|
|
|
|
```javascript
|
|
<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)
|
|
|
|
包含三个分析维度:销售结构分析、价格趋势分析、客户来源分析。
|
|
|
|
**布局结构:**
|
|
|
|
```html
|
|
<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 数据
|
|
|
|
各组件内部定义模拟数据,导致数据冗余和重复请求。
|
|
|
|
```javascript
|
|
const mockData = {
|
|
salesByPriceRange: [...],
|
|
priceTrend: [...],
|
|
// ...
|
|
};
|
|
```
|
|
|
|
### 5.2 JSON 文件阶段
|
|
|
|
将 mock 数据移到静态 JSON 文件:
|
|
|
|
```json
|
|
{
|
|
"marketAnalysis": {
|
|
"salesByPriceRange": [...],
|
|
"salesByRegion": [...],
|
|
"priceTrend": [...],
|
|
"customerSource": [...]
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5.3 共享数据架构阶段(最终方案)
|
|
|
|
在 App.vue 中集中获取数据,通过 props 传递给子组件:
|
|
|
|
```javascript
|
|
// 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 视频容器高度不匹配
|
|
|
|
**问题**:增加视频宽度后,容器高度计算错误,导致内容溢出或留白
|
|
|
|
**解决方案**:
|
|
```css
|
|
.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 右侧导航按钮消失
|
|
|
|
**问题**:视频宽度增加后,右侧切换按钮被隐藏
|
|
|
|
**解决方案**:
|
|
```css
|
|
.monitor-right {
|
|
width: 800px; /* 增加容器宽度 */
|
|
}
|
|
|
|
.video-content {
|
|
width: 320px; /* 固定视频宽度 */
|
|
}
|
|
```
|
|
|
|
### 7.4 预警颜色不一致
|
|
|
|
**问题**:环境监测的预警状态使用了黄色主题,与整体风格不符
|
|
|
|
**解决方案**:
|
|
```css
|
|
.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:地图集成
|
|
- 实现本地地图数据加载
|
|
- 完成交易流向可视化
|
|
- 添加交互控制功能
|
|
|
|
---
|
|
|
|
## 九、后续优化建议
|
|
|
|
1. **性能优化**:对 ECharts 图表实现懒加载
|
|
2. **响应式设计**:增加移动端适配
|
|
3. **数据可视化**:添加更多交互功能(缩放、筛选、导出)
|
|
4. **错误处理**:增加请求失败的重试机制和错误提示
|
|
5. **代码规范**:添加 TypeScript 类型支持
|
|
6. **测试覆盖**:增加单元测试和 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*
|
|
|