feat(screen): 添加模块化驾驶舱仪表板,包含天气、紧急情况和统计数据组件year statistics, and map center.
270
packages/screen/src/views/cockpit/README.md
Normal file
@ -0,0 +1,270 @@
|
||||
# 安全保通服务大屏 - 重构说明
|
||||
|
||||
## 📦 重构概览
|
||||
|
||||
本次重构将原来 567 行的单一组件拆分为模块化的组件架构,提高了代码的可维护性和可扩展性。
|
||||
|
||||
## 🎯 主要改进
|
||||
|
||||
### 1. **组件化架构**
|
||||
- ✅ 将单一大文件拆分为 7 个独立组件
|
||||
- ✅ 每个组件职责单一,易于维护
|
||||
- ✅ 组件可复用,便于扩展
|
||||
|
||||
### 2. **布局方式优化**
|
||||
- ❌ **重构前**: 大量使用 `position: absolute` 和固定像素定位
|
||||
- ✅ **重构后**: 使用 CSS Grid 和 Flexbox 实现响应式布局
|
||||
- ✅ 更易调整和适配不同屏幕尺寸
|
||||
|
||||
### 3. **数据驱动**
|
||||
- ❌ **重构前**: 数据硬编码在模板中
|
||||
- ✅ **重构后**: 使用 Vue 3 Composition API,数据驱动渲染
|
||||
- ✅ 便于后续接入真实 API 数据
|
||||
|
||||
### 4. **样式管理**
|
||||
- ❌ **重构前**: 类名混乱 (`box_1`, `group_3` 等)
|
||||
- ✅ **重构后**: 语义化类名,易于理解
|
||||
- ✅ 样式模块化,避免全局污染
|
||||
|
||||
## 📁 目录结构
|
||||
|
||||
```
|
||||
cockpit/
|
||||
├── index.vue # 主入口文件
|
||||
├── components/ # 组件目录
|
||||
│ ├── CockpitLayout.vue # 主布局组件
|
||||
│ ├── PageHeader.vue # 页面头部
|
||||
│ ├── WeatherWarning.vue # 气象预警 (左上)
|
||||
│ ├── EmergencyResources.vue # 应急资源 (左下)
|
||||
│ ├── BlockEvent.vue # 阻断事件 (右上)
|
||||
│ ├── YearStatistics.vue # 往年统计 (右下)
|
||||
│ └── MapCenter.vue # 中间地图区域
|
||||
└── assets/ # 资源文件
|
||||
└── img/ # 图片资源
|
||||
```
|
||||
|
||||
## 🔧 组件说明
|
||||
|
||||
### 1. CockpitLayout.vue (主布局)
|
||||
使用 CSS Grid 三列布局:
|
||||
```css
|
||||
grid-template-columns: 580px 1fr 580px;
|
||||
```
|
||||
- 左侧面板: 固定 580px
|
||||
- 中间地图: 自适应剩余空间
|
||||
- 右侧面板: 固定 580px
|
||||
|
||||
### 2. PageHeader.vue (页面头部)
|
||||
- 居中显示标题 "安全保通服务"
|
||||
- 右侧应用按钮
|
||||
- 使用 Flexbox 布局
|
||||
|
||||
### 3. WeatherWarning.vue (气象预警)
|
||||
**数据结构**:
|
||||
```javascript
|
||||
{
|
||||
warningLevels: [
|
||||
{ id, name, count, color, icon }
|
||||
],
|
||||
districts: [
|
||||
{ id, name, km, warning, warningColor }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**功能**:
|
||||
- 四级预警图标展示 (蓝/黄/橙/红)
|
||||
- 海拔800米以上气象情况
|
||||
- 区县预警列表
|
||||
|
||||
### 4. EmergencyResources.vue (应急资源)
|
||||
**数据结构**:
|
||||
```javascript
|
||||
{
|
||||
resources: [
|
||||
{ id, name, stations, supplies, equipment, equipmentClass, hasAlert }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**功能**:
|
||||
- 展示各区县应急资源数量
|
||||
- 服务站点、应急物资、应急设备统计
|
||||
- 异常状态图标提示
|
||||
|
||||
### 5. BlockEvent.vue (阻断事件)
|
||||
**数据结构**:
|
||||
```javascript
|
||||
{
|
||||
stats: [
|
||||
{ id, title, value }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**功能**:
|
||||
- 统计卡片展示 (2列网格布局)
|
||||
- 当前封闭交通、限速通行、抢通等数据
|
||||
|
||||
### 6. YearStatistics.vue (往年统计)
|
||||
**功能**:
|
||||
- 冰雪事件趋势图 (当前使用静态图片)
|
||||
- 图表图例
|
||||
- 坐标轴标签
|
||||
|
||||
**TODO**: 集成 ECharts 实现动态图表
|
||||
|
||||
### 7. MapCenter.vue (中间地图)
|
||||
**功能**:
|
||||
- 顶部功能按钮 (服务站点/预警路段/气象预警)
|
||||
- 地图标记点
|
||||
- 底部菜单 (应急体系/重大事件处置等)
|
||||
- 中央功能菜单
|
||||
|
||||
**TODO**: 集成实际地图 API (如高德地图/百度地图)
|
||||
|
||||
## 🚀 使用方式
|
||||
|
||||
### 1. 基础使用
|
||||
```vue
|
||||
<template>
|
||||
<CockpitLayout />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import CockpitLayout from './components/CockpitLayout.vue'
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. 修改数据
|
||||
每个组件都使用 `ref` 定义响应式数据,可以直接修改:
|
||||
|
||||
```javascript
|
||||
// WeatherWarning.vue
|
||||
const districts = ref([
|
||||
{ id: 1, name: '万州区', km: 32, warning: '红色预警', warningColor: 'rgba(229, 37, 42, 1)' }
|
||||
])
|
||||
```
|
||||
|
||||
### 3. 接入 API
|
||||
将静态数据改为 API 调用:
|
||||
|
||||
```javascript
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { fetchDistricts } from '@/api/weather'
|
||||
|
||||
const districts = ref([])
|
||||
|
||||
onMounted(async () => {
|
||||
districts.value = await fetchDistricts()
|
||||
})
|
||||
```
|
||||
|
||||
## 📊 后续优化建议
|
||||
|
||||
### 1. 集成真实地图
|
||||
推荐使用:
|
||||
- 高德地图 API
|
||||
- 百度地图 API
|
||||
- Mapbox GL
|
||||
|
||||
### 2. 集成图表库
|
||||
推荐使用:
|
||||
- **ECharts** (功能强大,配置灵活)
|
||||
- Chart.js
|
||||
- D3.js
|
||||
|
||||
示例代码:
|
||||
```javascript
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
onMounted(() => {
|
||||
const chart = echarts.init(chartRef.value)
|
||||
chart.setOption({
|
||||
xAxis: { data: xAxisYears.value },
|
||||
yAxis: {},
|
||||
series: [
|
||||
{ type: 'line', data: [1000, 2000, 3000, 4000, 5000] }
|
||||
]
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### 3. 添加响应式适配
|
||||
使用媒体查询或 `vw/vh` 单位适配不同屏幕:
|
||||
|
||||
```css
|
||||
@media (max-width: 1920px) {
|
||||
.cockpit-main {
|
||||
grid-template-columns: 450px 1fr 450px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 添加数据刷新
|
||||
使用定时器或 WebSocket 实现实时数据更新:
|
||||
|
||||
```javascript
|
||||
import { useIntervalFn } from '@vueuse/core'
|
||||
|
||||
useIntervalFn(() => {
|
||||
refreshData()
|
||||
}, 30000) // 每30秒刷新
|
||||
```
|
||||
|
||||
### 5. 添加交互功能
|
||||
- 地图标记点击事件
|
||||
- 表格行点击查看详情
|
||||
- 图表数据筛选
|
||||
- 时间范围选择
|
||||
|
||||
### 6. 性能优化
|
||||
- 使用虚拟滚动处理大量数据
|
||||
- 图片懒加载
|
||||
- 组件懒加载
|
||||
|
||||
```javascript
|
||||
const MapCenter = defineAsyncComponent(() =>
|
||||
import('./components/MapCenter.vue')
|
||||
)
|
||||
```
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **图片路径**: 确保所有图片资源存在于 `assets/img/` 目录中
|
||||
2. **字体文件**: 部分组件使用自定义字体,需要确保字体文件已引入
|
||||
3. **浏览器兼容**: Grid 布局需要现代浏览器支持 (IE11+ 需要 polyfill)
|
||||
|
||||
## 🎨 布局对比
|
||||
|
||||
### 重构前
|
||||
```
|
||||
├── 嵌套10层+ div
|
||||
├── position: absolute 定位超过50处
|
||||
├── 固定 margin/left/top 值
|
||||
└── 567行单文件
|
||||
```
|
||||
|
||||
### 重构后
|
||||
```
|
||||
├── 语义化组件结构
|
||||
├── Grid + Flexbox 布局
|
||||
├── 响应式设计理念
|
||||
└── 7个模块化组件,平均每个约150行
|
||||
```
|
||||
|
||||
## 📝 版本历史
|
||||
|
||||
- **v2.0** (2025-01-XX) - 组件化重构,使用 Grid 布局
|
||||
- **v1.0** (之前) - 原始版本,单文件实现
|
||||
|
||||
## 🤝 贡献指南
|
||||
|
||||
1. 修改组件时保持单一职责原则
|
||||
2. 新增功能优先考虑组件复用
|
||||
3. 样式使用 scoped,避免全局污染
|
||||
4. 数据结构清晰,便于接入 API
|
||||
|
||||
---
|
||||
|
||||
如有问题或建议,请联系开发团队。
|
||||
BIN
packages/screen/src/views/cockpit/assets/img/block-panel-bg.png
Normal file
|
After Width: | Height: | Size: 372 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
BIN
packages/screen/src/views/cockpit/assets/img/block-stat-icon.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
BIN
packages/screen/src/views/cockpit/assets/img/cockpit-main-bg.png
Normal file
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 354 B |
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 12 KiB |
BIN
packages/screen/src/views/cockpit/assets/img/header-bg.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
BIN
packages/screen/src/views/cockpit/assets/img/header-title-bg.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
packages/screen/src/views/cockpit/assets/img/map-btn-service.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
BIN
packages/screen/src/views/cockpit/assets/img/map-btn-weather.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 212 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 293 KiB |
|
After Width: | Height: | Size: 13 KiB |
133
packages/screen/src/views/cockpit/components/BlockEvent.vue
Normal file
@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div class="block-event">
|
||||
<div class="panel-header">
|
||||
<span class="title">阻断事件</span>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-grid">
|
||||
<div
|
||||
v-for="stat in stats"
|
||||
:key="stat.id"
|
||||
class="stat-card"
|
||||
>
|
||||
<img src="../assets/img/block-stat-icon.png" alt="icon" class="stat-icon" />
|
||||
<div class="stat-content">
|
||||
<div class="stat-title">{{ stat.title }}</div>
|
||||
<div class="stat-value">{{ stat.value }}</div>
|
||||
<div class="stat-unit">公里</div>
|
||||
<img src="../assets/img/block-stat-decoration.png" alt="decoration" class="stat-decoration" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const stats = ref([
|
||||
{ id: 1, title: '当前封闭交通', value: 12 },
|
||||
{ id: 2, title: '当前累计封闭交通', value: 45 },
|
||||
{ id: 3, title: '当前限速通行', value: 30 },
|
||||
{ id: 4, title: '当年累计限速通行', value: 60 },
|
||||
{ id: 5, title: '当前累计抢通', value: 60 }
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.block-event {
|
||||
background: url(../assets/img/block-panel-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
padding: 20px 30px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
position: relative;
|
||||
width: 293px;
|
||||
height: 49px;
|
||||
background: url(../assets/img/common-panel-header-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 24px;
|
||||
margin-bottom: 40px;
|
||||
|
||||
.title {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 22px;
|
||||
font-family: SourceHanSansCN-Medium, sans-serif;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 40px 20px;
|
||||
flex: 1;
|
||||
align-content: flex-start;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
.stat-icon {
|
||||
width: 53px;
|
||||
height: 84px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
.stat-title {
|
||||
height: 28px;
|
||||
background: url(../assets/img/block-stat-title-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 15px;
|
||||
font-family: SourceHanSansCN-Bold, sans-serif;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 42px;
|
||||
font-family: SourceHanSansCN-Bold, sans-serif;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.stat-unit {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
right: 20px;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.stat-decoration {
|
||||
width: 154px;
|
||||
height: 29px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 单独处理最后一个卡片占满整行 */
|
||||
.stat-card:nth-child(5) {
|
||||
grid-column: span 1;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="cockpit-layout">
|
||||
<!-- <PageHeader /> -->
|
||||
<div class="cockpit-main">
|
||||
<div class="left-panel">
|
||||
<WeatherWarning />
|
||||
<EmergencyResources />
|
||||
</div>
|
||||
|
||||
<div class="center-panel">
|
||||
<MapCenter />
|
||||
</div>
|
||||
|
||||
<div class="right-panel">
|
||||
<BlockEvent />
|
||||
<YearStatistics />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import PageHeader from './PageHeader.vue'
|
||||
import WeatherWarning from './WeatherWarning.vue'
|
||||
import EmergencyResources from './EmergencyResources.vue'
|
||||
import MapCenter from './MapCenter.vue'
|
||||
import BlockEvent from './BlockEvent.vue'
|
||||
import YearStatistics from './YearStatistics.vue'
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.cockpit-layout {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: url(../assets/img/cockpit-main-bg.png) no-repeat;
|
||||
background-size: cover;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cockpit-main {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: 580px 1fr 580px;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.left-panel,
|
||||
.right-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.center-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<div class="emergency-resources">
|
||||
<div class="panel-header">
|
||||
<span class="title">应急资源</span>
|
||||
</div>
|
||||
|
||||
<!-- 资源列表 -->
|
||||
<div class="resource-table">
|
||||
<div class="table-header">
|
||||
<span>所属区县</span>
|
||||
<span>服务站点</span>
|
||||
<span>应急物资</span>
|
||||
<span>应急设备</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="(resource, index) in resources"
|
||||
:key="resource.id"
|
||||
class="table-row"
|
||||
:class="{ 'row-alt': index % 2 === 0 }"
|
||||
>
|
||||
<div class="row-number">{{ index + 1 }}</div>
|
||||
<span class="district-name">{{ resource.name }}</span>
|
||||
<span class="count green">{{ resource.stations }}</span>
|
||||
<span class="count orange">{{ resource.supplies }}</span>
|
||||
<div class="equipment-cell">
|
||||
<span class="count" :class="resource.equipmentClass">{{ resource.equipment }}</span>
|
||||
<img
|
||||
v-if="resource.hasAlert"
|
||||
src="../assets/img/emergency-alert-icon.png"
|
||||
alt="alert"
|
||||
class="alert-icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const resources = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '万州区',
|
||||
stations: 32,
|
||||
supplies: 32,
|
||||
equipment: 25,
|
||||
equipmentClass: 'red',
|
||||
hasAlert: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '城口区',
|
||||
stations: 11,
|
||||
supplies: 11,
|
||||
equipment: 15,
|
||||
equipmentClass: 'red',
|
||||
hasAlert: true
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '长寿区',
|
||||
stations: 136,
|
||||
supplies: 136,
|
||||
equipment: 12,
|
||||
equipmentClass: 'red',
|
||||
hasAlert: false
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '涪陵区',
|
||||
stations: 35,
|
||||
supplies: 35,
|
||||
equipment: 12,
|
||||
equipmentClass: 'red',
|
||||
hasAlert: false
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '合川区',
|
||||
stations: 46,
|
||||
supplies: 46,
|
||||
equipment: 12,
|
||||
equipmentClass: 'red',
|
||||
hasAlert: false
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: '永川区',
|
||||
stations: 35,
|
||||
supplies: 35,
|
||||
equipment: 12,
|
||||
equipmentClass: 'red',
|
||||
hasAlert: false
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.emergency-resources {
|
||||
background: url(../assets/img/emergency-panel-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
padding: 20px 30px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
position: relative;
|
||||
width: 293px;
|
||||
height: 49px;
|
||||
background: url(../assets/img/common-panel-header-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 24px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.title {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 22px;
|
||||
font-family: SourceHanSansCN-Medium, sans-serif;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.resource-table {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.table-header {
|
||||
height: 35px;
|
||||
background: url(../assets/img/emergency-table-header-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 16px;
|
||||
font-family: SourceHanSansCN-Medium, sans-serif;
|
||||
font-weight: 500;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
height: 35px;
|
||||
display: grid;
|
||||
grid-template-columns: 35px 1fr 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 14px;
|
||||
font-family: PingFangSC-Medium, sans-serif;
|
||||
font-weight: 500;
|
||||
|
||||
&.row-alt {
|
||||
background: url(../assets/img/common-table-row-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
&:not(.row-alt) {
|
||||
background: url(../assets/img/common-table-row-bg-alt.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.row-number {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(28, 161, 255, 0.44);
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 14px;
|
||||
font-family: FZZYJW--GB1-0, sans-serif;
|
||||
}
|
||||
|
||||
&:not(.row-alt) .row-number {
|
||||
background-color: rgba(28, 161, 255, 0.2);
|
||||
}
|
||||
|
||||
.district-name {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.count {
|
||||
text-align: center;
|
||||
font-family: PingFangSC-Semibold, sans-serif;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
|
||||
&.green {
|
||||
color: rgba(17, 187, 119, 1);
|
||||
}
|
||||
|
||||
&.orange {
|
||||
color: rgba(255, 128, 11, 1);
|
||||
}
|
||||
|
||||
&.red {
|
||||
color: rgba(255, 6, 36, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.equipment-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
|
||||
.alert-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
286
packages/screen/src/views/cockpit/components/MapCenter.vue
Normal file
@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<div class="map-center">
|
||||
<!-- 顶部功能按钮 -->
|
||||
<div class="top-buttons">
|
||||
<button
|
||||
v-for="button in topButtons"
|
||||
:key="button.id"
|
||||
class="func-button"
|
||||
:class="button.class"
|
||||
>
|
||||
<img :src="button.icon" :alt="button.label" class="button-icon" />
|
||||
<span>{{ button.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 地图标记点 (这里应该集成实际地图,现在用占位符) -->
|
||||
<div class="map-markers">
|
||||
<div
|
||||
v-for="marker in markers"
|
||||
:key="marker.id"
|
||||
class="marker"
|
||||
:style="{ left: marker.x + 'px', top: marker.y + 'px' }"
|
||||
>
|
||||
<img :src="marker.icon" :alt="marker.type" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部菜单 -->
|
||||
<!--
|
||||
<div class="bottom-menu">
|
||||
<div class="menu-left">
|
||||
<div class="menu-text">
|
||||
应急体系<br />
|
||||
重大事件处置<br />
|
||||
冰雪灾害专题<br />
|
||||
水毁专题<br />
|
||||
节假日专题
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="menu-center">
|
||||
<img src="../assets/img/map-center-menu-bg.png" alt="center menu" class="center-menu-bg" />
|
||||
<div class="center-menu-content">
|
||||
<div class="menu-tabs">
|
||||
<span>总体概览</span>
|
||||
<span>综合评价</span>
|
||||
</div>
|
||||
<div class="menu-icons">
|
||||
<div class="menu-icon-item">
|
||||
<img src="../assets/img/map-menu-icon-road-network.png" alt="icon" />
|
||||
<span>路网结构<br />优化</span>
|
||||
</div>
|
||||
<div class="menu-icon-item">
|
||||
<img src="../assets/img/map-menu-icon-construction.png" alt="icon" />
|
||||
<span>建设工程<br />智监</span>
|
||||
</div>
|
||||
<div class="menu-icon-item highlighted">
|
||||
<img src="../assets/img/map-menu-icon-safety.png" alt="icon" />
|
||||
<span>安全保通<br />服务</span>
|
||||
</div>
|
||||
<div class="menu-icon-item">
|
||||
<img src="../assets/img/map-menu-icon-maintenance.png" alt="icon" />
|
||||
<span>养护作业<br />智管</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
// 导入图片
|
||||
import btnServiceIcon from '../assets/img/map-btn-service.png'
|
||||
import btnWarningRoadIcon from '../assets/img/map-btn-warning-road.png'
|
||||
import btnWeatherIcon from '../assets/img/map-btn-weather.png'
|
||||
import markerServiceIcon from '../assets/img/map-marker-service.png'
|
||||
import markerWarningIcon from '../assets/img/map-marker-warning.png'
|
||||
import markerAlertIcon from '../assets/img/map-marker-alert.png'
|
||||
|
||||
const topButtons = ref([
|
||||
{
|
||||
id: 1,
|
||||
label: '服务站点',
|
||||
class: 'button-primary',
|
||||
icon: btnServiceIcon
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: '预警路段',
|
||||
class: 'button-default',
|
||||
icon: btnWarningRoadIcon
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
label: '气象预警',
|
||||
class: 'button-default',
|
||||
icon: btnWeatherIcon
|
||||
}
|
||||
])
|
||||
|
||||
const markers = ref([
|
||||
{ id: 1, type: 'service', x: 200, y: 150, icon: markerServiceIcon },
|
||||
{ id: 2, type: 'warning', x: 350, y: 200, icon: markerWarningIcon },
|
||||
{ id: 3, type: 'alert', x: 450, y: 300, icon: markerAlertIcon }
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.map-center {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.top-buttons {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.func-button {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border: 0.5px solid rgba(60, 174, 255, 1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.button-default {
|
||||
background-color: rgba(35, 74, 117, 1);
|
||||
}
|
||||
|
||||
&.button-primary {
|
||||
background-color: rgba(33, 73, 118, 1);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(60, 174, 255, 0.3);
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
width: 28px;
|
||||
height: 23px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 12px;
|
||||
font-family: SourceHanSansCN-Regular, sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.map-markers {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
.marker {
|
||||
position: absolute;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-menu {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.menu-left {
|
||||
.menu-text {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 14px;
|
||||
line-height: 39px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-center {
|
||||
position: relative;
|
||||
width: 350px;
|
||||
height: 156px;
|
||||
|
||||
.center-menu-bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.center-menu-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 15px 34px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.menu-tabs {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: auto;
|
||||
margin-bottom: 5px;
|
||||
|
||||
span {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 12px;
|
||||
font-family: Helvetica, "Microsoft YaHei", Arial, sans-serif;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-icons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
|
||||
.menu-icon-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
&.highlighted {
|
||||
background: url(../assets/img/map-menu-item-highlight-bg.png) no-repeat;
|
||||
background-size: contain;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 12px;
|
||||
font-family: Helvetica, "Microsoft YaHei", Arial, sans-serif;
|
||||
text-align: center;
|
||||
line-height: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
64
packages/screen/src/views/cockpit/components/PageHeader.vue
Normal file
@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<header class="page-header">
|
||||
<div class="header-bg">
|
||||
<h1 class="title">安全保通服务</h1>
|
||||
<button class="app-button">应用</button>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.page-header {
|
||||
height: 137px;
|
||||
background: url(../assets/img/header-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header-bg {
|
||||
background-image: url(../assets/img/header-title-bg.png);
|
||||
width: 100%;
|
||||
height: 107px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 51px;
|
||||
letter-spacing: 2.32px;
|
||||
font-family: FZLTDHJW--GB1-0, sans-serif;
|
||||
white-space: nowrap;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.app-button {
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 165px;
|
||||
height: 44px;
|
||||
background: url(../assets/img/header-btn-app-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 16px;
|
||||
font-family: SourceHanSansCN-Regular, sans-serif;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
320
packages/screen/src/views/cockpit/components/WeatherWarning.vue
Normal file
@ -0,0 +1,320 @@
|
||||
<template>
|
||||
<div class="weather-warning">
|
||||
<div class="panel-header">
|
||||
<span class="title">气象预警</span>
|
||||
</div>
|
||||
|
||||
<!-- 预警等级展示 -->
|
||||
<div class="warning-levels">
|
||||
<div
|
||||
v-for="level in warningLevels"
|
||||
:key="level.id"
|
||||
class="warning-level-item"
|
||||
>
|
||||
<!-- 六边形徽章容器 -->
|
||||
<div class="badge-container">
|
||||
<!-- 六边形徽章 -->
|
||||
<div class="level-badge">
|
||||
<img
|
||||
src="../assets/img/weather-badge-hexagon.png"
|
||||
alt="badge"
|
||||
class="badge-bg"
|
||||
/>
|
||||
<span class="level-count" :style="{ color: level.color }">{{ level.count }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 底座光圈(双背景图) -->
|
||||
<div class="glow-base"></div>
|
||||
</div>
|
||||
|
||||
<!-- 底部文字 -->
|
||||
<span class="level-name">{{ level.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 海拔800米以上气象情况 -->
|
||||
<div class="altitude-section">
|
||||
<div class="altitude-header">
|
||||
<img src="../assets/img/weather-altitude-icon.png" alt="icon" class="altitude-icon" />
|
||||
<span>海拔800米以上气象情况</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 区县列表 -->
|
||||
<div class="district-table">
|
||||
<div class="table-header">
|
||||
<span>所属区县</span>
|
||||
<span>公里数</span>
|
||||
<span>预警情况</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="(district, index) in districts"
|
||||
:key="district.id"
|
||||
class="table-row"
|
||||
:class="{ 'row-alt': index % 2 === 0 }"
|
||||
>
|
||||
<div class="row-indicator" :style="{ backgroundColor: district.warningColor }"></div>
|
||||
<span class="district-name">{{ district.name }}</span>
|
||||
<span class="district-km">{{ district.km }}</span>
|
||||
<span class="district-warning" :style="{ color: district.warningColor }">
|
||||
{{ district.warning }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
// 导入图片
|
||||
import glowTopIcon from '../assets/img/weather-badge-glow-top.png'
|
||||
import glowBottomIcon from '../assets/img/weather-badge-glow-bottom.png'
|
||||
|
||||
const warningLevels = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '蓝色预警',
|
||||
count: 320,
|
||||
color: 'rgba(132, 199, 255, 1)',
|
||||
glowTopIcon: glowTopIcon,
|
||||
glowBottomIcon: glowBottomIcon
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '黄色预警',
|
||||
count: 36,
|
||||
color: 'rgba(215, 209, 38, 1)',
|
||||
glowTopIcon: glowTopIcon,
|
||||
glowBottomIcon: glowBottomIcon
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '橙色预警',
|
||||
count: 2,
|
||||
color: 'rgba(255, 114, 0, 1)',
|
||||
glowTopIcon: glowTopIcon,
|
||||
glowBottomIcon: glowBottomIcon
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '红色预警',
|
||||
count: 1,
|
||||
color: 'rgba(255, 32, 0, 1)',
|
||||
glowTopIcon: glowTopIcon,
|
||||
glowBottomIcon: glowBottomIcon
|
||||
}
|
||||
])
|
||||
|
||||
const districts = ref([
|
||||
{ id: 1, name: '万州区', km: 32, warning: '红色预警', warningColor: 'rgba(229, 37, 42, 1)' },
|
||||
{ id: 2, name: '城口区', km: 11, warning: '橙色预警', warningColor: 'rgba(255, 114, 0, 1)' },
|
||||
{ id: 3, name: '长寿区', km: 136, warning: '黄色预警', warningColor: 'rgba(215, 209, 38, 1)' },
|
||||
{ id: 4, name: '涪陵区', km: 35, warning: '蓝色预警', warningColor: 'rgba(47, 156, 246, 1)' },
|
||||
{ id: 5, name: '合川区', km: 46, warning: '蓝色预警', warningColor: 'rgba(47, 156, 246, 1)' }
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.weather-warning {
|
||||
background: url(../assets/img/weather-panel-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
padding: 20px 30px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
position: relative;
|
||||
width: 293px;
|
||||
height: 49px;
|
||||
background: url(../assets/img/common-panel-header-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 24px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.title {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 22px;
|
||||
font-family: SourceHanSansCN-Medium, sans-serif;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.warning-levels {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
margin: 0 30px;
|
||||
padding: 0 27px;
|
||||
}
|
||||
|
||||
.warning-level-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
// 徽章和光圈容器
|
||||
.badge-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// 六边形徽章
|
||||
.level-badge {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 90px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
|
||||
.badge-bg {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.level-count {
|
||||
position: relative;
|
||||
font-size: 32px;
|
||||
font-family: DISPLAYFREETFB, sans-serif;
|
||||
font-weight: normal;
|
||||
z-index: 1;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 底座光圈(双背景图)
|
||||
.glow-base {
|
||||
width: 72px;
|
||||
height: 65px;
|
||||
margin-top: -10px;
|
||||
z-index: 1;
|
||||
|
||||
// 双背景图实现
|
||||
background-image:
|
||||
url('../assets/img/weather-badge-glow-top.png'), // 上层
|
||||
url('../assets/img/weather-badge-glow-bottom.png'); // 下层(更宽)
|
||||
|
||||
background-position:
|
||||
center 0px, // 上层:紧贴顶部
|
||||
center 10px; // 下层:稍微偏下
|
||||
|
||||
background-size:
|
||||
100% auto, // 上层:正常宽度
|
||||
120% auto; // 下层:更宽 20%
|
||||
|
||||
background-repeat: no-repeat, no-repeat;
|
||||
}
|
||||
|
||||
// 底部文字标签
|
||||
.level-name {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 14px;
|
||||
font-family: SourceHanSansCN-Bold, sans-serif;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.altitude-section {
|
||||
margin: 20px 0;
|
||||
|
||||
.altitude-header {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
background: url(../assets/img/weather-altitude-bar-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 0 10px;
|
||||
|
||||
.altitude-icon {
|
||||
width: 39px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 16px;
|
||||
font-family: PingFangSC-Semibold, sans-serif;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.district-table {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.table-header {
|
||||
height: 35px;
|
||||
background: url(../assets/img/weather-table-header-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 14px;
|
||||
font-family: SourceHanSansCN-Medium, sans-serif;
|
||||
font-weight: 500;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
height: 35px;
|
||||
display: grid;
|
||||
grid-template-columns: 9px 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 0 10px;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 14px;
|
||||
font-family: PingFangSC-Medium, sans-serif;
|
||||
font-weight: 500;
|
||||
|
||||
&.row-alt {
|
||||
background: url(../assets/img/common-table-row-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
&:not(.row-alt) {
|
||||
background: url(../assets/img/common-table-row-bg-alt.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.row-indicator {
|
||||
width: 9px;
|
||||
height: 33px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.district-name {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.district-km {
|
||||
text-align: center;
|
||||
font-family: PingFangSC-Semibold, sans-serif;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.district-warning {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
181
packages/screen/src/views/cockpit/components/YearStatistics.vue
Normal file
@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<div class="year-statistics">
|
||||
<div class="panel-header">
|
||||
<span class="title">往年统计</span>
|
||||
</div>
|
||||
|
||||
<!-- 图表标题和图例 -->
|
||||
<div class="chart-header">
|
||||
<span class="y-axis-label">数量</span>
|
||||
<span class="chart-title">冰雪事件对比趋势图</span>
|
||||
<div class="legend">
|
||||
<div class="legend-item">
|
||||
<div class="legend-color blue"></div>
|
||||
<span>限速通行</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color cyan"></div>
|
||||
<span>封闭交通</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图表区域 (这里需要集成图表库如ECharts) -->
|
||||
<div class="chart-container">
|
||||
<div class="y-axis">
|
||||
<span v-for="value in yAxisValues" :key="value">{{ value }}</span>
|
||||
</div>
|
||||
<div class="chart-area">
|
||||
<img src="../assets/img/statistics-chart-image.png" alt="chart" class="chart-image" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- X轴标签 -->
|
||||
<div class="x-axis">
|
||||
<span v-for="year in xAxisYears" :key="year">{{ year }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const yAxisValues = ref([6000, 5000, 4000, 3000, 2000, 1000, 0])
|
||||
const xAxisYears = ref([
|
||||
'2020~2021',
|
||||
'2021~2022',
|
||||
'2022~2023',
|
||||
'2023~2024',
|
||||
'2024~2025'
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.year-statistics {
|
||||
background: url(../assets/img/statistics-panel-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
padding: 20px 30px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
position: relative;
|
||||
width: 293px;
|
||||
height: 49px;
|
||||
background: url(../assets/img/common-panel-header-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 24px;
|
||||
margin-bottom: 25px;
|
||||
|
||||
.title {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 22px;
|
||||
font-family: SourceHanSansCN-Medium, sans-serif;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-bottom: 10px;
|
||||
padding-left: 10px;
|
||||
|
||||
.y-axis-label {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 16px;
|
||||
font-family: PingFangSC-Semibold, sans-serif;
|
||||
font-weight: 600;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
|
||||
.legend-color {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 4px;
|
||||
|
||||
&.blue {
|
||||
background-color: rgba(0, 143, 255, 1);
|
||||
}
|
||||
|
||||
&.cyan {
|
||||
background-color: rgba(0, 242, 245, 1);
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.y-axis {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 5px 0;
|
||||
|
||||
span {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 12px;
|
||||
font-family: HelveticaNeue, sans-serif;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-area {
|
||||
flex: 1;
|
||||
background: url(../assets/img/statistics-chart-area-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 15px;
|
||||
|
||||
.chart-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.x-axis {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 0 40px 0 60px;
|
||||
|
||||
span {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
11
packages/screen/src/views/cockpit/index.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<CockpitLayout />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import CockpitLayout from './components/CockpitLayout.vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 全局样式已在各组件中定义 */
|
||||
</style>
|
||||