This commit is contained in:
huangchenhao 2025-10-31 16:16:57 +08:00
commit 8c6d03ef65
48 changed files with 1700 additions and 8 deletions

View File

@ -19,6 +19,7 @@
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.0",
"vite": "^5.0.0",
"sass": "^1.70.0"
"sass": "^1.70.0",
"less": "^4.4.2"
}
}

View File

@ -7,6 +7,11 @@ const routes = [
name: 'Home',
component: () => import('../views/Home.vue')
},
{
path: '/cockpit',
name: 'Cockpit',
component: () => import('../views/cockpit/index.vue')
},
{
path: '/test',
name: 'test',

View 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
---
如有问题或建议,请联系开发团队。

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

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

View File

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

View File

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

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

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

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

View 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([
'20202021',
'20212022',
'20222023',
'20232024',
'20242025'
])
</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>

View File

@ -0,0 +1,11 @@
<template>
<CockpitLayout />
</template>
<script setup>
import CockpitLayout from './components/CockpitLayout.vue'
</script>
<style scoped>
/* 全局样式已在各组件中定义 */
</style>

148
pnpm-lock.yaml generated
View File

@ -54,7 +54,7 @@ importers:
devDependencies:
'@vitejs/plugin-vue':
specifier: ^5.0.0
version: 5.2.4(vite@5.4.20(sass@1.93.2)(terser@5.44.0))(vue@3.5.22)
version: 5.2.4(vite@5.4.20(less@4.4.2)(sass@1.93.2)(terser@5.44.0))(vue@3.5.22)
sass:
specifier: ^1.70.0
version: 1.93.2
@ -66,7 +66,7 @@ importers:
version: 0.26.0(@babel/parser@7.28.4)(rollup@4.52.4)(vue@3.5.22)
vite:
specifier: ^5.0.0
version: 5.4.20(sass@1.93.2)(terser@5.44.0)
version: 5.4.20(less@4.4.2)(sass@1.93.2)(terser@5.44.0)
packages/screen:
dependencies:
@ -91,13 +91,16 @@ importers:
devDependencies:
'@vitejs/plugin-vue':
specifier: ^5.0.0
version: 5.2.4(vite@5.4.20(sass@1.93.2)(terser@5.44.0))(vue@3.5.22)
version: 5.2.4(vite@5.4.20(less@4.4.2)(sass@1.93.2)(terser@5.44.0))(vue@3.5.22)
less:
specifier: ^4.4.2
version: 4.4.2
sass:
specifier: ^1.70.0
version: 1.93.2
vite:
specifier: ^5.0.0
version: 5.4.20(sass@1.93.2)(terser@5.44.0)
version: 5.4.20(less@4.4.2)(sass@1.93.2)(terser@5.44.0)
packages/shared:
dependencies:
@ -662,6 +665,9 @@ packages:
confbox@0.2.2:
resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
copy-anything@2.0.6:
resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
@ -702,6 +708,10 @@ packages:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
errno@0.1.8:
resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
hasBin: true
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
@ -784,6 +794,9 @@ packages:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
@ -796,6 +809,15 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
iconv-lite@0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
image-size@0.5.5:
resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==}
engines: {node: '>=0.10.0'}
hasBin: true
immutable@5.1.4:
resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==}
@ -819,9 +841,17 @@ packages:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
is-what@3.14.1:
resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}
js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
less@4.4.2:
resolution: {integrity: sha512-j1n1IuTX1VQjIy3tT7cyGbX7nvQOsFLoIqobZv4ttI5axP923gA44zUj6miiA6R5Aoms4sEGVIIcucXUbRI14g==}
engines: {node: '>=14'}
hasBin: true
local-pkg@0.4.3:
resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==}
engines: {node: '>=14'}
@ -850,6 +880,10 @@ packages:
magic-string@0.30.19:
resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
make-dir@2.1.0:
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
engines: {node: '>=6'}
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@ -873,6 +907,11 @@ packages:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'}
hasBin: true
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
@ -888,6 +927,11 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
needle@3.3.1:
resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==}
engines: {node: '>= 4.4.x'}
hasBin: true
node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
@ -898,6 +942,10 @@ packages:
normalize-wheel-es@1.2.0:
resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==}
parse-node-version@1.0.1:
resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
engines: {node: '>= 0.10'}
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
@ -915,6 +963,10 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
pify@4.0.1:
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
engines: {node: '>=6'}
pinia@2.3.1:
resolution: {integrity: sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==}
peerDependencies:
@ -937,6 +989,9 @@ packages:
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
prr@1.0.1:
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
quansync@0.2.11:
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
@ -971,14 +1026,24 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
sass@1.93.2:
resolution: {integrity: sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==}
engines: {node: '>=14.0.0'}
hasBin: true
sax@1.4.1:
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
scule@1.3.0:
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
semver@5.7.2:
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
hasBin: true
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@ -1429,9 +1494,9 @@ snapshots:
dependencies:
vue: 3.5.22
'@vitejs/plugin-vue@5.2.4(vite@5.4.20(sass@1.93.2)(terser@5.44.0))(vue@3.5.22)':
'@vitejs/plugin-vue@5.2.4(vite@5.4.20(less@4.4.2)(sass@1.93.2)(terser@5.44.0))(vue@3.5.22)':
dependencies:
vite: 5.4.20(sass@1.93.2)(terser@5.44.0)
vite: 5.4.20(less@4.4.2)(sass@1.93.2)(terser@5.44.0)
vue: 3.5.22
'@vue/compiler-core@3.5.22':
@ -1588,6 +1653,10 @@ snapshots:
confbox@0.2.2: {}
copy-anything@2.0.6:
dependencies:
is-what: 3.14.1
csstype@3.1.3: {}
dayjs@1.11.18: {}
@ -1634,6 +1703,11 @@ snapshots:
entities@4.5.0: {}
errno@0.1.8:
dependencies:
prr: 1.0.1
optional: true
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
@ -1740,6 +1814,9 @@ snapshots:
gopd@1.2.0: {}
graceful-fs@4.2.11:
optional: true
has-symbols@1.1.0: {}
has-tostringtag@1.0.2:
@ -1750,6 +1827,14 @@ snapshots:
dependencies:
function-bind: 1.1.2
iconv-lite@0.6.3:
dependencies:
safer-buffer: 2.1.2
optional: true
image-size@0.5.5:
optional: true
immutable@5.1.4: {}
is-binary-path@2.1.0:
@ -1768,8 +1853,24 @@ snapshots:
is-number@7.0.0: {}
is-what@3.14.1: {}
js-tokens@9.0.1: {}
less@4.4.2:
dependencies:
copy-anything: 2.0.6
parse-node-version: 1.0.1
tslib: 2.3.0
optionalDependencies:
errno: 0.1.8
graceful-fs: 4.2.11
image-size: 0.5.5
make-dir: 2.1.0
mime: 1.6.0
needle: 3.3.1
source-map: 0.6.1
local-pkg@0.4.3: {}
local-pkg@0.5.1:
@ -1797,6 +1898,12 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
make-dir@2.1.0:
dependencies:
pify: 4.0.1
semver: 5.7.2
optional: true
math-intrinsics@1.1.0: {}
memoize-one@6.0.0: {}
@ -1814,6 +1921,9 @@ snapshots:
dependencies:
mime-db: 1.52.0
mime@1.6.0:
optional: true
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.2
@ -1829,6 +1939,12 @@ snapshots:
nanoid@3.3.11: {}
needle@3.3.1:
dependencies:
iconv-lite: 0.6.3
sax: 1.4.1
optional: true
node-addon-api@7.1.1:
optional: true
@ -1836,6 +1952,8 @@ snapshots:
normalize-wheel-es@1.2.0: {}
parse-node-version@1.0.1: {}
path-parse@1.0.7: {}
pathe@2.0.3: {}
@ -1846,6 +1964,9 @@ snapshots:
picomatch@4.0.3: {}
pify@4.0.1:
optional: true
pinia@2.3.1(vue@3.5.22):
dependencies:
'@vue/devtools-api': 6.6.4
@ -1874,6 +1995,9 @@ snapshots:
proxy-from-env@1.1.0: {}
prr@1.0.1:
optional: true
quansync@0.2.11: {}
queue-microtask@1.2.3: {}
@ -1926,6 +2050,9 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
safer-buffer@2.1.2:
optional: true
sass@1.93.2:
dependencies:
chokidar: 4.0.3
@ -1934,8 +2061,14 @@ snapshots:
optionalDependencies:
'@parcel/watcher': 2.5.1
sax@1.4.1:
optional: true
scule@1.3.0: {}
semver@5.7.2:
optional: true
source-map-js@1.2.1: {}
source-map-support@0.5.21:
@ -2034,13 +2167,14 @@ snapshots:
'@vue/shared': 3.5.22
vue: 3.5.22
vite@5.4.20(sass@1.93.2)(terser@5.44.0):
vite@5.4.20(less@4.4.2)(sass@1.93.2)(terser@5.44.0):
dependencies:
esbuild: 0.21.5
postcss: 8.5.6
rollup: 4.52.4
optionalDependencies:
fsevents: 2.3.3
less: 4.4.2
sass: 1.93.2
terser: 5.44.0