实现页面类型管理
@ -110,7 +110,4 @@ getStyle(type, item, status) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
实现功能计划:
|
当前项目用于青岛机场正式版
|
||||||
1.我目前需要脱离后台进行页面展示,给增加完善的模拟数据;
|
|
||||||
2.找一个线上地址测试地图的平台概览页面显示
|
|
||||||
3.添加新图层、绘制路线,引入绘制线、绘制面、绘制点
|
|
||||||
|
|||||||
93
public/dianziweilan.json
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": {
|
||||||
|
"type": "Polygon",
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
120.07177603096052,
|
||||||
|
36.370812533360755
|
||||||
|
],
|
||||||
|
[
|
||||||
|
120.08385709134191,
|
||||||
|
36.33999243140878
|
||||||
|
],
|
||||||
|
[
|
||||||
|
120.08565563667813,
|
||||||
|
36.34060862711547
|
||||||
|
],
|
||||||
|
[
|
||||||
|
120.08669982868061,
|
||||||
|
36.34062878164365
|
||||||
|
],
|
||||||
|
[
|
||||||
|
120.07455220429061,
|
||||||
|
36.37193239994289
|
||||||
|
],
|
||||||
|
[
|
||||||
|
120.07234631337181,
|
||||||
|
36.37156118999518
|
||||||
|
],
|
||||||
|
[
|
||||||
|
120.07177603096052,
|
||||||
|
36.370812533360755
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"type": "polygon",
|
||||||
|
"area": 671992.2620217785,
|
||||||
|
"perimeter": 6459.222679044949,
|
||||||
|
"vertices": 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": {
|
||||||
|
"type": "Polygon",
|
||||||
|
"coordinates": [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
120.09357854630166,
|
||||||
|
36.38111581893121
|
||||||
|
],
|
||||||
|
[
|
||||||
|
120.09150927608138,
|
||||||
|
36.38118099552585
|
||||||
|
],
|
||||||
|
[
|
||||||
|
120.09017484057917,
|
||||||
|
36.380485019173435
|
||||||
|
],
|
||||||
|
[
|
||||||
|
120.10265440053722,
|
||||||
|
36.34926707805199
|
||||||
|
],
|
||||||
|
[
|
||||||
|
120.10434101414667,
|
||||||
|
36.34985392006327
|
||||||
|
],
|
||||||
|
[
|
||||||
|
120.1053224475362,
|
||||||
|
36.35065874503395
|
||||||
|
],
|
||||||
|
[
|
||||||
|
120.09357854630166,
|
||||||
|
36.38111581893121
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"type": "polygon",
|
||||||
|
"area": 731220.4002154289,
|
||||||
|
"perimeter": 6460.346045623342,
|
||||||
|
"vertices": 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
4044
public/roadTest.json
Normal file
BIN
src/assets/images/add.png
Normal file
|
After Width: | Height: | Size: 486 B |
BIN
src/assets/images/aircraft.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 509 KiB |
BIN
src/assets/images/login-background.png
Normal file
|
After Width: | Height: | Size: 435 KiB |
BIN
src/assets/images/noPeopleCar.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/images/sub_icon.png
Normal file
|
After Width: | Height: | Size: 881 B |
BIN
src/assets/images/subheading.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
@ -209,7 +209,7 @@ function handleCurrentChange(val) {
|
|||||||
// width: 100%;
|
// width: 100%;
|
||||||
|
|
||||||
// 覆盖Element Plus分页组件的默认样式
|
// 覆盖Element Plus分页组件的默认样式
|
||||||
// ::v-deep(.el-pagination) {
|
// :deep(.el-pagination) {
|
||||||
// padding: 0;
|
// padding: 0;
|
||||||
// background: none;
|
// background: none;
|
||||||
// border: 1px solid #0a4273;
|
// border: 1px solid #0a4273;
|
||||||
|
|||||||
@ -157,11 +157,11 @@ function exportData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-input__wrapper:hover) {
|
:deep(.el-input__wrapper:hover) {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select) {
|
:deep(.el-select) {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
width: 140px !important;
|
width: 140px !important;
|
||||||
// 修改el-select的输入框的border
|
// 修改el-select的输入框的border
|
||||||
@ -169,7 +169,7 @@ function exportData() {
|
|||||||
--el-border-color: #343744 !important;
|
--el-border-color: #343744 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-input__wrapper) {
|
:deep(.el-input__wrapper) {
|
||||||
background: #343744 !important;
|
background: #343744 !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
@ -179,11 +179,11 @@ function exportData() {
|
|||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__wrapper.is-focused) {
|
:deep(.el-select__wrapper.is-focused) {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__wrapper) {
|
:deep(.el-select__wrapper) {
|
||||||
background: #343744 !important;
|
background: #343744 !important;
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
@ -194,11 +194,11 @@ function exportData() {
|
|||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__placeholder) {
|
:deep(.el-select__placeholder) {
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-button) {
|
:deep(.el-button) {
|
||||||
background: #343744;
|
background: #343744;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|||||||
@ -28,10 +28,13 @@
|
|||||||
<div class="search-buttons">
|
<div class="search-buttons">
|
||||||
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
|
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
|
||||||
<el-button class="reset-btn" @click="handleReset">重置</el-button>
|
<el-button class="reset-btn" @click="handleReset">重置</el-button>
|
||||||
|
<el-button class="export-btn" @click="handleExport">导出</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<el-button class="export-btn" @click="handleExport">导出</el-button>
|
<!-- 两个Tab切换的图标1.png 2.png -->
|
||||||
|
<!-- 默认1.png 显示当前表格点击2.png 显示卡片样式 -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -135,11 +135,11 @@ function handlePagination({ page: newPage, limit: newLimit }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 输入框和下拉框样式
|
// 输入框和下拉框样式
|
||||||
::v-deep(.el-input__wrapper:hover) {
|
:deep(.el-input__wrapper:hover) {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select) {
|
:deep(.el-select) {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
width: 160px !important;
|
width: 160px !important;
|
||||||
// 修改el-select的输入框的border
|
// 修改el-select的输入框的border
|
||||||
@ -147,11 +147,11 @@ function handlePagination({ page: newPage, limit: newLimit }) {
|
|||||||
--el-border-color: #343744 !important;
|
--el-border-color: #343744 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__wrapper.is-focused) {
|
:deep(.el-select__wrapper.is-focused) {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__wrapper) {
|
:deep(.el-select__wrapper) {
|
||||||
background: #343744 !important;
|
background: #343744 !important;
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
@ -161,7 +161,7 @@ function handlePagination({ page: newPage, limit: newLimit }) {
|
|||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-input__wrapper) {
|
:deep(.el-input__wrapper) {
|
||||||
background: #343744 !important;
|
background: #343744 !important;
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
@ -170,7 +170,7 @@ function handlePagination({ page: newPage, limit: newLimit }) {
|
|||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__placeholder) {
|
:deep(.el-select__placeholder) {
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,8 +8,6 @@
|
|||||||
<OpenLayersZoomControl
|
<OpenLayersZoomControl
|
||||||
:map="map"
|
:map="map"
|
||||||
:resetView="resetView"
|
:resetView="resetView"
|
||||||
:routeLayerVisible="routeLayerVisible"
|
|
||||||
@layerToggle="toggleRouteLayer"
|
|
||||||
@compass="compass"
|
@compass="compass"
|
||||||
@zoomIn="zoomIn"
|
@zoomIn="zoomIn"
|
||||||
@zoomOut="zoomOut"
|
@zoomOut="zoomOut"
|
||||||
@ -25,13 +23,16 @@
|
|||||||
<!-- 比例尺控件(传递map对象) -->
|
<!-- 比例尺控件(传递map对象) -->
|
||||||
<OpenLayersScaleControl :map="map" />
|
<OpenLayersScaleControl :map="map" />
|
||||||
|
|
||||||
|
<!-- 路线绘制控件 -->
|
||||||
|
<RouteDrawControl :map="map" ref="routeDrawControlRef" v-if="map" />
|
||||||
|
|
||||||
<!-- 插槽,允许父组件添加其他控件 -->
|
<!-- 插槽,允许父组件添加其他控件 -->
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted, watch } from "vue";
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
import Map from "ol/Map";
|
import Map from "ol/Map";
|
||||||
import View from "ol/View";
|
import View from "ol/View";
|
||||||
import TileLayer from "ol/layer/Tile";
|
import TileLayer from "ol/layer/Tile";
|
||||||
@ -47,18 +48,7 @@ import OpenLayersMapInfo from "./controls/OpenLayersMapInfo.vue";
|
|||||||
import OverviewMap from 'ol/control/OverviewMap';
|
import OverviewMap from 'ol/control/OverviewMap';
|
||||||
import ScaleLine from 'ol/control/ScaleLine';
|
import ScaleLine from 'ol/control/ScaleLine';
|
||||||
import OpenLayersScaleControl from "./controls/OpenLayersScaleControl.vue";
|
import OpenLayersScaleControl from "./controls/OpenLayersScaleControl.vue";
|
||||||
import OSM from 'ol/source/OSM';
|
import RouteDrawControl from "./controls/RouteDrawControl.vue";
|
||||||
import VectorLayer from 'ol/layer/Vector';
|
|
||||||
import VectorSource from 'ol/source/Vector';
|
|
||||||
import Feature from 'ol/Feature';
|
|
||||||
import Point from 'ol/geom/Point';
|
|
||||||
import LineString from 'ol/geom/LineString';
|
|
||||||
import Polygon from 'ol/geom/Polygon';
|
|
||||||
import Style from 'ol/style/Style';
|
|
||||||
import Icon from 'ol/style/Icon';
|
|
||||||
import Stroke from 'ol/style/Stroke';
|
|
||||||
import Fill from 'ol/style/Fill';
|
|
||||||
import { fromLonLat } from 'ol/proj';
|
|
||||||
|
|
||||||
// 注册 EPSG:4528 投影
|
// 注册 EPSG:4528 投影
|
||||||
proj4.defs(
|
proj4.defs(
|
||||||
@ -92,12 +82,6 @@ const minZoom = 7; // 最小缩放级别
|
|||||||
const maxZoom = 15; // 最大缩放级别
|
const maxZoom = 15; // 最大缩放级别
|
||||||
const initialZoom = 8; // 设置为7.5,在7和8之间
|
const initialZoom = 8; // 设置为7.5,在7和8之间
|
||||||
|
|
||||||
const routeLayerVisible = ref(false); // 默认不显示路线图层
|
|
||||||
|
|
||||||
function toggleRouteLayer() {
|
|
||||||
routeLayerVisible.value = !routeLayerVisible.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 检查是否有保存的默认视图
|
// 检查是否有保存的默认视图
|
||||||
let savedView = null;
|
let savedView = null;
|
||||||
@ -130,19 +114,6 @@ onMounted(() => {
|
|||||||
rotation: savedView ? savedView.rotation : rotation, // 使用保存的旋转角度或默认值
|
rotation: savedView ? savedView.rotation : rotation, // 使用保存的旋转角度或默认值
|
||||||
});
|
});
|
||||||
|
|
||||||
// 使用OSM底图
|
|
||||||
const baseLayer = new TileLayer({
|
|
||||||
source: new OSM()
|
|
||||||
});
|
|
||||||
|
|
||||||
// 创建矢量图层
|
|
||||||
const vectorSource = new VectorSource();
|
|
||||||
const vectorLayer = new VectorLayer({
|
|
||||||
source: vectorSource,
|
|
||||||
zIndex: 2,
|
|
||||||
visible: routeLayerVisible.value
|
|
||||||
});
|
|
||||||
|
|
||||||
// 创建地图,与.ol3页面完全一致
|
// 创建地图,与.ol3页面完全一致
|
||||||
map.value = new Map({
|
map.value = new Map({
|
||||||
target: mapContainer.value,
|
target: mapContainer.value,
|
||||||
@ -157,7 +128,18 @@ onMounted(() => {
|
|||||||
constrainResolution: true,
|
constrainResolution: true,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
layers: [baseLayer, vectorLayer],
|
layers: [
|
||||||
|
// 使用TileLayer包裹TileSuperMapRest
|
||||||
|
new TileLayer({
|
||||||
|
source: new TileSuperMapRest({
|
||||||
|
url: "http://10.96.3.10:8090/iserver/services/map-QDJC_DT-GX3/rest/maps/QDJC2DZT",
|
||||||
|
projection: "EPSG:4528",
|
||||||
|
tileGrid: tileGrid,
|
||||||
|
wrapX: false,
|
||||||
|
attributions: "", // 空字符串移除属性信息
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
view: view,
|
view: view,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -206,73 +188,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
// 示例:添加一个点
|
|
||||||
const pointFeature = new Feature({
|
|
||||||
geometry: new Point([13394416, 4136655])
|
|
||||||
});
|
|
||||||
pointFeature.setStyle(new Style({
|
|
||||||
image: new Icon({
|
|
||||||
src: 'https://openlayers.org/en/latest/examples/data/icon.png',
|
|
||||||
scale: 0.7
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
vectorSource.addFeature(pointFeature);
|
|
||||||
|
|
||||||
// 示例:添加一条线
|
|
||||||
const lineFeature = new Feature({
|
|
||||||
geometry: new LineString([
|
|
||||||
[13394416, 4136655],
|
|
||||||
[13395000, 4137000]
|
|
||||||
])
|
|
||||||
});
|
|
||||||
lineFeature.setStyle(new Style({
|
|
||||||
stroke: new Stroke({
|
|
||||||
color: 'blue',
|
|
||||||
width: 2
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
vectorSource.addFeature(lineFeature);
|
|
||||||
|
|
||||||
// 示例:添加一个面
|
|
||||||
const polygonFeature = new Feature({
|
|
||||||
geometry: new Polygon([[
|
|
||||||
[13394416, 4136655],
|
|
||||||
[13395000, 4136655],
|
|
||||||
[13395000, 4137000],
|
|
||||||
[13394416, 4137000],
|
|
||||||
[13394416, 4136655]
|
|
||||||
]])
|
|
||||||
});
|
|
||||||
polygonFeature.setStyle(new Style({
|
|
||||||
stroke: new Stroke({ color: 'red', width: 2 }),
|
|
||||||
fill: new Fill({ color: 'rgba(255,0,0,0.2)' })
|
|
||||||
}));
|
|
||||||
vectorSource.addFeature(polygonFeature);
|
|
||||||
|
|
||||||
// --- 随机生成青岛附近的路线 ---
|
|
||||||
function randomLngLat() {
|
|
||||||
const lng = 120.1 + Math.random() * 0.4;
|
|
||||||
const lat = 35.9 + Math.random() * 0.4;
|
|
||||||
return [lng, lat];
|
|
||||||
}
|
|
||||||
// 生成3条路线,每条5~8个点
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
const points = [];
|
|
||||||
const count = 5 + Math.floor(Math.random() * 4);
|
|
||||||
for (let j = 0; j < count; j++) {
|
|
||||||
points.push(fromLonLat(randomLngLat()));
|
|
||||||
}
|
|
||||||
const lineFeature = new Feature({
|
|
||||||
geometry: new LineString(points)
|
|
||||||
});
|
|
||||||
lineFeature.setStyle(new Style({
|
|
||||||
stroke: new Stroke({
|
|
||||||
color: ['red', 'blue', 'green'][i % 3],
|
|
||||||
width: 3
|
|
||||||
})
|
|
||||||
}));
|
|
||||||
vectorSource.addFeature(lineFeature);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保地图完全加载后再次调整视图
|
// 确保地图完全加载后再次调整视图
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -299,10 +215,6 @@ onMounted(() => {
|
|||||||
// 当地图旋转时,不需要额外操作,OpenLayers内部会自动处理拖拽方向
|
// 当地图旋转时,不需要额外操作,OpenLayers内部会自动处理拖拽方向
|
||||||
// 但我们可以在这里做一些自定义处理,比如更新UI等
|
// 但我们可以在这里做一些自定义处理,比如更新UI等
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(routeLayerVisible, (val) => {
|
|
||||||
vectorLayer.setVisible(val);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
|||||||
904
src/components/map/controls/LayerSwitcher.vue
Normal file
@ -0,0 +1,904 @@
|
|||||||
|
<template>
|
||||||
|
<div class="layer-switcher">
|
||||||
|
<div class="layer-icon" @click="toggleLayerPanel"></div>
|
||||||
|
|
||||||
|
<!-- 图层面板 -->
|
||||||
|
<div class="layer-panel" v-if="showPanel">
|
||||||
|
<!-- 标签页切换 -->
|
||||||
|
<div class="panel-tabs">
|
||||||
|
<div
|
||||||
|
class="tab"
|
||||||
|
:class="{ active: activeTab === 'icon' }"
|
||||||
|
@click="activeTab = 'icon'"
|
||||||
|
>
|
||||||
|
图标
|
||||||
|
</div>
|
||||||
|
<!-- <div
|
||||||
|
class="tab"
|
||||||
|
:class="{ active: activeTab === 'text' }"
|
||||||
|
@click="activeTab = 'text'"
|
||||||
|
>
|
||||||
|
文本
|
||||||
|
</div> -->
|
||||||
|
<div
|
||||||
|
class="tab"
|
||||||
|
:class="{ active: activeTab === 'road' }"
|
||||||
|
@click="activeTab = 'road'"
|
||||||
|
>
|
||||||
|
道路
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-content" v-if="activeTab === 'icon'">
|
||||||
|
|
||||||
|
<!-- 航空器图层 -->
|
||||||
|
<div class="layer-group">
|
||||||
|
<div class="group-title">航空器</div>
|
||||||
|
<div class="layer-grid">
|
||||||
|
<div class="layer-item" v-for="layer in exampleVehicleLayers.filter(l => l.id !== 'no-people-car')" :key="'example-'+layer.id">
|
||||||
|
<label class="checkbox-container">
|
||||||
|
<input type="checkbox" :checked="layer.visible" @change="toggleExampleLayer(layer)">
|
||||||
|
<span class="checkmark"></span>
|
||||||
|
<span class="layer-name">{{ layer.name }}</span>
|
||||||
|
<img :src="layer.icon" class="layer-icon-preview" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 无人车全选 -->
|
||||||
|
<div class="layer-group">
|
||||||
|
<div class="group-title">无人车</div>
|
||||||
|
<div class="layer-grid-full">
|
||||||
|
<div class="layer-item">
|
||||||
|
<label class="checkbox-container">
|
||||||
|
<input type="checkbox" :checked="getExampleLayerById('no-people-car').visible" @change="toggleExampleLayer(getExampleLayerById('no-people-car'))">
|
||||||
|
<span class="checkmark"></span>
|
||||||
|
<span class="layer-name">无人车全选</span>
|
||||||
|
<img :src="noPeopleCarIcon" class="layer-icon-preview" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 常用车辆 -->
|
||||||
|
<div class="layer-group">
|
||||||
|
<div class="group-title">常用车辆</div>
|
||||||
|
<div class="layer-grid">
|
||||||
|
<div class="layer-item">
|
||||||
|
<label class="checkbox-container">
|
||||||
|
<input type="checkbox" :checked="getLayerById('driving-car').visible" @change="toggleVehicleLayer(getLayerById('driving-car'))">
|
||||||
|
<span class="checkmark"></span>
|
||||||
|
<span class="layer-name">驱鸟车</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="layer-item">
|
||||||
|
<label class="checkbox-container">
|
||||||
|
<input type="checkbox" :checked="getLayerById('push-car').visible" @change="toggleVehicleLayer(getLayerById('push-car'))">
|
||||||
|
<span class="checkmark"></span>
|
||||||
|
<span class="layer-name">摆渡车</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="layer-item">
|
||||||
|
<label class="checkbox-container">
|
||||||
|
<input type="checkbox" :checked="getLayerById('military-car').visible" @change="toggleVehicleLayer(getLayerById('military-car'))">
|
||||||
|
<span class="checkmark"></span>
|
||||||
|
<span class="layer-name">牵引车</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 其他车辆图层 -->
|
||||||
|
<div class="layer-group">
|
||||||
|
<div class="group-title">其他车辆</div>
|
||||||
|
<div class="layer-grid">
|
||||||
|
<div class="layer-item" v-for="layer in otherVehicleLayers" :key="'vehicle-'+layer.id">
|
||||||
|
<label class="checkbox-container">
|
||||||
|
<input type="checkbox" :checked="layer.visible" @change="toggleVehicleLayer(layer)">
|
||||||
|
<span class="checkmark"></span>
|
||||||
|
<span class="layer-name">{{ layer.name }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-content" v-else-if="activeTab === 'road'">
|
||||||
|
<div class="layer-group">
|
||||||
|
<div class="group-title">道路图层</div>
|
||||||
|
<div class="layer-grid-full">
|
||||||
|
<div class="layer-item">
|
||||||
|
<label class="checkbox-container">
|
||||||
|
<input type="checkbox" :checked="!hideRoadLayer" @change="toggleHideRoadLayer" />
|
||||||
|
<span class="checkmark"></span>
|
||||||
|
<span class="layer-name">电子围栏</span>
|
||||||
|
<svg class="layer-icon-preview" width="24" height="24" viewBox="0 0 1024 1024"><path d="M356.246145 681.56286c-68.156286-41.949414-107.246583-103.84102-107.246583-169.805384 0-65.966411 39.090297-127.860063 107.246583-169.809477 12.046361-7.414877 15.800871-23.190165 8.385994-35.236526-7.413853-12.046361-23.191188-15.801894-35.236526-8.387018-39.640836 24.399713-72.539106 56.044434-95.137801 91.515297-23.86657 37.461193-36.481889 79.620385-36.481889 121.917724 0 42.297338 12.615319 84.454484 36.481889 121.914654 22.598694 35.469839 55.496965 67.11456 95.137801 91.51325 4.185322 2.576685 8.821923 3.804652 13.400195 3.804652 8.598842 0 16.998139-4.329609 21.836331-12.190647C372.047016 704.752002 368.291482 688.976714 356.246145 681.56286z" fill="#409eff"/></svg>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<!-- 添加自定义路线图层选项 -->
|
||||||
|
<div class="layer-item">
|
||||||
|
<label class="checkbox-container">
|
||||||
|
<input type="checkbox" v-model="showCustomRoadLayer" />
|
||||||
|
<span class="checkmark"></span>
|
||||||
|
<span class="layer-name">路线图</span>
|
||||||
|
<svg class="layer-icon-preview" width="24" height="24" viewBox="0 0 1024 1024"><path d="M356.246145 681.56286c-68.156286-41.949414-107.246583-103.84102-107.246583-169.805384 0-65.966411 39.090297-127.860063 107.246583-169.809477 12.046361-7.414877 15.800871-23.190165 8.385994-35.236526-7.413853-12.046361-23.191188-15.801894-35.236526-8.387018-39.640836 24.399713-72.539106 56.044434-95.137801 91.515297-23.86657 37.461193-36.481889 79.620385-36.481889 121.917724 0 42.297338 12.615319 84.454484 36.481889 121.914654 22.598694 35.469839 55.496965 67.11456 95.137801 91.51325 4.185322 2.576685 8.821923 3.804652 13.400195 3.804652 8.598842 0 16.998139-4.329609 21.836331-12.190647C372.047016 704.752002 368.291482 688.976714 356.246145 681.56286z" fill="#FF5722"/></svg>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-content" v-else>
|
||||||
|
<!-- 文本标签页内容 -->
|
||||||
|
<div class="layer-group">
|
||||||
|
<div class="group-title">文本样式</div>
|
||||||
|
<div class="style-selector">
|
||||||
|
<div class="style-item">
|
||||||
|
<div class="style-label">默认样式</div>
|
||||||
|
<div class="radio-box" :class="{ active: selectedTextStyle === 'default' }" @click="selectTextStyle('default')"></div>
|
||||||
|
</div>
|
||||||
|
<div class="style-item">
|
||||||
|
<div class="style-label">蓝色样式</div>
|
||||||
|
<div class="radio-box blue" :class="{ active: selectedTextStyle === 'blue' }" @click="selectTextStyle('blue')"></div>
|
||||||
|
</div>
|
||||||
|
<div class="style-item">
|
||||||
|
<div class="style-label">白色样式</div>
|
||||||
|
<div class="radio-box white" :class="{ active: selectedTextStyle === 'white' }" @click="selectTextStyle('white')"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch, computed, onBeforeMount, onUnmounted } from 'vue';
|
||||||
|
import { Vector as VectorSource } from 'ol/source';
|
||||||
|
import { Vector as VectorLayer } from 'ol/layer';
|
||||||
|
import { Style, Icon, Stroke, Fill } from 'ol/style';
|
||||||
|
import Feature from 'ol/Feature';
|
||||||
|
import Point from 'ol/geom/Point';
|
||||||
|
import GeoJSON from 'ol/format/GeoJSON';
|
||||||
|
|
||||||
|
// 导入车辆图标
|
||||||
|
import car1Icon from '../../../assets/images/Aircraft.png';
|
||||||
|
import car2Icon from '../../../assets/images/noPeopleCar.png';
|
||||||
|
import noPeopleCarIcon from '../../../assets/images/noPeopleCar.png';
|
||||||
|
|
||||||
|
// 定义props接收地图实例
|
||||||
|
const props = defineProps({
|
||||||
|
map: Object
|
||||||
|
});
|
||||||
|
|
||||||
|
// 响应式状态
|
||||||
|
const showPanel = ref(false);
|
||||||
|
const activeTab = ref('icon'); // 默认选中图标标签页
|
||||||
|
const selectedTextStyle = ref('white'); // 默认选中白色样式
|
||||||
|
|
||||||
|
// 新增:道路tab相关状态
|
||||||
|
const hideRoadLayer = ref(false); // 默认不隐藏(即显示)
|
||||||
|
const showRoadLayer = computed(() => !hideRoadLayer.value); // 计算属性,与hideRoadLayer相反
|
||||||
|
let roadVectorLayer = null;
|
||||||
|
|
||||||
|
// 新增:自定义路线图层状态
|
||||||
|
const showCustomRoadLayer = ref(true); // 默认显示
|
||||||
|
let customRoadVectorLayer = null;
|
||||||
|
|
||||||
|
// 车辆图层 - 默认选中
|
||||||
|
const exampleVehicleLayers = ref([
|
||||||
|
{
|
||||||
|
id: 'car1-layer',
|
||||||
|
name: '滑入航空器',
|
||||||
|
visible: true,
|
||||||
|
icon: car1Icon,
|
||||||
|
layer: null,
|
||||||
|
features: [
|
||||||
|
{ id: 'car1-1', position: [40507699.051041, 4026243.105796], name: '滑入航空器1' },
|
||||||
|
{ id: 'car1-2', position: [40508097.909698, 4026315.085762], name: '滑入航空器2' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'car1-layer1',
|
||||||
|
name: '滑出航空器',
|
||||||
|
visible: true,
|
||||||
|
icon: car1Icon,
|
||||||
|
layer: null,
|
||||||
|
features: [
|
||||||
|
{ id: 'car2-1', position: [4.0507e7, 4024000], name: '滑出航空器1' },
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'no-people-car',
|
||||||
|
name: '无人车全选',
|
||||||
|
visible: true,
|
||||||
|
icon: noPeopleCarIcon,
|
||||||
|
layer: null,
|
||||||
|
features: [
|
||||||
|
{ id: 'no-people-car-1', position: [40508392.141630, 4026279.431719], name: '无人车1' },
|
||||||
|
{ id: 'no-people-car-2', position: [40507625.995559, 4025622.462289], name: '无人车2' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 车辆图层 - 设置默认显示状态
|
||||||
|
const vehicleLayers = ref([
|
||||||
|
{ id: 'slip-in-plane', name: '滑入航空器', visible: true, layer: null },
|
||||||
|
{ id: 'slip-out-plane', name: '滑出航空器', visible: true, layer: null },
|
||||||
|
{ id: 'uav', name: '无人车全选', visible: true, layer: null },
|
||||||
|
{ id: 'driving-car', name: '驱鸟车', visible: true, layer: null },
|
||||||
|
{ id: 'push-car', name: '摆渡车', visible: true, layer: null },
|
||||||
|
{ id: 'military-car', name: '牵引车', visible: true, layer: null },
|
||||||
|
{ id: 'special-car', name: '特勤车全选', visible: false, layer: null },
|
||||||
|
{ id: 'fire-car', name: '消防车', visible: false, layer: null },
|
||||||
|
{ id: 'water-car', name: '清水车', visible: false, layer: null },
|
||||||
|
{ id: 'patrol-car', name: '巡逻车', visible: false, layer: null },
|
||||||
|
{ id: 'emergency-car', name: '急救车', visible: false, layer: null },
|
||||||
|
{ id: 'container-car', name: '客梯车', visible: false, layer: null },
|
||||||
|
{ id: 'small-car', name: '小型客车', visible: false, layer: null },
|
||||||
|
{ id: 'maintenance-car', name: '维修车', visible: false, layer: null },
|
||||||
|
{ id: 'tool-car', name: '工具车', visible: false, layer: null },
|
||||||
|
{ id: 'tour-car', name: '巡游车', visible: false, layer: null },
|
||||||
|
{ id: 'sewage-car', name: '污水车', visible: false, layer: null },
|
||||||
|
{ id: 'road-car', name: '道路车', visible: false, layer: null },
|
||||||
|
{ id: 'garbage-car', name: '垃圾车', visible: false, layer: null },
|
||||||
|
{ id: 'police-car', name: '警察车', visible: false, layer: null },
|
||||||
|
{ id: 'forklift', name: '叉车', visible: false, layer: null }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 文本样式
|
||||||
|
const textStyles = ref([
|
||||||
|
{ id: 'slip-in-plane', name: '滑入航空器', visible: true, layer: null },
|
||||||
|
{ id: 'slip-out-plane', name: '滑出航空器', visible: true, layer: null },
|
||||||
|
{ id: 'uav', name: '无人车全选', visible: true, layer: null },
|
||||||
|
{ id: 'driving-car', name: '驱鸟车', visible: true, layer: null },
|
||||||
|
{ id: 'push-car', name: '摆渡车', visible: true, layer: null },
|
||||||
|
{ id: 'military-car', name: '军引车', visible: true, layer: null },
|
||||||
|
{ id: 'special-car', name: '特勤车全选', visible: false, layer: null },
|
||||||
|
{ id: 'fire-car', name: '消防车', visible: false, layer: null },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 计算属性:其他车辆图层(排除常用车辆)
|
||||||
|
const otherVehicleLayers = computed(() => {
|
||||||
|
const excludedIds = ['uav', 'driving-car', 'push-car', 'military-car', 'slip-in-plane', 'slip-out-plane'];
|
||||||
|
return vehicleLayers.value.filter(layer => !excludedIds.includes(layer.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 通过ID获取图层
|
||||||
|
function getLayerById(id) {
|
||||||
|
return vehicleLayers.value.find(layer => layer.id === id) || { visible: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过ID获取示例图层
|
||||||
|
function getExampleLayerById(id) {
|
||||||
|
return exampleVehicleLayers.value.find(layer => layer.id === id) || { visible: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 向父组件发出事件
|
||||||
|
const emit = defineEmits(['layerChange']);
|
||||||
|
|
||||||
|
// 切换图层面板显示
|
||||||
|
function toggleLayerPanel() {
|
||||||
|
showPanel.value = !showPanel.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换示例图层
|
||||||
|
function toggleExampleLayer(layer) {
|
||||||
|
layer.visible = !layer.visible;
|
||||||
|
|
||||||
|
if (layer.layer) {
|
||||||
|
layer.layer.setVisible(layer.visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发出图层变化事件
|
||||||
|
emit('layerChange', {
|
||||||
|
type: 'exampleLayer',
|
||||||
|
layer: layer
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化示例图层
|
||||||
|
function initExampleLayers() {
|
||||||
|
if (!props.map) return;
|
||||||
|
|
||||||
|
// 为每个示例图层创建矢量图层
|
||||||
|
exampleVehicleLayers.value.forEach(layerConfig => {
|
||||||
|
// 创建矢量数据源
|
||||||
|
const source = new VectorSource();
|
||||||
|
|
||||||
|
// 创建样式
|
||||||
|
const style = new Style({
|
||||||
|
image: new Icon({
|
||||||
|
src: layerConfig.icon,
|
||||||
|
scale: 1.5,
|
||||||
|
anchor: [0.5, 0.5]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加车辆特征
|
||||||
|
layerConfig.features.forEach(vehicle => {
|
||||||
|
const feature = new Feature({
|
||||||
|
geometry: new Point(vehicle.position),
|
||||||
|
name: vehicle.name,
|
||||||
|
id: vehicle.id
|
||||||
|
});
|
||||||
|
|
||||||
|
feature.setStyle(style);
|
||||||
|
source.addFeature(feature);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建矢量图层
|
||||||
|
const vectorLayer = new VectorLayer({
|
||||||
|
source: source,
|
||||||
|
visible: layerConfig.visible,
|
||||||
|
zIndex: 10 // 确保在基础图层之上
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置图层引用
|
||||||
|
layerConfig.layer = vectorLayer;
|
||||||
|
|
||||||
|
// 添加到地图
|
||||||
|
props.map.addLayer(vectorLayer);
|
||||||
|
|
||||||
|
// 确保可见性设置正确
|
||||||
|
vectorLayer.setVisible(layerConfig.visible);
|
||||||
|
|
||||||
|
console.log(`初始化图层: ${layerConfig.id}, 可见性: ${layerConfig.visible}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换业务图层
|
||||||
|
function toggleVehicleLayer(layer) {
|
||||||
|
layer.visible = !layer.visible;
|
||||||
|
|
||||||
|
if (layer.layer) {
|
||||||
|
layer.layer.setVisible(layer.visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发出图层变化事件
|
||||||
|
emit('layerChange', {
|
||||||
|
type: 'vehicleLayer',
|
||||||
|
layer: layer
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换文本样式
|
||||||
|
function toggleTextStyle(textStyle) {
|
||||||
|
textStyle.visible = !textStyle.visible;
|
||||||
|
|
||||||
|
// 发出文本样式变化事件
|
||||||
|
emit('layerChange', {
|
||||||
|
type: 'textStyle',
|
||||||
|
style: textStyle
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择文本样式
|
||||||
|
function selectTextStyle(style) {
|
||||||
|
selectedTextStyle.value = style;
|
||||||
|
|
||||||
|
// 发出文本样式选择事件
|
||||||
|
emit('layerChange', {
|
||||||
|
type: 'selectTextStyle',
|
||||||
|
style: style
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化图层
|
||||||
|
function initLayers() {
|
||||||
|
if (!props.map) return;
|
||||||
|
|
||||||
|
// 初始化示例图层
|
||||||
|
initExampleLayers();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置图层可见性的方法(供外部调用)
|
||||||
|
function setLayerVisibility(layerId, visible) {
|
||||||
|
// 查找并设置示例图层可见性
|
||||||
|
const exampleLayer = exampleVehicleLayers.value.find(layer => layer.id === layerId);
|
||||||
|
if (exampleLayer) {
|
||||||
|
exampleLayer.visible = visible;
|
||||||
|
if (exampleLayer.layer) {
|
||||||
|
exampleLayer.layer.setVisible(visible);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找并设置车辆图层可见性
|
||||||
|
const vehicleLayer = vehicleLayers.value.find(layer => layer.id === layerId);
|
||||||
|
if (vehicleLayer) {
|
||||||
|
vehicleLayer.visible = visible;
|
||||||
|
|
||||||
|
// 发出图层变化事件
|
||||||
|
emit('layerChange', {
|
||||||
|
type: 'vehicleLayer',
|
||||||
|
layer: vehicleLayer
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换电子围栏显示状态
|
||||||
|
function toggleHideRoadLayer() {
|
||||||
|
hideRoadLayer.value = !hideRoadLayer.value;
|
||||||
|
console.log('toggleHideRoadLayer: hideRoadLayer =', hideRoadLayer.value, 'showRoadLayer =', showRoadLayer.value);
|
||||||
|
|
||||||
|
if (showRoadLayer.value) {
|
||||||
|
addRoadLayer();
|
||||||
|
} else {
|
||||||
|
removeRoadLayer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时初始化
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.map) {
|
||||||
|
initLayers();
|
||||||
|
// 初始化自定义路线图层
|
||||||
|
loadCustomRoadLayer();
|
||||||
|
// 初始化电子围栏图层 - 默认显示
|
||||||
|
if (showRoadLayer.value) {
|
||||||
|
addRoadLayer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听地图实例变化
|
||||||
|
watch(() => props.map, (newMap) => {
|
||||||
|
if (newMap) {
|
||||||
|
initLayers();
|
||||||
|
// 初始化自定义路线图层
|
||||||
|
loadCustomRoadLayer();
|
||||||
|
// 初始化电子围栏图层
|
||||||
|
if (showRoadLayer.value) {
|
||||||
|
addRoadLayer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 向外暴露方法
|
||||||
|
defineExpose({
|
||||||
|
setLayerVisibility
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadCustomRoadLayer() {
|
||||||
|
// 先移除已存在的自定义路线图层,避免重复
|
||||||
|
removeCustomRoadLayer();
|
||||||
|
if (!props.map) return;
|
||||||
|
try {
|
||||||
|
// 使用相对路径加载自定义路线文件
|
||||||
|
const res = await fetch('./roadTest.json');
|
||||||
|
const geojson = await res.json();
|
||||||
|
const source = new VectorSource({
|
||||||
|
features: new GeoJSON().readFeatures(geojson, {
|
||||||
|
dataProjection: 'EPSG:4326',
|
||||||
|
featureProjection: props.map.getView().getProjection()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
customRoadVectorLayer = new VectorLayer({
|
||||||
|
source,
|
||||||
|
style: new Style({
|
||||||
|
stroke: new Stroke({ color: '#888', width: 4 })
|
||||||
|
}),
|
||||||
|
zIndex: 2, // 确保在标准道路图层之上
|
||||||
|
visible: showCustomRoadLayer.value
|
||||||
|
});
|
||||||
|
|
||||||
|
props.map.addLayer(customRoadVectorLayer);
|
||||||
|
console.log('loadCustomRoadLayer: 已添加自定义路线图层', customRoadVectorLayer);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('loadCustomRoadLayer: 加载或添加自定义路线图层失败', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeCustomRoadLayer() {
|
||||||
|
if (!props.map) return;
|
||||||
|
let removed = false;
|
||||||
|
// 获取所有图层,查找类型为 VectorLayer 且 zIndex 为 2 的(即自定义路线图层)
|
||||||
|
const layers = props.map.getLayers().getArray();
|
||||||
|
for (let i = layers.length - 1; i >= 0; i--) {
|
||||||
|
const lyr = layers[i];
|
||||||
|
if (lyr instanceof VectorLayer && lyr.getZIndex && lyr.getZIndex() === 2) {
|
||||||
|
props.map.removeLayer(lyr);
|
||||||
|
removed = true;
|
||||||
|
console.log('removeCustomRoadLayer: 已移除自定义路线图层', lyr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customRoadVectorLayer = null;
|
||||||
|
if (!removed) {
|
||||||
|
console.log('removeCustomRoadLayer: 没有找到可移除的自定义路线图层');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听自定义路线显示状态变化
|
||||||
|
watch(showCustomRoadLayer, (val) => {
|
||||||
|
console.log('showCustomRoadLayer变化:', val);
|
||||||
|
if (val) {
|
||||||
|
if (customRoadVectorLayer) {
|
||||||
|
customRoadVectorLayer.setVisible(true);
|
||||||
|
} else {
|
||||||
|
loadCustomRoadLayer();
|
||||||
|
}
|
||||||
|
} else if (customRoadVectorLayer) {
|
||||||
|
customRoadVectorLayer.setVisible(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function addRoadLayer() {
|
||||||
|
// 先移除已存在的道路图层,避免重复
|
||||||
|
removeRoadLayer();
|
||||||
|
if (!props.map) return;
|
||||||
|
try {
|
||||||
|
// 使用相对路径,确保在任何部署环境下都能正确加载
|
||||||
|
const res = await fetch('./dianziweilan.json');
|
||||||
|
const geojson = await res.json();
|
||||||
|
const source = new VectorSource({
|
||||||
|
features: new GeoJSON().readFeatures(geojson, {
|
||||||
|
dataProjection: 'EPSG:4326',
|
||||||
|
featureProjection: props.map.getView().getProjection()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建两个图层样式 - 一个用于普通状态,一个用于闪烁状态
|
||||||
|
const normalStyle = new Style({
|
||||||
|
stroke: new Stroke({ color: 'rgba(0, 0, 0, 0.4)', width: 4 }),
|
||||||
|
fill: new Fill({ color: 'rgba(0, 0, 0, 0.5)' })
|
||||||
|
});
|
||||||
|
|
||||||
|
const flashStyle = new Style({
|
||||||
|
stroke: new Stroke({ color: 'rgba(255, 0, 0, 0.8)', width: 4 }),
|
||||||
|
fill: new Fill({ color: 'rgba(255, 0, 0, 0.3)' })
|
||||||
|
});
|
||||||
|
|
||||||
|
roadVectorLayer = new VectorLayer({
|
||||||
|
source,
|
||||||
|
style: normalStyle,
|
||||||
|
zIndex: 1,
|
||||||
|
className: 'fence-layer'
|
||||||
|
});
|
||||||
|
|
||||||
|
props.map.addLayer(roadVectorLayer);
|
||||||
|
console.log('addRoadLayer: 已添加电子围栏图层', roadVectorLayer);
|
||||||
|
|
||||||
|
// 开始闪烁效果
|
||||||
|
startFenceFlashing(roadVectorLayer, normalStyle, flashStyle);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error('addRoadLayer: 加载或添加电子围栏图层失败', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let flashingInterval = null;
|
||||||
|
|
||||||
|
// 启动闪烁效果
|
||||||
|
function startFenceFlashing(layer, normalStyle, flashStyle) {
|
||||||
|
// 清除可能存在的旧定时器
|
||||||
|
if (flashingInterval) {
|
||||||
|
clearInterval(flashingInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 闪烁状态标志
|
||||||
|
let isFlashing = false;
|
||||||
|
|
||||||
|
// 定时切换样式
|
||||||
|
flashingInterval = setInterval(() => {
|
||||||
|
if (!layer) return;
|
||||||
|
|
||||||
|
layer.setStyle(isFlashing ? normalStyle : flashStyle);
|
||||||
|
isFlashing = !isFlashing;
|
||||||
|
}, 800); // 每800毫秒切换一次样式
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止闪烁效果
|
||||||
|
function stopFenceFlashing() {
|
||||||
|
if (flashingInterval) {
|
||||||
|
clearInterval(flashingInterval);
|
||||||
|
flashingInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除电子围栏图层时也停止闪烁
|
||||||
|
function removeRoadLayer() {
|
||||||
|
if (!props.map) return;
|
||||||
|
stopFenceFlashing();
|
||||||
|
let removed = false;
|
||||||
|
// 获取所有图层,查找类型为 VectorLayer 且 zIndex 为 1 的(即道路图层)
|
||||||
|
const layers = props.map.getLayers().getArray();
|
||||||
|
for (let i = layers.length - 1; i >= 0; i--) {
|
||||||
|
const lyr = layers[i];
|
||||||
|
if (lyr instanceof VectorLayer && lyr.getZIndex && lyr.getZIndex() === 1) {
|
||||||
|
props.map.removeLayer(lyr);
|
||||||
|
removed = true;
|
||||||
|
console.log('removeRoadLayer: 已移除道路图层', lyr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
roadVectorLayer = null;
|
||||||
|
if (!removed) {
|
||||||
|
console.log('removeRoadLayer: 没有找到可移除的道路图层');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(showRoadLayer, (val) => {
|
||||||
|
console.log('showRoadLayer变化:', val);
|
||||||
|
if (val) addRoadLayer();
|
||||||
|
else removeRoadLayer();
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
removeRoadLayer();
|
||||||
|
removeCustomRoadLayer();
|
||||||
|
stopFenceFlashing();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.layer-switcher {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-panel {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 50px;
|
||||||
|
width:397px;
|
||||||
|
background-color: #424851;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.4);
|
||||||
|
z-index: 3100;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.panel-tabs {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid #303850;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
padding: 0 20px;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
color: #F0F0F0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab:hover {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background-color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-content {
|
||||||
|
padding: 10px;
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-title {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
border-bottom: 1px solid #999999;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto auto;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-grid-full {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-item {
|
||||||
|
padding: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义复选框样式 */
|
||||||
|
.checkbox-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container input {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid #999999;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container:hover input ~ .checkmark {
|
||||||
|
background-color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container input:checked ~ .checkmark {
|
||||||
|
background-color: #0096ff;
|
||||||
|
border-color: #0078cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container input:checked ~ .checkmark:after {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container .checkmark:after {
|
||||||
|
left: 6px;
|
||||||
|
top: 2px;
|
||||||
|
width: 5px;
|
||||||
|
height: 10px;
|
||||||
|
border: solid white;
|
||||||
|
border-width: 0 2px 2px 0;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-name {
|
||||||
|
margin-left: 5px;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图层图标预览 */
|
||||||
|
.layer-icon-preview {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-left: 8px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动条样式 */
|
||||||
|
.panel-content::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-content::-webkit-scrollbar-track {
|
||||||
|
background: #555555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-content::-webkit-scrollbar-thumb {
|
||||||
|
background: #999999;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-content::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #bbbbbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文本样式选择器 */
|
||||||
|
.style-selector {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.style-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.style-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-box {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid #999999;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-box:hover {
|
||||||
|
border-color: #bbbbbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-box.active {
|
||||||
|
border-color: #0096ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-box.active::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background-color: #0096ff;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-box.blue {
|
||||||
|
border-color: #0096ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-box.blue.active::after {
|
||||||
|
background-color: #0096ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-box.white {
|
||||||
|
border-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-box.white.active::after {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 道路json样式 */
|
||||||
|
.road-json {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #333;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.road-json pre {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="map-info">
|
<div class="map-info">
|
||||||
<input type="text" readonly :value="xyText" />
|
<!-- <input type="text" readonly :value="xyText" />
|
||||||
<input type="text" readonly :value="wgs84Text" />
|
<input type="text" readonly :value="wgs84Text" /> -->
|
||||||
<!-- <input type="text" readonly :value="pixelText" /> -->
|
<!-- <input type="text" readonly :value="pixelText" /> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -39,7 +39,7 @@ function handleMouseMove(event) {
|
|||||||
// 3. 转换为WGS84
|
// 3. 转换为WGS84
|
||||||
try {
|
try {
|
||||||
const [lng, lat] = transform(coordinate, 'EPSG:4528', 'EPSG:4326');
|
const [lng, lat] = transform(coordinate, 'EPSG:4528', 'EPSG:4326');
|
||||||
//console.log('WGS84坐标:', lng, lat);
|
// console.log('WGS84坐标:', lng, lat);
|
||||||
wgs84Text.value = `WGS84: 经度=${lng.toFixed(6)}°, 纬度=${lat.toFixed(6)}°`;
|
wgs84Text.value = `WGS84: 经度=${lng.toFixed(6)}°, 纬度=${lat.toFixed(6)}°`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error('坐标转换失败:', error);
|
// console.error('坐标转换失败:', error);
|
||||||
|
|||||||
@ -1,25 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="zoom-control">
|
<div class="zoom-control">
|
||||||
<el-popover
|
<LayerSwitcher class="layer-switcher" :map="map" @layerChange="onLayerChange" ref="layerSwitcherRef" />
|
||||||
placement="right"
|
|
||||||
width="160"
|
|
||||||
trigger="click"
|
|
||||||
v-model:visible="popoverVisible"
|
|
||||||
popper-class="layer-popover"
|
|
||||||
>
|
|
||||||
<el-checkbox
|
|
||||||
:model-value="props.routeLayerVisible"
|
|
||||||
@change="onLayerToggle"
|
|
||||||
>道路图层</el-checkbox>
|
|
||||||
<template #reference>
|
|
||||||
<div
|
|
||||||
class="layer-toggle"
|
|
||||||
:class="{ active: props.routeLayerVisible }"
|
|
||||||
@click="handleLayerBtnClick"
|
|
||||||
title="图层控制"
|
|
||||||
></div>
|
|
||||||
</template>
|
|
||||||
</el-popover>
|
|
||||||
<div class="compass" @click="onCompass"></div>
|
<div class="compass" @click="onCompass"></div>
|
||||||
<div class="zoom-in" @click="onZoomIn"></div>
|
<div class="zoom-in" @click="onZoomIn"></div>
|
||||||
<div class="zoom-out" @click="onZoomOut"></div>
|
<div class="zoom-out" @click="onZoomOut"></div>
|
||||||
@ -28,8 +10,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { ElPopover, ElCheckbox } from 'element-plus';
|
import LayerSwitcher from './LayerSwitcher.vue';
|
||||||
// import LayerSwitcher from './LayerSwitcher.vue';
|
|
||||||
|
|
||||||
// 定义props接受地图实例
|
// 定义props接受地图实例
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -37,10 +18,6 @@ const props = defineProps({
|
|||||||
resetView: {
|
resetView: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: null
|
default: null
|
||||||
},
|
|
||||||
routeLayerVisible: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -48,9 +25,7 @@ const props = defineProps({
|
|||||||
const layerSwitcherRef = ref(null);
|
const layerSwitcherRef = ref(null);
|
||||||
|
|
||||||
// 向父组件发出事件
|
// 向父组件发出事件
|
||||||
const emit = defineEmits(['compass', 'zoomIn', 'zoomOut', 'layerChange', 'layerToggle']);
|
const emit = defineEmits(['compass', 'zoomIn', 'zoomOut', 'layerChange']);
|
||||||
|
|
||||||
const popoverVisible = ref(false);
|
|
||||||
|
|
||||||
// 指南针/重置视图
|
// 指南针/重置视图
|
||||||
function onCompass() {
|
function onCompass() {
|
||||||
@ -82,14 +57,6 @@ function onLayerChange(layerInfo) {
|
|||||||
emit('layerChange', layerInfo);
|
emit('layerChange', layerInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onLayerToggle() {
|
|
||||||
emit('layerToggle');
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLayerBtnClick() {
|
|
||||||
popoverVisible.value = !popoverVisible.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导出方法供外部调用
|
// 导出方法供外部调用
|
||||||
defineExpose({
|
defineExpose({
|
||||||
// 设置图层可见性的方法
|
// 设置图层可见性的方法
|
||||||
@ -116,22 +83,6 @@ defineExpose({
|
|||||||
gap: 25px;
|
gap: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layer-toggle {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
background: url("../../../assets/images/layer.png") no-repeat;
|
|
||||||
background-size: 100% 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 3000;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
box-sizing: border-box;
|
|
||||||
transition: border 0.2s;
|
|
||||||
}
|
|
||||||
.layer-toggle.active {
|
|
||||||
border: 2px solid #409eff;
|
|
||||||
box-shadow: 0 0 8px #409eff44;
|
|
||||||
}
|
|
||||||
.compass {
|
.compass {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
@ -140,6 +91,15 @@ defineExpose({
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 3000;
|
z-index: 3000;
|
||||||
}
|
}
|
||||||
|
.layer-switcher{
|
||||||
|
z-index: 3000;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background: url("../../../assets/images/layer.png") no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.zoom-in {
|
.zoom-in {
|
||||||
background: url("../../../assets/images/zoomOut.png") no-repeat;
|
background: url("../../../assets/images/zoomOut.png") no-repeat;
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
@ -148,6 +108,7 @@ defineExpose({
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoom-out {
|
.zoom-out {
|
||||||
background: url("../../../assets/images/zoomIn.png") no-repeat;
|
background: url("../../../assets/images/zoomIn.png") no-repeat;
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
@ -159,7 +120,7 @@ defineExpose({
|
|||||||
|
|
||||||
/* 响应式调整 */
|
/* 响应式调整 */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.zoom-in, .zoom-out, .compass, .layer-toggle {
|
.zoom-in, .zoom-out, .compass {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
background-size: 80%;
|
background-size: 80%;
|
||||||
@ -167,9 +128,4 @@ defineExpose({
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.layer-popover {
|
|
||||||
padding: 12px 16px;
|
|
||||||
min-width: 120px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
712
src/components/map/controls/RouteDrawControl.vue
Normal file
@ -0,0 +1,712 @@
|
|||||||
|
<template>
|
||||||
|
<div class="route-draw-control">
|
||||||
|
<div class="draw-tools">
|
||||||
|
<div class="tool-button" @click="startDrawLine" :class="{ active: isDrawing && drawType === 'LineString' }">
|
||||||
|
<span>绘制路线</span>
|
||||||
|
</div>
|
||||||
|
<div class="tool-button" @click="startDrawPolygon" :class="{ active: isDrawing && drawType === 'Polygon' }">
|
||||||
|
<span>绘制点线面</span>
|
||||||
|
</div>
|
||||||
|
<div class="tool-button" @click="clearDraw">
|
||||||
|
<span>清除</span>
|
||||||
|
</div>
|
||||||
|
<div class="tool-button" @click="exportRouteData">
|
||||||
|
<span>导出数据</span>
|
||||||
|
</div>
|
||||||
|
<div class="tool-button" @click="showImportDialog = true">
|
||||||
|
<span>导入数据</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧点线面信息展示面板 -->
|
||||||
|
<div class="polygon-panel" v-if="showPolygonPanel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<span>点线面信息</span>
|
||||||
|
<span class="close-btn" @click="closePolygonPanel">×</span>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
|
<div class="info-item" v-for="(polygon, index) in polygons" :key="index">
|
||||||
|
<div class="info-header">点线面 {{ index + 1 }}</div>
|
||||||
|
<div class="info-content">
|
||||||
|
<p>面积: {{ polygon.area.toFixed(2) }} 平方米</p>
|
||||||
|
<p>周长: {{ polygon.perimeter.toFixed(2) }} 米</p>
|
||||||
|
<p>顶点数: {{ polygon.vertices }}</p>
|
||||||
|
<button class="focus-btn" @click="focusOnPolygon(polygon)">定位</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="no-data" v-if="polygons.length === 0">
|
||||||
|
暂无点线面数据,请先绘制点线面
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 路线信息面板 -->
|
||||||
|
<div class="route-lines-panel" v-if="showRouteLinesPanel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<span>路线信息</span>
|
||||||
|
<span class="close-btn" @click="closeRouteLinesPanel">×</span>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
|
<div class="info-item" v-for="(line, index) in routeLines" :key="index">
|
||||||
|
<div class="info-header">路线 {{ index + 1 }}</div>
|
||||||
|
<div class="info-content">
|
||||||
|
<p>长度: {{ line.length.toFixed(2) }} 米</p>
|
||||||
|
<p>顶点数: {{ line.vertices }}</p>
|
||||||
|
<button class="focus-btn" @click="focusOnRoute(line)">定位</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="no-data" v-if="routeLines.length === 0">
|
||||||
|
暂无路线数据,请先绘制路线
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 导入JSON对话框 -->
|
||||||
|
<div class="import-dialog" v-if="showImportDialog">
|
||||||
|
<div class="dialog-content">
|
||||||
|
<div class="dialog-header">
|
||||||
|
<h3>导入数据</h3>
|
||||||
|
<span class="close-btn" @click="showImportDialog = false">×</span>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-body">
|
||||||
|
<textarea v-model="importJsonText" placeholder="请粘贴JSON数据"></textarea>
|
||||||
|
<div class="file-upload">
|
||||||
|
<label for="jsonFileInput">或者上传JSON文件</label>
|
||||||
|
<input type="file" id="jsonFileInput" accept=".json" @change="handleFileUpload">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<button @click="showImportDialog = false">取消</button>
|
||||||
|
<button @click="importRouteData" class="primary-btn">导入</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
||||||
|
import Draw from 'ol/interaction/Draw';
|
||||||
|
import { Vector as VectorSource } from 'ol/source';
|
||||||
|
import { Vector as VectorLayer } from 'ol/layer';
|
||||||
|
import { Style, Stroke, Fill, Circle } from 'ol/style';
|
||||||
|
import { getArea, getLength } from 'ol/sphere';
|
||||||
|
import { LineString, Polygon } from 'ol/geom';
|
||||||
|
import GeoJSON from 'ol/format/GeoJSON';
|
||||||
|
import Feature from 'ol/Feature';
|
||||||
|
|
||||||
|
// 定义props接收地图实例
|
||||||
|
const props = defineProps({
|
||||||
|
map: Object
|
||||||
|
});
|
||||||
|
|
||||||
|
// 响应式状态
|
||||||
|
const isDrawing = ref(false);
|
||||||
|
const drawType = ref('');
|
||||||
|
const showPolygonPanel = ref(false);
|
||||||
|
const showRouteLinesPanel = ref(false);
|
||||||
|
const polygons = ref([]);
|
||||||
|
const routeLines = ref([]);
|
||||||
|
const showImportDialog = ref(false);
|
||||||
|
const importJsonText = ref('');
|
||||||
|
|
||||||
|
// 图层和交互
|
||||||
|
let drawInteraction = null;
|
||||||
|
let vectorSource = null;
|
||||||
|
let vectorLayer = null;
|
||||||
|
|
||||||
|
// 初始化绘图图层
|
||||||
|
function initDrawLayer() {
|
||||||
|
if (!props.map) return;
|
||||||
|
|
||||||
|
// 创建矢量数据源
|
||||||
|
vectorSource = new VectorSource();
|
||||||
|
|
||||||
|
// 创建矢量图层
|
||||||
|
vectorLayer = new VectorLayer({
|
||||||
|
source: vectorSource,
|
||||||
|
style: new Style({
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(0, 150, 255, 0.3)'
|
||||||
|
}),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#0096ff',
|
||||||
|
width: 3
|
||||||
|
}),
|
||||||
|
image: new Circle({
|
||||||
|
radius: 7,
|
||||||
|
fill: new Fill({
|
||||||
|
color: '#0096ff'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加图层到地图
|
||||||
|
props.map.addLayer(vectorLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始绘制线
|
||||||
|
function startDrawLine() {
|
||||||
|
clearDraw();
|
||||||
|
isDrawing.value = true;
|
||||||
|
drawType.value = 'LineString';
|
||||||
|
showRouteLinesPanel.value = true;
|
||||||
|
|
||||||
|
drawInteraction = new Draw({
|
||||||
|
source: vectorSource,
|
||||||
|
type: 'LineString',
|
||||||
|
style: new Style({
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#ff0000',
|
||||||
|
width: 3
|
||||||
|
}),
|
||||||
|
image: new Circle({
|
||||||
|
radius: 5,
|
||||||
|
fill: new Fill({
|
||||||
|
color: '#ff0000'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听绘制结束事件
|
||||||
|
drawInteraction.on('drawend', function(event) {
|
||||||
|
const feature = event.feature;
|
||||||
|
const geometry = feature.getGeometry();
|
||||||
|
|
||||||
|
// 计算路线长度
|
||||||
|
const length = getLength(geometry);
|
||||||
|
const vertices = geometry.getCoordinates().length;
|
||||||
|
|
||||||
|
// 设置特征属性
|
||||||
|
feature.setProperties({
|
||||||
|
type: 'route',
|
||||||
|
length: length,
|
||||||
|
vertices: vertices
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加到路线列表
|
||||||
|
routeLines.value.push({
|
||||||
|
id: Date.now(),
|
||||||
|
feature: feature,
|
||||||
|
length: length,
|
||||||
|
vertices: vertices,
|
||||||
|
extent: geometry.getExtent()
|
||||||
|
});
|
||||||
|
|
||||||
|
isDrawing.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
props.map.addInteraction(drawInteraction);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始绘制多边形(点线面)
|
||||||
|
function startDrawPolygon() {
|
||||||
|
clearDraw();
|
||||||
|
isDrawing.value = true;
|
||||||
|
drawType.value = 'Polygon';
|
||||||
|
showPolygonPanel.value = true;
|
||||||
|
|
||||||
|
drawInteraction = new Draw({
|
||||||
|
source: vectorSource,
|
||||||
|
type: 'Polygon',
|
||||||
|
style: new Style({
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255, 165, 0, 0.3)'
|
||||||
|
}),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#ffa500',
|
||||||
|
width: 3
|
||||||
|
}),
|
||||||
|
image: new Circle({
|
||||||
|
radius: 5,
|
||||||
|
fill: new Fill({
|
||||||
|
color: '#ffa500'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听绘制结束事件
|
||||||
|
drawInteraction.on('drawend', function(event) {
|
||||||
|
const feature = event.feature;
|
||||||
|
const geometry = feature.getGeometry();
|
||||||
|
|
||||||
|
// 计算面积和周长
|
||||||
|
const area = getArea(geometry);
|
||||||
|
const perimeter = getLength(geometry.getLinearRing(0));
|
||||||
|
const vertices = geometry.getLinearRing(0).getCoordinates().length - 1; // 减去重复的闭合点
|
||||||
|
|
||||||
|
// 设置特征属性
|
||||||
|
feature.setProperties({
|
||||||
|
type: 'polygon',
|
||||||
|
area: area,
|
||||||
|
perimeter: perimeter,
|
||||||
|
vertices: vertices
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加到点线面列表
|
||||||
|
polygons.value.push({
|
||||||
|
id: Date.now(),
|
||||||
|
feature: feature,
|
||||||
|
area: area,
|
||||||
|
perimeter: perimeter,
|
||||||
|
vertices: vertices,
|
||||||
|
extent: geometry.getExtent()
|
||||||
|
});
|
||||||
|
|
||||||
|
isDrawing.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
props.map.addInteraction(drawInteraction);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除绘制
|
||||||
|
function clearDraw() {
|
||||||
|
if (drawInteraction) {
|
||||||
|
props.map.removeInteraction(drawInteraction);
|
||||||
|
drawInteraction = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vectorSource) {
|
||||||
|
vectorSource.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
isDrawing.value = false;
|
||||||
|
polygons.value = [];
|
||||||
|
routeLines.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭点线面面板
|
||||||
|
function closePolygonPanel() {
|
||||||
|
showPolygonPanel.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭路线面板
|
||||||
|
function closeRouteLinesPanel() {
|
||||||
|
showRouteLinesPanel.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定位到指定点线面
|
||||||
|
function focusOnPolygon(polygon) {
|
||||||
|
if (props.map && polygon.extent) {
|
||||||
|
props.map.getView().fit(polygon.extent, {
|
||||||
|
padding: [50, 50, 50, 50],
|
||||||
|
duration: 1000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定位到指定路线
|
||||||
|
function focusOnRoute(route) {
|
||||||
|
if (props.map && route.extent) {
|
||||||
|
props.map.getView().fit(route.extent, {
|
||||||
|
padding: [50, 50, 50, 50],
|
||||||
|
duration: 1000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出路线数据为GeoJSON
|
||||||
|
function exportRouteData() {
|
||||||
|
if (!vectorSource || vectorSource.getFeatures().length === 0) {
|
||||||
|
alert('没有可导出的数据,请先绘制路线或点线面');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建GeoJSON格式化器
|
||||||
|
const format = new GeoJSON();
|
||||||
|
|
||||||
|
// 获取所有特征
|
||||||
|
const features = vectorSource.getFeatures();
|
||||||
|
|
||||||
|
// 将特征转换为GeoJSON
|
||||||
|
const geojsonObj = format.writeFeaturesObject(features, {
|
||||||
|
dataProjection: 'EPSG:4326', // 输出为WGS84坐标
|
||||||
|
featureProjection: props.map.getView().getProjection() // 从地图投影转换
|
||||||
|
});
|
||||||
|
|
||||||
|
// 转换为JSON字符串
|
||||||
|
const geojsonStr = JSON.stringify(geojsonObj, null, 2);
|
||||||
|
|
||||||
|
// 创建下载链接
|
||||||
|
const blob = new Blob([geojsonStr], { type: 'application/json' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = 'roadTest.json'; // 固定文件名为roadTest.json
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
console.log('数据已导出为GeoJSON,文件名:roadTest.json');
|
||||||
|
alert('文件已下载为roadTest.json,请将其复制到项目的public目录下');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理文件上传
|
||||||
|
function handleFileUpload(event) {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
importJsonText.value = e.target.result;
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从JSON数据导入路线
|
||||||
|
function importRouteData() {
|
||||||
|
if (!importJsonText.value) {
|
||||||
|
alert('请输入或上传JSON数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 解析JSON
|
||||||
|
const geojsonObj = JSON.parse(importJsonText.value);
|
||||||
|
|
||||||
|
// 创建GeoJSON格式化器
|
||||||
|
const format = new GeoJSON();
|
||||||
|
|
||||||
|
// 清除现有图层
|
||||||
|
clearDraw();
|
||||||
|
|
||||||
|
// 从GeoJSON读取特征
|
||||||
|
const features = format.readFeatures(geojsonObj, {
|
||||||
|
dataProjection: 'EPSG:4326', // 输入为WGS84坐标
|
||||||
|
featureProjection: props.map.getView().getProjection() // 转换到地图投影
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加特征到矢量源
|
||||||
|
vectorSource.addFeatures(features);
|
||||||
|
|
||||||
|
// 处理导入的特征,分类为路线和点线面
|
||||||
|
features.forEach(feature => {
|
||||||
|
const geometry = feature.getGeometry();
|
||||||
|
const props = feature.getProperties();
|
||||||
|
|
||||||
|
if (geometry instanceof LineString || props.type === 'route') {
|
||||||
|
// 这是一条路线
|
||||||
|
const length = getLength(geometry);
|
||||||
|
const vertices = geometry.getCoordinates().length;
|
||||||
|
|
||||||
|
routeLines.value.push({
|
||||||
|
id: Date.now() + Math.random(),
|
||||||
|
feature: feature,
|
||||||
|
length: props.length || length,
|
||||||
|
vertices: props.vertices || vertices,
|
||||||
|
extent: geometry.getExtent()
|
||||||
|
});
|
||||||
|
} else if (geometry instanceof Polygon || props.type === 'polygon') {
|
||||||
|
// 这是一个点线面
|
||||||
|
const area = getArea(geometry);
|
||||||
|
const perimeter = getLength(geometry.getLinearRing(0));
|
||||||
|
const vertices = geometry.getLinearRing(0).getCoordinates().length - 1;
|
||||||
|
|
||||||
|
polygons.value.push({
|
||||||
|
id: Date.now() + Math.random(),
|
||||||
|
feature: feature,
|
||||||
|
area: props.area || area,
|
||||||
|
perimeter: props.perimeter || perimeter,
|
||||||
|
vertices: props.vertices || vertices,
|
||||||
|
extent: geometry.getExtent()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新面板显示
|
||||||
|
if (routeLines.value.length > 0) {
|
||||||
|
showRouteLinesPanel.value = true;
|
||||||
|
}
|
||||||
|
if (polygons.value.length > 0) {
|
||||||
|
showPolygonPanel.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭导入对话框
|
||||||
|
showImportDialog.value = false;
|
||||||
|
importJsonText.value = '';
|
||||||
|
|
||||||
|
// 缩放到所有特征的范围
|
||||||
|
if (features.length > 0) {
|
||||||
|
const extent = vectorSource.getExtent();
|
||||||
|
props.map.getView().fit(extent, {
|
||||||
|
padding: [50, 50, 50, 50],
|
||||||
|
duration: 1000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('成功导入数据,共', features.length, '个特征');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('导入数据失败:', error);
|
||||||
|
alert('导入失败,请检查JSON格式是否正确');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时初始化
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.map) {
|
||||||
|
initDrawLayer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听地图实例变化
|
||||||
|
watch(() => props.map, (newMap) => {
|
||||||
|
if (newMap) {
|
||||||
|
initDrawLayer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 组件卸载时清理
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (props.map) {
|
||||||
|
if (drawInteraction) {
|
||||||
|
props.map.removeInteraction(drawInteraction);
|
||||||
|
}
|
||||||
|
if (vectorLayer) {
|
||||||
|
props.map.removeLayer(vectorLayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 向外暴露方法
|
||||||
|
defineExpose({
|
||||||
|
startDrawLine,
|
||||||
|
startDrawPolygon,
|
||||||
|
clearDraw,
|
||||||
|
exportRouteData,
|
||||||
|
importRouteData,
|
||||||
|
showImportDialog
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.route-draw-control {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draw-tools {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 20px;
|
||||||
|
z-index: 3000;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 120px;
|
||||||
|
border: 2px solid #0096ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-button {
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.2s;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-button:hover {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-button.active {
|
||||||
|
background-color: #0096ff;
|
||||||
|
color: white;
|
||||||
|
border-color: #0078cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.polygon-panel, .route-lines-panel {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
width: 300px;
|
||||||
|
max-height: 80vh;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||||||
|
z-index: 3000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-lines-panel {
|
||||||
|
right: 340px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
padding: 12px 15px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
font-weight: bold;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-content {
|
||||||
|
padding: 15px;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: calc(80vh - 50px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-header {
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-content {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-content p {
|
||||||
|
margin: 5px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus-btn {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
background-color: #0096ff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus-btn:hover {
|
||||||
|
background-color: #0078cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data {
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 导入对话框样式 */
|
||||||
|
.import-dialog {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
width: 500px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-header {
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-body textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
resize: none;
|
||||||
|
font-family: monospace;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload label {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer button {
|
||||||
|
padding: 8px 15px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer button.primary-btn {
|
||||||
|
background-color: #0096ff;
|
||||||
|
color: white;
|
||||||
|
border-color: #0078cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer button.primary-btn:hover {
|
||||||
|
background-color: #0078cc;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -76,7 +76,7 @@ onMounted(() => {
|
|||||||
.car-alarm-container {
|
.car-alarm-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 20px;
|
left: 20px;
|
||||||
top: 90px;
|
top: 50px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap:24px;
|
gap:24px;
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="event-list-container">
|
<div class="event-list-container">
|
||||||
<div class="event-list-header">
|
<div class="event-list-header">
|
||||||
<div class="header-title">车辆列表 ({{ totalCount }})</div>
|
<div class="header-title">
|
||||||
|
<img src="../../../assets/images/sub_icon.png" alt="list_bg" class="header-title-icon">
|
||||||
|
<div class="header-title-text">车辆列表 ({{ totalCount }})</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<div class="filter-dropdown">
|
<div class="filter-dropdown">
|
||||||
<span class="selected-filter">全部</span>
|
<span class="selected-filter">全部</span>
|
||||||
@ -181,10 +185,9 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="scss">
|
||||||
.event-list-container {
|
.event-list-container {
|
||||||
background: url("../../../assets/images/list_bg.png") no-repeat;
|
background-color: #27313F;
|
||||||
background-size: 100% 100%;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@ -203,21 +206,27 @@ onMounted(() => {
|
|||||||
|
|
||||||
|
|
||||||
.header-title {
|
.header-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-left: 35px;
|
background:url("../../../assets/images/subheading.png") no-repeat;
|
||||||
padding:0 10px;
|
background-size:100% 100%;
|
||||||
|
padding:5px 10px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
img{
|
||||||
|
width: 31px;
|
||||||
|
height: 15px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-actions {
|
.header-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding:0 16px;
|
padding:0 16px;
|
||||||
margin: 20px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-dropdown {
|
.filter-dropdown {
|
||||||
|
|||||||
@ -13,20 +13,23 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<img src="../../assets/images/close.png" alt="close" class="close-icon" />
|
<img src="../../assets/images/close.png" alt="close" class="close-icon" @click="handleLogout" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
import { ElMessageBox } from 'element-plus'
|
||||||
import { useRouter, useRoute } from "vue-router";
|
import { useRouter, useRoute } from "vue-router";
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
import usePermissionStore from '@/store/modules/permission';
|
import usePermissionStore from '@/store/modules/permission';
|
||||||
|
import useUserStore from '@/store/modules/user'
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
const userStore = useUserStore()
|
||||||
const permissionStore = usePermissionStore();
|
const permissionStore = usePermissionStore();
|
||||||
|
|
||||||
// 定义Tab选项
|
// 定义Tab选项
|
||||||
@ -70,6 +73,19 @@ function handleTabClick(path) {
|
|||||||
router.push(path);
|
router.push(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理退出登录
|
||||||
|
function handleLogout() {
|
||||||
|
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
userStore.logOut().then(() => {
|
||||||
|
router.push('/login');
|
||||||
|
});
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -324,7 +324,7 @@ const cardList = [
|
|||||||
:deep(.el-tabs__item.is-active) {
|
:deep(.el-tabs__item.is-active) {
|
||||||
color: #409eff;
|
color: #409eff;
|
||||||
}
|
}
|
||||||
::v-deep .el-tabs__nav-wrap::after {
|
:deep .el-tabs__nav-wrap::after {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background-color: #4C4F5F;
|
background-color: #4C4F5F;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<div class="park-container">
|
<div class="park-container">
|
||||||
<!-- 车辆状态统计卡片 -->
|
|
||||||
<VehicleStats :stats="vehicleStats" />
|
|
||||||
|
|
||||||
<!-- 搜索区域 -->
|
<!-- 搜索区域 -->
|
||||||
<div class="search-area">
|
<div class="search-area">
|
||||||
@ -34,7 +33,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<el-button class="export-btn" @click="handleExport">导出</el-button>
|
<el-button class="search-btn" type="primary" @click="handleAdd">新增</el-button>
|
||||||
|
<el-button class="reset-btn" @click="handleImport">导入</el-button>
|
||||||
|
<el-button class="reset-btn" @click="handleExport">导出</el-button>
|
||||||
|
<el-button class="export-btn" @click="handleDelete" :disabled="multiple">删除</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -56,34 +58,22 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="120" align="left">
|
<el-table-column label="操作" width="120" align="left">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button link text @click="handleView(scope.row)">查看</el-button>
|
<el-button link text @click="handleEdit(scope.row)">编辑</el-button>
|
||||||
|
<el-button link text type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="车辆号" prop="carId" align="left" />
|
<el-table-column label="车牌号" prop="carNo" align="left" />
|
||||||
<el-table-column label="类型" prop="type" align="left" />
|
<el-table-column label="类型" prop="type" align="left" />
|
||||||
<el-table-column label="品牌" prop="brand" align="left" />
|
<el-table-column label="品牌" prop="brand" align="left" />
|
||||||
<el-table-column label="所属单位" prop="organization" align="left" />
|
<el-table-column label="所属单位" prop="organization" align="left" />
|
||||||
<el-table-column label="当前故障数" prop="faultCount" align="left" />
|
|
||||||
<el-table-column label="作业状态" align="left">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-tag :type="getRouteStatusType(scope.row.routeStatus)">
|
|
||||||
{{ scope.row.routeStatus }}
|
|
||||||
</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="车辆状态" align="left">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-tag :type="getStatusType(scope.row.status)">
|
|
||||||
{{ scope.row.status }}
|
|
||||||
</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="充电状态" prop="chargeStatus" align="left" />
|
|
||||||
<el-table-column label="电池电量" prop="batteryLevel" align="left" />
|
|
||||||
<el-table-column label="最新位置" prop="location" align="left" />
|
|
||||||
<el-table-column label="最新时间" prop="lastUpdateTime" align="left" width="160" />
|
|
||||||
<el-table-column label="负责人" prop="contact" align="left" />
|
<el-table-column label="负责人" prop="contact" align="left" />
|
||||||
<el-table-column label="负责人电话" prop="phonenumber" align="left" />
|
<el-table-column label="负责人电话" prop="phonenumber" align="left" />
|
||||||
|
<el-table-column label="创建人" prop="createBy" align="left" />
|
||||||
|
|
||||||
|
<el-table-column label="最新时间" prop="lastUpdateTime" align="left" width="160" />
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<!-- 分页组件 -->
|
<!-- 分页组件 -->
|
||||||
@ -95,14 +85,138 @@
|
|||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 新增/编辑弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialog.visible"
|
||||||
|
:title="dialog.title"
|
||||||
|
width="600px"
|
||||||
|
append-to-body
|
||||||
|
class="vehicle-dialog"
|
||||||
|
>
|
||||||
|
<el-form ref="vehicleFormRef" :model="form" :rules="rules" label-width="100px">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="车牌号" prop="carNo">
|
||||||
|
<el-input v-model="form.carNo" placeholder="请输入车牌号" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="车辆类型" prop="type">
|
||||||
|
<el-input v-model="form.type" placeholder="请输入车辆类型" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="车辆品牌" prop="brand">
|
||||||
|
<el-input v-model="form.brand" placeholder="请输入车辆品牌" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="所属单位" prop="organization">
|
||||||
|
<el-input v-model="form.organization" placeholder="请输入所属单位" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="联系人" prop="contact">
|
||||||
|
<el-input v-model="form.contact" placeholder="请输入联系人" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="电话" prop="phonenumber">
|
||||||
|
<el-input v-model="form.phonenumber" placeholder="请输入电话" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item label="车辆图片" prop="image">
|
||||||
|
<div class="avatar-uploader-box">
|
||||||
|
<div v-if="form.image" class="avatar-preview">
|
||||||
|
<img :src="form.image" class="avatar" />
|
||||||
|
<div class="avatar-replace" @click.stop="handleRemoveImage">
|
||||||
|
<el-icon><Close /></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-upload
|
||||||
|
v-else
|
||||||
|
class="avatar-uploader"
|
||||||
|
:action="uploadUrl"
|
||||||
|
:headers="headers"
|
||||||
|
:show-file-list="false"
|
||||||
|
:on-success="handleImageSuccess"
|
||||||
|
:before-upload="beforeImageUpload"
|
||||||
|
>
|
||||||
|
<div class="avatar-upload-placeholder">
|
||||||
|
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
|
||||||
|
</div>
|
||||||
|
</el-upload>
|
||||||
|
<div class="avatar-count">{{ form.image ? '1/1' : '0/1' }}</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button class="export-btn" @click="dialog.visible = false">取 消</el-button>
|
||||||
|
<el-button class="search-btn" type="primary" @click="submitForm">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 导入弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="importDialog.visible"
|
||||||
|
title="导入车辆信息"
|
||||||
|
width="500px"
|
||||||
|
append-to-body
|
||||||
|
class="import-dialog"
|
||||||
|
>
|
||||||
|
<div class="import-title">选择需要导入的车辆文件</div>
|
||||||
|
<el-upload
|
||||||
|
ref="uploadRef"
|
||||||
|
:limit="1"
|
||||||
|
accept=".xlsx, .xls"
|
||||||
|
:headers="upload.headers"
|
||||||
|
:action="upload.url + '?updateSupport=' + upload.updateSupport"
|
||||||
|
:disabled="upload.isUploading"
|
||||||
|
:on-progress="handleFileUploadProgress"
|
||||||
|
:on-success="handleFileSuccess"
|
||||||
|
:auto-upload="false"
|
||||||
|
drag
|
||||||
|
class="import-upload"
|
||||||
|
>
|
||||||
|
<div class="upload-content">
|
||||||
|
<el-icon class="upload-icon"><upload-filled /></el-icon>
|
||||||
|
<div class="upload-text">拖拽文件到这里,或</div>
|
||||||
|
<div class="upload-link">
|
||||||
|
<el-icon class="upload-icon-small" style="color: #347AE2"><upload-filled /></el-icon>
|
||||||
|
<span class="click-upload">点此添加</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-upload>
|
||||||
|
<div class="import-footer">
|
||||||
|
<div class="footer-left">
|
||||||
|
<el-checkbox v-model="upload.updateSupport">是否更新已经存在的车辆数据</el-checkbox>
|
||||||
|
</div>
|
||||||
|
<div class="footer-right">
|
||||||
|
<span class="file-format">仅允许导入xls、xlsx格式文件。</span>
|
||||||
|
<el-link type="primary" :underline="false" class="download-link" @click="importTemplate">下载模板</el-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button class="reset-btn" @click="importDialog.visible = false">取 消</el-button>
|
||||||
|
<el-button class="search-btn" type="primary" @click="submitFileForm">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup name="VehicleMonitor">
|
<script setup name="VehiclePark">
|
||||||
import { ref, reactive, onMounted, computed } from 'vue';
|
import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
|
||||||
import { getToken } from "@/utils/auth";
|
import { getToken } from "@/utils/auth";
|
||||||
import { parseTime } from "@/utils/ruoyi";
|
import { parseTime } from "@/utils/ruoyi";
|
||||||
import { Search, Download } from '@element-plus/icons-vue';
|
import { Search, Download, Plus, UploadFilled, Close } from '@element-plus/icons-vue';
|
||||||
import VehicleStats from '@/components/car/VehicleStats.vue';
|
import VehicleStats from '@/components/car/VehicleStats.vue';
|
||||||
import Pagination from '@/components/Pagination/index.vue';
|
import Pagination from '@/components/Pagination/index.vue';
|
||||||
|
|
||||||
@ -142,11 +256,68 @@ const queryParams = ref({
|
|||||||
status: undefined
|
status: undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
// 模拟车辆监控数据
|
// 弹窗控制
|
||||||
|
const dialog = reactive({
|
||||||
|
visible: false,
|
||||||
|
title: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const importDialog = reactive({
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const form = ref({
|
||||||
|
id: undefined,
|
||||||
|
carNo: '',
|
||||||
|
type: '',
|
||||||
|
brand: '',
|
||||||
|
organization: '',
|
||||||
|
contact: '',
|
||||||
|
phonenumber: '',
|
||||||
|
image: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules = {
|
||||||
|
carNo: [
|
||||||
|
{ required: true, message: "车牌号不能为空", trigger: "blur" }
|
||||||
|
],
|
||||||
|
type: [
|
||||||
|
{ required: true, message: "车辆类型不能为空", trigger: "change" }
|
||||||
|
],
|
||||||
|
brand: [
|
||||||
|
{ required: true, message: "车辆品牌不能为空", trigger: "blur" }
|
||||||
|
],
|
||||||
|
organization: [
|
||||||
|
{ required: true, message: "所属单位不能为空", trigger: "blur" }
|
||||||
|
],
|
||||||
|
contact: [
|
||||||
|
{ required: true, message: "联系人不能为空", trigger: "blur" }
|
||||||
|
],
|
||||||
|
phonenumber: [
|
||||||
|
{ required: true, message: "电话不能为空", trigger: "blur" }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 上传相关
|
||||||
|
const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/common/upload";
|
||||||
|
const headers = { Authorization: "Bearer " + getToken() };
|
||||||
|
const upload = reactive({
|
||||||
|
isUploading: false,
|
||||||
|
updateSupport: 0,
|
||||||
|
title: "",
|
||||||
|
open: false,
|
||||||
|
// 新增导入文件的接口URL
|
||||||
|
url: import.meta.env.VITE_APP_BASE_API + "/car/park/importData",
|
||||||
|
headers: { Authorization: "Bearer " + getToken() }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 模拟车辆数据
|
||||||
const mockVehicleData = [
|
const mockVehicleData = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
carId: 'QH001',
|
carNo: '鲁B12345',
|
||||||
type: '驱动车',
|
type: '驱动车',
|
||||||
brand: '大众',
|
brand: '大众',
|
||||||
organization: '青岛机场物流公司',
|
organization: '青岛机场物流公司',
|
||||||
@ -154,15 +325,15 @@ const mockVehicleData = [
|
|||||||
routeStatus: '任务中',
|
routeStatus: '任务中',
|
||||||
status: '在线',
|
status: '在线',
|
||||||
chargeStatus: '未充电',
|
chargeStatus: '未充电',
|
||||||
batteryLevel: '85%',
|
batteryLevel: '89%',
|
||||||
location: '航站楼A区',
|
location: '跑道区域A',
|
||||||
lastUpdateTime: '2024-04-17 10:48:30',
|
lastUpdateTime: '2024-01-15 10:30:00',
|
||||||
contact: '张三',
|
contact: '张三',
|
||||||
phonenumber: '12345678920'
|
phonenumber: '13800138001'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
carId: 'QH002',
|
carNo: '鲁B67890',
|
||||||
type: '巡检车',
|
type: '巡检车',
|
||||||
brand: '金龙',
|
brand: '金龙',
|
||||||
organization: '青岛机场运输公司',
|
organization: '青岛机场运输公司',
|
||||||
@ -170,59 +341,59 @@ const mockVehicleData = [
|
|||||||
routeStatus: '待命中',
|
routeStatus: '待命中',
|
||||||
status: '在线',
|
status: '在线',
|
||||||
chargeStatus: '未充电',
|
chargeStatus: '未充电',
|
||||||
batteryLevel: '72%',
|
batteryLevel: '65%',
|
||||||
location: '航站楼B区',
|
location: '航站楼前',
|
||||||
lastUpdateTime: '2024-04-17 10:48:30',
|
lastUpdateTime: '2024-01-16 14:20:00',
|
||||||
contact: '李四',
|
contact: '李四',
|
||||||
phonenumber: '12345678921'
|
phonenumber: '13800138002'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
carId: 'QH003',
|
carNo: '鲁B11111',
|
||||||
type: '驱动车',
|
type: '驱动车',
|
||||||
brand: '解放',
|
brand: '解放',
|
||||||
organization: '青岛机场货运部',
|
organization: '青岛机场货运部',
|
||||||
faultCount: 2,
|
faultCount: 3,
|
||||||
routeStatus: '充电中',
|
routeStatus: '充电中',
|
||||||
status: '故障',
|
status: '离线',
|
||||||
chargeStatus: '充电中',
|
chargeStatus: '充电中',
|
||||||
batteryLevel: '45%',
|
batteryLevel: '25%',
|
||||||
location: '充电站',
|
location: '充电站',
|
||||||
lastUpdateTime: '2024-04-17 10:48:30',
|
lastUpdateTime: '2024-01-17 09:15:00',
|
||||||
contact: '王五',
|
contact: '王五',
|
||||||
phonenumber: '12345678922'
|
phonenumber: '13800138003'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
carId: 'QH004',
|
carNo: '鲁B22222',
|
||||||
type: '驱动车',
|
type: '巡检车',
|
||||||
brand: '福特',
|
brand: '宇通',
|
||||||
organization: '青岛机场物流公司',
|
organization: '青岛机场安保部',
|
||||||
faultCount: 0,
|
faultCount: 0,
|
||||||
routeStatus: '任务中',
|
routeStatus: '等待中',
|
||||||
status: '在线',
|
status: '故障',
|
||||||
chargeStatus: '未充电',
|
chargeStatus: '未充电',
|
||||||
batteryLevel: '65%',
|
batteryLevel: '12%',
|
||||||
location: '货运区',
|
location: '货运区B',
|
||||||
lastUpdateTime: '2024-04-17 10:48:30',
|
lastUpdateTime: '2024-01-17 16:40:00',
|
||||||
contact: '赵六',
|
contact: '赵六',
|
||||||
phonenumber: '12345678923'
|
phonenumber: '13800138004'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 5,
|
id: 5,
|
||||||
carId: 'QH005',
|
carNo: '鲁B33333',
|
||||||
type: '巡检车',
|
type: '驱动车',
|
||||||
brand: '宇通',
|
brand: '福田',
|
||||||
organization: '青岛机场运输公司',
|
organization: '青岛机场维修部',
|
||||||
faultCount: 0,
|
faultCount: 2,
|
||||||
routeStatus: '待命中',
|
routeStatus: '待命中',
|
||||||
status: '离线',
|
status: '在线',
|
||||||
chargeStatus: '未充电',
|
chargeStatus: '未充电',
|
||||||
batteryLevel: '0%',
|
batteryLevel: '78%',
|
||||||
location: '维修站',
|
location: '维修区C',
|
||||||
lastUpdateTime: '2024-04-17 10:48:30',
|
lastUpdateTime: '2024-01-18 08:10:00',
|
||||||
contact: '钱七',
|
contact: '钱七',
|
||||||
phonenumber: '12345678924'
|
phonenumber: '13800138005'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -259,15 +430,13 @@ function getStatusType(status) {
|
|||||||
/** 查询车辆列表 */
|
/** 查询车辆列表 */
|
||||||
function getList() {
|
function getList() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
// 使用模拟数据
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// 根据查询条件筛选数据
|
|
||||||
let filteredData = [...mockVehicleData];
|
let filteredData = [...mockVehicleData];
|
||||||
|
|
||||||
if (queryParams.value.carNo) {
|
if (queryParams.value.carNo) {
|
||||||
const keyword = queryParams.value.carNo.toLowerCase();
|
const keyword = queryParams.value.carNo.toLowerCase();
|
||||||
filteredData = filteredData.filter(item =>
|
filteredData = filteredData.filter(item =>
|
||||||
item.carId.toLowerCase().includes(keyword) ||
|
item.carNo.toLowerCase().includes(keyword) ||
|
||||||
item.brand.toLowerCase().includes(keyword) ||
|
item.brand.toLowerCase().includes(keyword) ||
|
||||||
item.organization.toLowerCase().includes(keyword)
|
item.organization.toLowerCase().includes(keyword)
|
||||||
);
|
);
|
||||||
@ -284,12 +453,6 @@ function getList() {
|
|||||||
loading.value = false;
|
loading.value = false;
|
||||||
vehicleList.value = filteredData;
|
vehicleList.value = filteredData;
|
||||||
total.value = filteredData.length;
|
total.value = filteredData.length;
|
||||||
|
|
||||||
// 更新统计数据
|
|
||||||
vehicleStats.value[0].count = mockVehicleData.filter(v => v.status === '在线').length;
|
|
||||||
vehicleStats.value[1].count = mockVehicleData.filter(v => v.routeStatus === '任务中').length;
|
|
||||||
vehicleStats.value[2].count = mockVehicleData.filter(v => v.status === '故障').length;
|
|
||||||
vehicleStats.value[3].count = mockVehicleData.filter(v => v.status === '离线').length;
|
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,18 +481,137 @@ function handleSelectionChange(selection) {
|
|||||||
multiple.value = !selection.length;
|
multiple.value = !selection.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查看详情按钮操作 */
|
/** 新增按钮操作 */
|
||||||
function handleView(row) {
|
function handleAdd() {
|
||||||
proxy.$modal.msgSuccess("查看车辆详情功能已模拟");
|
reset();
|
||||||
|
dialog.visible = true;
|
||||||
|
dialog.title = "添加车辆";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 修改按钮操作 */
|
||||||
|
function handleEdit(row) {
|
||||||
|
reset();
|
||||||
|
const id = row.id || ids.value[0];
|
||||||
|
// 模拟获取详情
|
||||||
|
const vehicle = mockVehicleData.find(item => item.id === id);
|
||||||
|
if (vehicle) {
|
||||||
|
Object.assign(form.value, vehicle);
|
||||||
|
}
|
||||||
|
dialog.visible = true;
|
||||||
|
dialog.title = "修改车辆";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 提交按钮 */
|
||||||
|
function submitForm() {
|
||||||
|
proxy.$refs["vehicleFormRef"].validate(valid => {
|
||||||
|
if (valid) {
|
||||||
|
if (form.value.id != null) {
|
||||||
|
// 更新
|
||||||
|
proxy.$modal.msgSuccess("修改成功");
|
||||||
|
} else {
|
||||||
|
// 新增
|
||||||
|
proxy.$modal.msgSuccess("新增成功");
|
||||||
|
}
|
||||||
|
dialog.visible = false;
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
function handleDelete(row) {
|
||||||
|
const vehicleIds = row.id || ids.value;
|
||||||
|
proxy.$modal.confirm('是否确认删除车辆编号为"' + vehicleIds + '"的数据项?').then(function() {
|
||||||
|
return proxy.$modal.msgSuccess("删除成功");
|
||||||
|
}).then(() => {
|
||||||
|
getList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导入按钮操作 */
|
||||||
|
function handleImport() {
|
||||||
|
importDialog.visible = true;
|
||||||
|
upload.title = "车辆导入";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 文件上传中处理 */
|
||||||
|
function handleFileUploadProgress(event, file, fileList) {
|
||||||
|
upload.isUploading = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 文件上传成功处理 */
|
||||||
|
function handleFileSuccess(response, file, fileList) {
|
||||||
|
upload.isUploading = false;
|
||||||
|
proxy.$refs["uploadRef"].clearFiles();
|
||||||
|
proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true });
|
||||||
|
importDialog.visible = false;
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 提交上传文件 */
|
||||||
|
function submitFileForm() {
|
||||||
|
proxy.$refs["uploadRef"].submit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 导出按钮操作 */
|
/** 导出按钮操作 */
|
||||||
function handleExport() {
|
function handleExport() {
|
||||||
proxy.$modal.msgSuccess("导出功能已模拟");
|
proxy.download("car/park/export", {
|
||||||
|
...queryParams.value,
|
||||||
|
pageNum: undefined,
|
||||||
|
pageSize: undefined
|
||||||
|
}, `车辆数据_${new Date().getTime()}.xlsx`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 图片上传成功处理 */
|
||||||
|
function handleImageSuccess(response, file) {
|
||||||
|
form.value.image = response.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 图片上传前处理 */
|
||||||
|
function beforeImageUpload(file) {
|
||||||
|
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';
|
||||||
|
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||||
|
|
||||||
|
if (!isJPG) {
|
||||||
|
proxy.$message.error('上传图片只能是 JPG/PNG 格式!');
|
||||||
|
}
|
||||||
|
if (!isLt2M) {
|
||||||
|
proxy.$message.error('上传图片大小不能超过 2MB!');
|
||||||
|
}
|
||||||
|
return isJPG && isLt2M;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 表单重置 */
|
||||||
|
function reset() {
|
||||||
|
form.value = {
|
||||||
|
id: undefined,
|
||||||
|
carNo: '',
|
||||||
|
type: '',
|
||||||
|
brand: '',
|
||||||
|
organization: '',
|
||||||
|
contact: '',
|
||||||
|
phonenumber: '',
|
||||||
|
image: ''
|
||||||
|
};
|
||||||
|
proxy.resetForm("vehicleFormRef");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 下载模板操作 */
|
||||||
|
function importTemplate() {
|
||||||
|
proxy.download('car/park/importTemplate', {}, `车辆数据模板_${new Date().getTime()}.xlsx`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 移除图片操作 */
|
||||||
|
function handleRemoveImage() {
|
||||||
|
form.value.image = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList();
|
getList();
|
||||||
|
// 测试图片显示
|
||||||
|
// form.value.image = 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png';
|
||||||
|
// dialog.visible = true;
|
||||||
|
// dialog.title = "添加车辆";
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -377,23 +659,22 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 输入框 下拉框 搜索栏
|
// 输入框 下拉框 搜索栏
|
||||||
::v-deep(.el-input__wrapper:hover) {
|
:deep(.el-input__wrapper:hover) {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select) {
|
:deep(.el-select) {
|
||||||
border:none !important;
|
border:none !important;
|
||||||
width:160px !important;
|
width:160px !important;
|
||||||
// 修改el-select的输入框的border
|
|
||||||
--el-border-color-hover: #343744!important;
|
--el-border-color-hover: #343744!important;
|
||||||
--el-border-color: #343744!important;
|
--el-border-color: #343744!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__wrapper.is-focused) {
|
:deep(.el-select__wrapper.is-focused) {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__wrapper) {
|
:deep(.el-select__wrapper) {
|
||||||
background: #343744 !important;
|
background: #343744 !important;
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
@ -401,10 +682,9 @@ onMounted(() => {
|
|||||||
width: 160px;
|
width: 160px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
border:none !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-input__wrapper) {
|
:deep(.el-input__wrapper) {
|
||||||
background: #343744 !important;
|
background: #343744 !important;
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
@ -413,9 +693,10 @@ onMounted(() => {
|
|||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__placeholder) {
|
:deep(.el-select__placeholder) {
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-table {
|
.custom-table {
|
||||||
background-color: #292c38 !important;
|
background-color: #292c38 !important;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
@ -423,7 +704,6 @@ onMounted(() => {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
// 防止表格闪白
|
|
||||||
:deep(.el-loading-mask) {
|
:deep(.el-loading-mask) {
|
||||||
background-color: rgba(41, 44, 56, 0.7) !important;
|
background-color: rgba(41, 44, 56, 0.7) !important;
|
||||||
}
|
}
|
||||||
@ -436,7 +716,6 @@ onMounted(() => {
|
|||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保整个表格区域背景色
|
|
||||||
:deep(.el-table) {
|
:deep(.el-table) {
|
||||||
background-color: #292c38 !important;
|
background-color: #292c38 !important;
|
||||||
}
|
}
|
||||||
@ -445,21 +724,8 @@ onMounted(() => {
|
|||||||
background-color: #292c38 !important;
|
background-color: #292c38 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-box{
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border-radius: 6px;
|
|
||||||
|
|
||||||
img{
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 6px;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:deep(.el-table__inner-wrapper::before) {
|
:deep(.el-table__inner-wrapper::before) {
|
||||||
display: none; /* 隐藏表格顶部的边框线 */
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table__header) {
|
:deep(.el-table__header) {
|
||||||
@ -473,7 +739,7 @@ onMounted(() => {
|
|||||||
background-color: #343744 !important;
|
background-color: #343744 !important;
|
||||||
color: #96A0B5!important;
|
color: #96A0B5!important;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
border-bottom: none; /* 移除th底部边线 */
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr th.el-table__cell:first-child {
|
tr th.el-table__cell:first-child {
|
||||||
@ -503,7 +769,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table__body-wrapper) {
|
:deep(.el-table__body-wrapper) {
|
||||||
|
|
||||||
td {
|
td {
|
||||||
height: 68px!important;
|
height: 68px!important;
|
||||||
background-color: #292c38;
|
background-color: #292c38;
|
||||||
@ -514,7 +779,6 @@ onMounted(() => {
|
|||||||
|
|
||||||
:deep(.el-table__body tr:hover > td) {
|
:deep(.el-table__body tr:hover > td) {
|
||||||
background: #2B3B5A!important;
|
background: #2B3B5A!important;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-button--link) {
|
:deep(.el-button--link) {
|
||||||
@ -552,7 +816,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 全局表格样式覆盖,防止闪白 */
|
/* 全局表格样式覆盖 */
|
||||||
:deep(.el-table) {
|
:deep(.el-table) {
|
||||||
--el-table-bg-color: #292c38 !important;
|
--el-table-bg-color: #292c38 !important;
|
||||||
--el-table-tr-bg-color: #292c38 !important;
|
--el-table-tr-bg-color: #292c38 !important;
|
||||||
@ -565,7 +829,100 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 媒体查询,处理响应式布局
|
// 弹窗样式
|
||||||
|
.vehicle-dialog, .import-dialog {
|
||||||
|
:deep(.el-dialog) {
|
||||||
|
background-color: #343744;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-dialog__header) {
|
||||||
|
background-color: #343744;
|
||||||
|
color: #ffffff;
|
||||||
|
border-bottom: 1px solid #4C4F5F;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-dialog__body) {
|
||||||
|
background-color: #343744;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-dialog__footer) {
|
||||||
|
background-color: #343744;
|
||||||
|
border-top: 1px solid #4C4F5F;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
color: #96A0B5;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
background: #292C38 !important;
|
||||||
|
border: 1px solid #4C4F5F !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-select__wrapper) {
|
||||||
|
background: #292C38 !important;
|
||||||
|
border: 1px solid #4C4F5F !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__inner) {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-select__placeholder) {
|
||||||
|
color: #96A0B5 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片上传样式
|
||||||
|
.avatar-uploader {
|
||||||
|
:deep(.el-upload) {
|
||||||
|
border: 1px dashed #4C4F5F;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: var(--el-transition-duration-fast);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #347AE2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-upload-dragger) {
|
||||||
|
background-color: #292C38;
|
||||||
|
border: 1px dashed #4C4F5F;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 90px;
|
||||||
|
height: 90px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #347AE2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-uploader-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
color: #8c939d;
|
||||||
|
width: 90px;
|
||||||
|
height: 90px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 90px;
|
||||||
|
height: 90px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 媒体查询
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
.search-area {
|
.search-area {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@ -610,5 +967,199 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 导入上传样式
|
||||||
|
.import-dialog {
|
||||||
|
:deep(.el-dialog__header) {
|
||||||
|
margin-right: 0;
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 1px solid #4C4F5F;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-dialog__body) {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-dialog__footer) {
|
||||||
|
padding: 20px;
|
||||||
|
border-top: 1px solid #4C4F5F;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #96A0B5;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-upload {
|
||||||
|
:deep(.el-upload-dragger) {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
background-color: #292C38;
|
||||||
|
border: 1px dashed #4C4F5F;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #347AE2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
color: #96A0B5;
|
||||||
|
|
||||||
|
.upload-icon {
|
||||||
|
font-size: 40px;
|
||||||
|
color: #96A0B5;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-text {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: #347AE2;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.upload-icon-small {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.click-upload {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
.footer-left {
|
||||||
|
:deep(.el-checkbox__label) {
|
||||||
|
color: #96A0B5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.file-format {
|
||||||
|
color: #96A0B5;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-link {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 头像上传样式 */
|
||||||
|
.avatar-uploader-box {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-uploader {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-preview {
|
||||||
|
position: relative;
|
||||||
|
width: 90px;
|
||||||
|
height: 90px;
|
||||||
|
border-radius: 0;
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 0;
|
||||||
|
border: 1px solid #343744;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-replace {
|
||||||
|
position: absolute;
|
||||||
|
top: -8px;
|
||||||
|
right: -8px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-upload-placeholder {
|
||||||
|
width: 90px;
|
||||||
|
height: 90px;
|
||||||
|
border: 1px dashed #4C4F5F;
|
||||||
|
border-radius: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
position: relative;
|
||||||
|
background-color: transparent;
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #409EFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-uploader-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
color: #8c939d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-count {
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
right: -25px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #ffffff;
|
||||||
|
z-index: 20;
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@ -1,483 +1,383 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<div class="type-container">
|
<div class="type-container">
|
||||||
<!-- 搜索区域 -->
|
<!-- Tab栏+右侧按钮 -->
|
||||||
<div class="search-area">
|
<el-tabs
|
||||||
<el-input
|
v-model="editableTabsValue"
|
||||||
v-model="queryParams.typeName"
|
|
||||||
placeholder="请输入类型名称查询"
|
class="demo-tabs"
|
||||||
clearable
|
editable
|
||||||
prefix-icon="Search"
|
@edit="handleTabsEdit"
|
||||||
class="search-input"
|
>
|
||||||
@keyup.enter="handleQuery"
|
<template #add-icon>
|
||||||
/>
|
<el-icon><Select /></el-icon>
|
||||||
|
</template>
|
||||||
<div class="search-buttons">
|
<el-tab-pane
|
||||||
<el-button type="primary" class="search-btn" @click="handleQuery">搜索</el-button>
|
v-for="item in editableTabs"
|
||||||
<el-button class="reset-btn" @click="resetQuery">重置</el-button>
|
:key="item.name"
|
||||||
|
:label="item.label"
|
||||||
|
:name="item.name"
|
||||||
|
>
|
||||||
|
{{ item.content }}
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
<!-- 搜索栏和操作按钮 -->
|
||||||
|
<div class="search-action-bar">
|
||||||
|
<div class="search-area">
|
||||||
|
<el-input
|
||||||
|
v-model="query"
|
||||||
|
placeholder="请输入关键字查询"
|
||||||
|
class="search-input"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
<el-button type="primary" class="search-btn" @click="handleSearch"
|
||||||
|
>搜索</el-button
|
||||||
|
>
|
||||||
|
<el-button class="reset-btn" @click="handleReset">重置</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<el-button type="primary" class="search-btn" @click="handleAdd">新增</el-button>
|
<el-button type="primary" class="add-btn" @click="handleAdd"
|
||||||
<el-button class="export-btn" type="info" :disabled="multiple" @click="handleDelete">删除</el-button>
|
>新增</el-button
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
class="delete-btn"
|
||||||
|
@click="handleDelete"
|
||||||
|
:disabled="!selected.length"
|
||||||
|
>删除</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 表格 -->
|
||||||
<!-- 类型表格 -->
|
|
||||||
<el-table
|
<el-table
|
||||||
v-loading="loading"
|
:data="tableData"
|
||||||
:data="typeList"
|
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
style="width: 100%"
|
|
||||||
class="custom-table"
|
class="custom-table"
|
||||||
:header-cell-style="{ backgroundColor: '#343744', color: '#fff' }"
|
:header-cell-style="{ backgroundColor: '#343744', color: '#fff' }"
|
||||||
:bg-color="'#292C38'"
|
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="50" align="left" />
|
<el-table-column type="selection" width="50" />
|
||||||
<el-table-column label="序号" width="60" align="left">
|
<el-table-column label="操作" width="120">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ scope.$index + 1 }}
|
<el-button link text @click="handleEdit(scope.row)">编辑</el-button>
|
||||||
</template>
|
<el-button
|
||||||
</el-table-column>
|
link
|
||||||
<el-table-column label="操作" width="120" align="left">
|
text
|
||||||
<template #default="scope">
|
type="danger"
|
||||||
<el-button link text @click="handleUpdate(scope.row)">编辑</el-button>
|
@click="handleDelete([scope.row])"
|
||||||
<el-button link text type="primary" @click="handleDelete(scope.row)">删除</el-button>
|
>删除</el-button
|
||||||
</template>
|
>
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="类型名称" prop="typeName" align="left" />
|
|
||||||
<el-table-column label="类型编码" prop="typeCode" align="left" />
|
|
||||||
<el-table-column label="描述" prop="description" align="left" />
|
|
||||||
<el-table-column label="创建人" prop="creator" align="left" />
|
|
||||||
<el-table-column label="创建时间" align="left" prop="createTime" width="160">
|
|
||||||
<template #default="scope">
|
|
||||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="二级类型" prop="type" />
|
||||||
|
<el-table-column label="创建人" prop="creator" />
|
||||||
|
<el-table-column label="创建时间" prop="createTime" />
|
||||||
</el-table>
|
</el-table>
|
||||||
|
<!-- 分页 -->
|
||||||
<!-- 分页组件 -->
|
|
||||||
<pagination
|
<pagination
|
||||||
v-show="total > 0"
|
v-show="total > 0"
|
||||||
:total="total"
|
:total="total"
|
||||||
v-model:page="queryParams.pageNum"
|
v-model:page="pageNum"
|
||||||
v-model:limit="queryParams.pageSize"
|
v-model:limit="pageSize"
|
||||||
@pagination="getList"
|
@pagination="handleTabsEdit"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 添加或修改类型对话框 -->
|
|
||||||
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
|
|
||||||
<el-form :model="form" :rules="rules" ref="typeRef" label-width="100px">
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="24">
|
|
||||||
<el-form-item label="类型名称" prop="typeName">
|
|
||||||
<el-input
|
|
||||||
v-model="form.typeName"
|
|
||||||
placeholder="请输入类型名称"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="24">
|
|
||||||
<el-form-item label="类型编码" prop="typeCode">
|
|
||||||
<el-input
|
|
||||||
v-model="form.typeCode"
|
|
||||||
placeholder="请输入类型编码"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="24">
|
|
||||||
<el-form-item label="描述" prop="description">
|
|
||||||
<el-input
|
|
||||||
v-model="form.description"
|
|
||||||
type="textarea"
|
|
||||||
:rows="3"
|
|
||||||
placeholder="请输入描述信息"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<el-button class="export-btn" @click="cancel">取 消</el-button>
|
|
||||||
<el-button class="search-btn" type="primary" @click="submitForm">确 定</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup name="CarType">
|
<script setup lang="ts">
|
||||||
import { getCurrentInstance, ref } from 'vue';
|
import { ref, watch } from "vue";
|
||||||
import { parseTime } from "@/utils/ruoyi";
|
import { Select } from "@element-plus/icons-vue";
|
||||||
|
let tabIndex = 2;
|
||||||
|
const editableTabsValue = ref("2");
|
||||||
|
const editableTabs = ref([
|
||||||
|
{ label: "无人车", name: "无人车",content:'' },
|
||||||
|
{ label: "特勤车", name: "特勤车",content:''},
|
||||||
|
{ label: "普通车", name: "普通车" ,content:''},
|
||||||
|
]);
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance();
|
const activeTab = ref("无人车");
|
||||||
|
const query = ref("");
|
||||||
const typeList = ref([]);
|
const pageNum = ref(1);
|
||||||
const open = ref(false);
|
const pageSize = ref(10);
|
||||||
const loading = ref(true);
|
|
||||||
const ids = ref([]);
|
|
||||||
const single = ref(true);
|
|
||||||
const multiple = ref(true);
|
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
const title = ref("");
|
const selected = ref([]);
|
||||||
|
|
||||||
// 模拟数据
|
const mockData = ref([
|
||||||
const mockTypeData = [
|
|
||||||
{
|
{
|
||||||
id: 1,
|
tab: "无人车",
|
||||||
typeName: '无人车',
|
list: [
|
||||||
typeCode: 'WRC',
|
{
|
||||||
description: '无人驾驶运输车辆',
|
id: 1,
|
||||||
creator: '系统管理员',
|
type: "驱动车",
|
||||||
createTime: '2024-04-17 10:48:30'
|
creator: "系统管理员",
|
||||||
|
createTime: "2024-08-17 15:48:30",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
type: "牵引车",
|
||||||
|
creator: "系统管理员",
|
||||||
|
createTime: "2024-08-17 15:48:30",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
tab: "特勤车",
|
||||||
typeName: '客车',
|
list: [
|
||||||
typeCode: 'KC',
|
{
|
||||||
description: '载客运输车辆',
|
id: 3,
|
||||||
creator: '系统管理员',
|
type: "消防车",
|
||||||
createTime: '2024-04-17 10:48:30'
|
creator: "系统管理员",
|
||||||
|
createTime: "2024-08-17 15:48:30",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
tab: "普通车",
|
||||||
typeName: '特种车',
|
list: [
|
||||||
typeCode: 'TZC',
|
{
|
||||||
description: '特种作业车辆',
|
id: 4,
|
||||||
creator: '系统管理员',
|
type: "大巴车",
|
||||||
createTime: '2024-04-17 10:48:30'
|
creator: "系统管理员",
|
||||||
}
|
createTime: "2024-08-17 15:48:30",
|
||||||
];
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const queryParams = ref({
|
const tableData = ref([]);
|
||||||
pageNum: 1,
|
const handleTabsEdit = (targetName, action) => {
|
||||||
pageSize: 10,
|
if (action === "add") {
|
||||||
typeName: undefined
|
const newTabName = `${++tabIndex}`;
|
||||||
});
|
editableTabs.value.push({
|
||||||
|
label: "新标签页",
|
||||||
const form = ref({});
|
name: newTabName,
|
||||||
|
content:''
|
||||||
const rules = ref({
|
});
|
||||||
typeName: [
|
editableTabsValue.value = newTabName;
|
||||||
{ required: true, message: "类型名称不能为空", trigger: "blur" }
|
} else if (action === "remove") {
|
||||||
],
|
const tabs = editableTabs.value;
|
||||||
typeCode: [
|
let activeName = editableTabsValue.value;
|
||||||
{ required: true, message: "类型编码不能为空", trigger: "blur" }
|
if (activeName === targetName) {
|
||||||
]
|
tabs.forEach((tab, index) => {
|
||||||
});
|
if (tab.name === targetName) {
|
||||||
|
const nextTab = tabs[index + 1] || tabs[index - 1];
|
||||||
/** 查询类型列表 */
|
if (nextTab) {
|
||||||
function getList() {
|
activeName = nextTab.name;
|
||||||
loading.value = true;
|
}
|
||||||
// 使用模拟数据
|
|
||||||
setTimeout(() => {
|
|
||||||
loading.value = false;
|
|
||||||
typeList.value = mockTypeData;
|
|
||||||
total.value = mockTypeData.length;
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
|
||||||
function handleQuery() {
|
|
||||||
queryParams.value.pageNum = 1;
|
|
||||||
getList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
|
||||||
function resetQuery() {
|
|
||||||
proxy.resetForm("queryRef");
|
|
||||||
queryParams.value.pageNum = 1;
|
|
||||||
handleQuery();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 选择条数 */
|
|
||||||
function handleSelectionChange(selection) {
|
|
||||||
ids.value = selection.map(item => item.id);
|
|
||||||
single.value = selection.length != 1;
|
|
||||||
multiple.value = !selection.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
|
||||||
function handleDelete(row) {
|
|
||||||
const typeIds = row.id || ids.value;
|
|
||||||
proxy.$modal.confirm('确定删除此类型吗?', '删除').then(function() {
|
|
||||||
// 模拟删除操作
|
|
||||||
getList();
|
|
||||||
proxy.$modal.msgSuccess("删除成功");
|
|
||||||
}).catch(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 新增按钮操作 */
|
|
||||||
function handleAdd() {
|
|
||||||
reset();
|
|
||||||
title.value = "添加车辆类型";
|
|
||||||
open.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 修改按钮操作 */
|
|
||||||
function handleUpdate(row) {
|
|
||||||
reset();
|
|
||||||
const typeId = row.id || ids.value[0];
|
|
||||||
// 使用模拟数据
|
|
||||||
const typeInfo = mockTypeData.find(d => d.id === typeId);
|
|
||||||
if (typeInfo) {
|
|
||||||
form.value = {...typeInfo};
|
|
||||||
open.value = true;
|
|
||||||
title.value = "修改车辆类型";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 重置表单 */
|
|
||||||
function reset() {
|
|
||||||
form.value = {
|
|
||||||
id: undefined,
|
|
||||||
typeName: undefined,
|
|
||||||
typeCode: undefined,
|
|
||||||
description: undefined
|
|
||||||
};
|
|
||||||
proxy.resetForm("typeRef");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 提交表单 */
|
|
||||||
function submitForm() {
|
|
||||||
proxy.$refs["typeRef"].validate(valid => {
|
|
||||||
if (valid) {
|
|
||||||
if (form.value.id != undefined) {
|
|
||||||
// 模拟修改操作
|
|
||||||
const index = mockTypeData.findIndex(item => item.id === form.value.id);
|
|
||||||
if (index !== -1) {
|
|
||||||
mockTypeData[index] = {...form.value};
|
|
||||||
}
|
}
|
||||||
proxy.$modal.msgSuccess("修改成功");
|
});
|
||||||
open.value = false;
|
|
||||||
getList();
|
|
||||||
} else {
|
|
||||||
// 模拟新增操作
|
|
||||||
form.value.id = mockTypeData.length + 1;
|
|
||||||
form.value.createTime = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
|
||||||
form.value.creator = '系统管理员';
|
|
||||||
mockTypeData.push({...form.value});
|
|
||||||
proxy.$modal.msgSuccess("新增成功");
|
|
||||||
open.value = false;
|
|
||||||
getList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 取消按钮 */
|
editableTabsValue.value = activeName;
|
||||||
function cancel() {
|
editableTabs.value = tabs.filter((tab) => tab.name !== targetName);
|
||||||
open.value = false;
|
}
|
||||||
reset();
|
};
|
||||||
}
|
// 搜索
|
||||||
|
const handleSearch=()=>{
|
||||||
|
|
||||||
onMounted(() => {
|
}
|
||||||
getList();
|
// 重置
|
||||||
});
|
const handleReset=()=>{
|
||||||
|
|
||||||
|
}
|
||||||
|
// 新增
|
||||||
|
const handleAdd=()=>{
|
||||||
|
|
||||||
|
}
|
||||||
|
// 删除
|
||||||
|
const handleDelete=(rows)=>{
|
||||||
|
|
||||||
|
}
|
||||||
|
// 表格
|
||||||
|
const handleSelectionChange =()=>{
|
||||||
|
|
||||||
|
}
|
||||||
|
// 编辑
|
||||||
|
const handleEdit =(rows)=>{
|
||||||
|
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.app-container {
|
.app-container {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background-color: #292C38;
|
background-color: #292c38;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.type-container {
|
.type-container {
|
||||||
background-color: #292c38;
|
background-color: #292c38;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
.demo-tabs {
|
||||||
|
|
||||||
.search-area {
|
margin-bottom: 20px;
|
||||||
|
:deep(.el-tabs__header) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
:deep(.el-tabs__item){
|
||||||
|
color:#fff!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.add-tab-btn {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-left: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
vertical-align: middle;
|
||||||
|
transition: filter 0.2s;
|
||||||
|
filter: brightness(1);
|
||||||
|
&:hover {
|
||||||
|
filter: brightness(1.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.search-action-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
.search-area {
|
||||||
.search-input {
|
|
||||||
width: 250px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-buttons {
|
|
||||||
margin-right: 20px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
.search-input {
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
.search-btn,
|
||||||
|
.reset-btn {
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 15px;
|
||||||
|
height: 36px;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
.search-btn {
|
||||||
|
background: #347ae2;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.reset-btn {
|
||||||
|
background: #343744;
|
||||||
|
color: #96a0b5;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
.add-btn,
|
||||||
|
.delete-btn {
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 15px;
|
||||||
|
height: 36px;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
.add-btn {
|
||||||
|
background: #347ae2;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.delete-btn {
|
||||||
|
background: #343744;
|
||||||
|
color: #96a0b5;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 输入框 下拉框 搜索栏
|
|
||||||
::v-deep(.el-input__wrapper:hover) {
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep(.el-input__wrapper) {
|
|
||||||
background: #343744 !important;
|
|
||||||
color: #96A0B5 !important;
|
|
||||||
border-radius: 8px !important;
|
|
||||||
height: 36px !important;
|
|
||||||
border: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-table {
|
.custom-table {
|
||||||
background-color: #292c38 !important;
|
background-color: #292c38 !important;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
// 防止表格闪白
|
|
||||||
:deep(.el-loading-mask) {
|
:deep(.el-loading-mask) {
|
||||||
background-color: rgba(41, 44, 56, 0.7) !important;
|
background-color: rgba(41, 44, 56, 0.7) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table__empty-block) {
|
:deep(.el-table__empty-block) {
|
||||||
background-color: #292c38 !important;
|
background-color: #292c38 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table__empty-text) {
|
:deep(.el-table__empty-text) {
|
||||||
color: #96A0B5 !important;
|
color: #96a0b5 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保整个表格区域背景色
|
|
||||||
:deep(.el-table) {
|
:deep(.el-table) {
|
||||||
background-color: #292c38 !important;
|
background-color: #292c38 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table__body) {
|
:deep(.el-table__body) {
|
||||||
background-color: #292c38 !important;
|
background-color: #292c38 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table__inner-wrapper::before) {
|
:deep(.el-table__inner-wrapper::before) {
|
||||||
display: none; /* 隐藏表格顶部的边框线 */
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table__header) {
|
:deep(.el-table__header) {
|
||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 4px;
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table__header-wrapper) {
|
:deep(.el-table__header-wrapper) {
|
||||||
th {
|
th {
|
||||||
background-color: #343744 !important;
|
background-color: #343744 !important;
|
||||||
color: #96A0B5 !important;
|
color: #96a0b5 !important;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
border-bottom: none; /* 移除th底部边线 */
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr th.el-table__cell:first-child {
|
tr th.el-table__cell:first-child {
|
||||||
border-top-left-radius: 6px;
|
border-top-left-radius: 6px;
|
||||||
|
|
||||||
.cell {
|
.cell {
|
||||||
border-top-left-radius: 6px;
|
border-top-left-radius: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tr th.el-table__cell:last-child {
|
tr th.el-table__cell:last-child {
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
|
|
||||||
.cell {
|
.cell {
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-table__header th.el-table__cell {
|
.el-table__header th.el-table__cell {
|
||||||
background-color: #343744 !important;
|
background-color: #343744 !important;
|
||||||
color: #96A0B5;
|
color: #96a0b5;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr {
|
tr {
|
||||||
background-color: #343744 !important;
|
background-color: #343744 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table__body-wrapper) {
|
:deep(.el-table__body-wrapper) {
|
||||||
td {
|
td {
|
||||||
height: 68px!important;
|
height: 68px !important;
|
||||||
background-color: #292c38;
|
background-color: #292c38;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
border-bottom: 1px solid #4C4F5F;
|
border-bottom: 1px solid #4c4f5f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table__body tr:hover > td) {
|
:deep(.el-table__body tr:hover > td) {
|
||||||
background: #2B3B5A!important;
|
background: #2b3b5a !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-button--link) {
|
:deep(.el-button--link) {
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #347AE2;
|
color: #347ae2;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0 5px;
|
margin: 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-button.is-text) {
|
:deep(.el-button.is-text) {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
color: #347AE2;
|
color: #347ae2;
|
||||||
|
&:hover,
|
||||||
&:hover, &:focus, &:active {
|
&:focus,
|
||||||
|
&:active {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
color: #347AE2;
|
color: #347ae2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-button.el-button--primary.is-text) {
|
:deep(.el-button.el-button--primary.is-text) {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
color: #347AE2;
|
color: #347ae2;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
color: #347AE2;
|
color: #347ae2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table__body tr:hover .el-button--link.is-text) {
|
:deep(.el-table__body tr:hover .el-button--link.is-text) {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
color: #347AE2;
|
color: #347ae2;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 弹窗样式优化
|
|
||||||
:deep(.el-dialog) {
|
|
||||||
.el-form-item__label {
|
|
||||||
color: #96A0B5 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-input__wrapper {
|
|
||||||
background-color: #292c38 !important;
|
|
||||||
box-shadow: 0 0 0 1px #565C70 inset !important;
|
|
||||||
|
|
||||||
.el-input__inner {
|
|
||||||
color: #ffffff !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-textarea__inner {
|
|
||||||
background-color: #292c38 !important;
|
|
||||||
border-color: #565C70 !important;
|
|
||||||
color: #ffffff !important;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
box-shadow: 0 0 0 1px #409EFF inset !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@ -154,22 +154,31 @@ getCookie();
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-image: url("../assets/images/login-background.jpg");
|
background-image: url("../assets/images/login-background.png");
|
||||||
background-size: cover;
|
background-size: 100% 100%;
|
||||||
|
|
||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
margin: 0px auto 30px auto;
|
margin: 0px auto 30px auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #707070;
|
color: #ffffff;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-form {
|
.login-form {
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
background: #ffffff;
|
background: rgba(41, 44, 56, 0.8);
|
||||||
|
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
||||||
width: 400px;
|
width: 400px;
|
||||||
padding: 25px 25px 5px 25px;
|
padding: 25px 25px 5px 25px;
|
||||||
.el-input {
|
.el-input {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
:deep(.el-input__inner) {
|
||||||
|
color: #ffffff;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="platform-overview platform-no-padding">
|
<div class="platform-overview platform-no-padding">
|
||||||
<OpenLayersMap />
|
<OpenLayersMap ref="mapRef" />
|
||||||
<CarAlarm />
|
<CarAlarm />
|
||||||
<!-- 右侧展开/收起按钮 -->
|
<!-- 右侧展开/收起按钮 -->
|
||||||
<img
|
<img
|
||||||
class="eventlist-toggle-btn"
|
class="eventlist-toggle-btn"
|
||||||
|
:class="{ 'btn-opened': showEventList }"
|
||||||
:src="showEventList ? btn_left : btn_right"
|
:src="showEventList ? btn_left : btn_right"
|
||||||
:alt="showEventList ? '收起车辆列表' : '展开车辆列表'"
|
:alt="showEventList ? '收起车辆列表' : '展开车辆列表'"
|
||||||
@click="toggleEventList"
|
@click="toggleEventList"
|
||||||
/>
|
/>
|
||||||
<!-- 车辆列表 -->
|
<!-- 车辆列表 -->
|
||||||
<Eventlist v-if="showEventList" />
|
<Eventlist v-if="showEventList" />
|
||||||
|
|
||||||
|
<!-- 绘制工具栏 -->
|
||||||
|
<!-- <div class="draw-toolbar">
|
||||||
|
<div class="toolbar-title">绘制工具</div>
|
||||||
|
<div class="toolbar-buttons">
|
||||||
|
<button @click="startDrawLine" class="toolbar-button">绘制路线</button>
|
||||||
|
<button @click="startDrawPolygon" class="toolbar-button">绘制点线面</button>
|
||||||
|
<button @click="clearDraw" class="toolbar-button clear">清除</button>
|
||||||
|
<button @click="exportRouteData" class="toolbar-button export">导出数据</button>
|
||||||
|
<button @click="importRouteData" class="toolbar-button import">导入数据</button>
|
||||||
|
</div>
|
||||||
|
</div>-->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -22,12 +35,54 @@ import Eventlist from '../../components/map/info/eventlist.vue';
|
|||||||
import btn_left from '../../assets/images/btn_left.png';
|
import btn_left from '../../assets/images/btn_left.png';
|
||||||
import btn_right from '../../assets/images/btn_right.png';
|
import btn_right from '../../assets/images/btn_right.png';
|
||||||
|
|
||||||
const showEventList = ref(false);
|
const showEventList = ref(false); // 默认不显示列表
|
||||||
|
const mapRef = ref(null);
|
||||||
|
|
||||||
function toggleEventList() {
|
function toggleEventList() {
|
||||||
showEventList.value = !showEventList.value;
|
showEventList.value = !showEventList.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRouteDrawControlExpose() {
|
||||||
|
// 通过OpenLayersMap的ref获取RouteDrawControl的ref
|
||||||
|
return mapRef.value?.$refs?.routeDrawControlRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startDrawLine() {
|
||||||
|
const routeDraw = getRouteDrawControlExpose();
|
||||||
|
if (routeDraw && routeDraw.startDrawLine) {
|
||||||
|
routeDraw.startDrawLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startDrawPolygon() {
|
||||||
|
const routeDraw = getRouteDrawControlExpose();
|
||||||
|
if (routeDraw && routeDraw.startDrawPolygon) {
|
||||||
|
routeDraw.startDrawPolygon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearDraw() {
|
||||||
|
const routeDraw = getRouteDrawControlExpose();
|
||||||
|
if (routeDraw && routeDraw.clearDraw) {
|
||||||
|
routeDraw.clearDraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportRouteData() {
|
||||||
|
const routeDraw = getRouteDrawControlExpose();
|
||||||
|
if (routeDraw && routeDraw.exportRouteData) {
|
||||||
|
routeDraw.exportRouteData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function importRouteData() {
|
||||||
|
const routeDraw = getRouteDrawControlExpose();
|
||||||
|
if (routeDraw && routeDraw.showImportDialog) {
|
||||||
|
// 调用RouteDrawControl组件中的方法显示导入对话框
|
||||||
|
routeDraw.showImportDialog = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.querySelector('.app-main')?.classList.add('platform-no-padding');
|
document.querySelector('.app-main')?.classList.add('platform-no-padding');
|
||||||
});
|
});
|
||||||
@ -57,13 +112,90 @@ onUnmounted(() => {
|
|||||||
height: 80px;
|
height: 80px;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: right 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮打开状态 - 移到左侧 */
|
||||||
|
.eventlist-toggle-btn.btn-opened {
|
||||||
|
right: 405px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 绘制工具栏 */
|
||||||
|
.draw-toolbar {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 2000;
|
||||||
|
width: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-title {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #0096ff;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-buttons {
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-button {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-button:hover {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-button.clear {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: white;
|
||||||
|
border-color: #d32f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-button.clear:hover {
|
||||||
|
background-color: #d32f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-button.export {
|
||||||
|
background-color: #4caf50;
|
||||||
|
color: white;
|
||||||
|
border-color: #388e3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-button.export:hover {
|
||||||
|
background-color: #388e3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-button.import {
|
||||||
|
background-color: #ff9800;
|
||||||
|
color: white;
|
||||||
|
border-color: #f57c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-button.import:hover {
|
||||||
|
background-color: #f57c00;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 只影响本页,精准去除AppMain的左右padding */
|
/* 只影响本页,精准去除AppMain的左右padding */
|
||||||
:deep(.platform-no-padding) .app-main {
|
// :deep(.platform-no-padding) .app-main {
|
||||||
padding-left: 0 !important;
|
// padding-left: 0 !important;
|
||||||
padding-right: 0 !important;
|
// padding-right: 0 !important;
|
||||||
}
|
// }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@ -716,11 +716,11 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 输入框 下拉框 搜索栏
|
// 输入框 下拉框 搜索栏
|
||||||
::v-deep(.el-input__wrapper:hover) {
|
:deep(.el-input__wrapper:hover) {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select) {
|
:deep(.el-select) {
|
||||||
border:none !important;
|
border:none !important;
|
||||||
width:140px !important;
|
width:140px !important;
|
||||||
// 修改el-select的输入框的border
|
// 修改el-select的输入框的border
|
||||||
@ -728,11 +728,11 @@ onMounted(() => {
|
|||||||
--el-border-color: #343744!important;
|
--el-border-color: #343744!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__wrapper.is-focused) {
|
:deep(.el-select__wrapper.is-focused) {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__wrapper) {
|
:deep(.el-select__wrapper) {
|
||||||
background: #343744 !important;
|
background: #343744 !important;
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
@ -743,7 +743,7 @@ onMounted(() => {
|
|||||||
border:none !important;
|
border:none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-input__wrapper) {
|
:deep(.el-input__wrapper) {
|
||||||
background: #343744 !important;
|
background: #343744 !important;
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
@ -752,7 +752,7 @@ onMounted(() => {
|
|||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__placeholder) {
|
:deep(.el-select__placeholder) {
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
}
|
}
|
||||||
.custom-table {
|
.custom-table {
|
||||||
|
|||||||
@ -269,11 +269,11 @@ getList();
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 输入框 下拉框 搜索栏
|
// 输入框 下拉框 搜索栏
|
||||||
::v-deep(.el-input__wrapper:hover) {
|
:deep(.el-input__wrapper:hover) {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select) {
|
:deep(.el-select) {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
width: 140px !important;
|
width: 140px !important;
|
||||||
// 修改el-select的输入框的border
|
// 修改el-select的输入框的border
|
||||||
@ -281,11 +281,11 @@ getList();
|
|||||||
--el-border-color: #343744 !important;
|
--el-border-color: #343744 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__wrapper.is-focused) {
|
:deep(.el-select__wrapper.is-focused) {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__wrapper) {
|
:deep(.el-select__wrapper) {
|
||||||
background: #343744 !important;
|
background: #343744 !important;
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
@ -295,7 +295,7 @@ getList();
|
|||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-input__wrapper) {
|
:deep(.el-input__wrapper) {
|
||||||
background: #343744 !important;
|
background: #343744 !important;
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
@ -304,7 +304,7 @@ getList();
|
|||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__placeholder) {
|
:deep(.el-select__placeholder) {
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -509,11 +509,11 @@ getList();
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 输入框 下拉框 搜索栏
|
// 输入框 下拉框 搜索栏
|
||||||
::v-deep(.el-input__wrapper:hover) {
|
:deep(.el-input__wrapper:hover) {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select) {
|
:deep(.el-select) {
|
||||||
border:none !important;
|
border:none !important;
|
||||||
width:140px !important;
|
width:140px !important;
|
||||||
// 修改el-select的输入框的border
|
// 修改el-select的输入框的border
|
||||||
@ -521,11 +521,11 @@ getList();
|
|||||||
--el-border-color: #343744!important;
|
--el-border-color: #343744!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__wrapper.is-focused) {
|
:deep(.el-select__wrapper.is-focused) {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__wrapper) {
|
:deep(.el-select__wrapper) {
|
||||||
background: #343744 !important;
|
background: #343744 !important;
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
@ -536,7 +536,7 @@ getList();
|
|||||||
border:none !important;
|
border:none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-input__wrapper) {
|
:deep(.el-input__wrapper) {
|
||||||
background: #343744 !important;
|
background: #343744 !important;
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
@ -545,7 +545,7 @@ getList();
|
|||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__placeholder) {
|
:deep(.el-select__placeholder) {
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
}
|
}
|
||||||
.custom-table {
|
.custom-table {
|
||||||
|
|||||||
@ -558,11 +558,11 @@ function getPostOptions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 输入框 下拉框 搜索栏
|
// 输入框 下拉框 搜索栏
|
||||||
::v-deep(.el-input__wrapper:hover) {
|
:deep(.el-input__wrapper:hover) {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select) {
|
:deep(.el-select) {
|
||||||
border:none !important;
|
border:none !important;
|
||||||
width:140px !important;
|
width:140px !important;
|
||||||
// 修改el-select的输入框的border
|
// 修改el-select的输入框的border
|
||||||
@ -570,11 +570,11 @@ function getPostOptions() {
|
|||||||
--el-border-color: #343744!important;
|
--el-border-color: #343744!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__wrapper.is-focused) {
|
:deep(.el-select__wrapper.is-focused) {
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__wrapper) {
|
:deep(.el-select__wrapper) {
|
||||||
background: #343744 !important;
|
background: #343744 !important;
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
@ -585,7 +585,7 @@ function getPostOptions() {
|
|||||||
border:none !important;
|
border:none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-input__wrapper) {
|
:deep(.el-input__wrapper) {
|
||||||
background: #343744 !important;
|
background: #343744 !important;
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
@ -594,7 +594,7 @@ function getPostOptions() {
|
|||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-select__placeholder) {
|
:deep(.el-select__placeholder) {
|
||||||
color: #96A0B5 !important;
|
color: #96A0B5 !important;
|
||||||
}
|
}
|
||||||
.custom-table {
|
.custom-table {
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export default defineConfig(({ mode, command }) => {
|
|||||||
proxy: {
|
proxy: {
|
||||||
// https://cn.vitejs.dev/config/#server-proxy
|
// https://cn.vitejs.dev/config/#server-proxy
|
||||||
'/dev-api': {
|
'/dev-api': {
|
||||||
target: 'http://localhost:8099',
|
target: 'http://10.0.0.17:8099',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
||||||
}
|
}
|
||||||
|
|||||||