修改显示

main
suixy 4 months ago
parent 52a8d2598e
commit d1babb697d

@ -6,4 +6,8 @@
</template>
<style scoped>
* {
margin: 0;
padding: 0;
}
</style>

@ -72,3 +72,60 @@ export function ShutDownWork(data) {
url: '/api/Fod/ShutDownWork', method: 'get', data: data
});
}
export function getAllAirPort(data) {
return request({
url: '/api/FodAirPort/GetAll', method: 'get', data: data
});
}
export function addAirPort(data) {
return request({
url: '/api/FodAirPort/InsertAir', method: 'post', data: data
});
}
export function updateAirPort(data) {
return request({
url: '/api/FodAirPort/UpdateAir', method: 'post', data: data
});
}
export function delAirPort(data) {
return request({
url: '/api/FodAirPort/DeleteAir', method: 'get', params: data
});
}
export function getArea(data) {
return request({
url: '/api/FodAirPort/GetAllArea', method: 'get', params: data
});
}
export function addArea(data) {
return request({
url: '/api/FodAirPort/InsertArea', method: 'post', data: data
});
}
export function updateArea(data) {
return request({
url: '/api/FodAirPort/UpdateArea', method: 'post', data: data
});
}
export function delArea(data) {
return request({
url: '/api/FodAirPort/DeleteArea', method: 'get', params: data
});
}
export function GetAllCData(data) {
return request({
url: '/api/FodAirPort/GetAllCData', method: 'get', params: data
});
}

@ -0,0 +1,410 @@
<template>
<div ref="wrapperRef" class="map-wrapper">
<!-- 中心区域 -->
<div
class="map-content"
:style="{ transformOrigin: `${transformOriginX}px ${transformOriginY}px`,transform: ` scale(${scale}) translate(${0}px, ${0}px)` }"
>
<div class="map-area"></div>
<div
v-if="lineCoordinates"
class="connection-line"
:style="{
left: lineCoordinates.startX + 'px',
bottom: lineCoordinates.startY + 'px',
width: lineCoordinates.width + 'px',
transform: `translateY(-50%) rotate(${lineCoordinates.angle}deg) scaleY(${1/scale})`,
transformOrigin: 'left center'
}"
></div>
<!-- Box元素直接用内容坐标不再额外 *scale/+translate -->
<div
v-if="boxPos"
class="box"
:style="{
left: boxContentX + 'px',
bottom: boxContentY + 'px',
transform: `translate(-50%, 50%) rotate(${boxPos.rotate}deg) scale(${1 / scale})`
}"
>
<div class="scan"></div>
</div>
<div
v-for="(dot, index) in dots"
:key="index"
class="dot"
:style="{
left: `${lonToPixel(dot.lon)}px`,
bottom: `${latToPixel(dot.lat)}px`,
transform: `translate(-50%, 50%) scale(${1 / scale})`
}"
></div>
</div>
<!-- 左侧刻度尺 -->
<div class="y-ruler">
<div
v-for="tick in yTicks"
:key="tick.value"
class="tick"
:style="{ bottom: tick.pixel + 'px' }"
>
<div class="line"></div>
<div class="label">{{ tick.display }}</div>
</div>
</div>
<!-- 下方刻度尺 -->
<div class="x-ruler">
<div
v-for="tick in xTicks"
:key="tick.value"
class="tick"
:style="{ left: tick.pixel + 'px' }"
>
<div class="line"></div>
<div class="label">{{ tick.display }}</div>
</div>
</div>
</div>
</template>
<script setup>
import {ref, computed, onMounted} from "vue"
const props = defineProps({
minlon: Number,
maxlon: Number,
minlat: Number,
maxlat: Number,
boxPos: Object,// { x, y, rotate }
dots: Array,
dotIndex: Number
})
setInterval(() => {
// props.boxPos.x += (-0.001 + (Math.random() * 0.002))
// props.boxPos.y += (-0.001 + (Math.random() * 0.002))
props.boxPos.rotate += (-0 + (Math.random() * 50))
}, 1000)
const wrapperRef = ref(null)
const parentWidth = ref(0)
const parentHeight = ref(0)
//
const rulerWidth = 40
const rulerHeight = 20
//
const zoomLevel = ref(1)
const scale = computed(() =>
zoomLevel.value === 1 ? 1 : zoomLevel.value === 2 ? 10 : 100
)
const tickValue = computed(() =>
zoomLevel.value === 1 ? 100 : zoomLevel.value === 2 ? 10 : 1
)
const translateX = ref(0)
const translateY = ref(0)
//
const updateSize = () => {
if (wrapperRef.value) {
const rect = wrapperRef.value.getBoundingClientRect()
parentWidth.value = rect.width
parentHeight.value = rect.height
}
}
onMounted(updateSize)
window.addEventListener("resize", updateSize)
//
const contentWidth = computed(() => parentWidth.value - rulerWidth)
const contentHeight = computed(() => parentHeight.value - rulerHeight)
//
function lonToPixel(lon) {
return ((lon - props.minlon) / (props.maxlon - props.minlon)) * contentWidth.value
}
function latToPixel(lat) {
return ((lat - props.minlat) / (props.maxlat - props.minlat)) * contentHeight.value
}
const transformOriginX = ref(0)
const transformOriginY = ref(0)
//
function setZoom(center, level) {
zoomLevel.value = level
const contentX = lonToPixel(center.lon)
const contentY = latToPixel(center.lat)
const scaledX = (contentX + translateX.value) * scale.value
const scaledY = (contentY + translateY.value) * scale.value
transformOriginX.value = contentX
transformOriginY.value = contentHeight.value - contentY
const cx = lonToPixel(center.lon)
const cy = latToPixel(center.lat)
const targetX = contentWidth.value / 2
const targetY = contentHeight.value / 2
translateX.value = targetX - cx * scale.value
translateY.value = targetY - cy * scale.value
}
//
function formatTick(valueMeters) {
if (zoomLevel.value === 1) return Math.round(valueMeters / 100) * 100
if (zoomLevel.value === 2) return Math.round(valueMeters / 10) * 10
return Math.round(valueMeters)
}
// X
const xTicks = computed(() => {
const ticks = []
const pixelsPerLon = contentWidth.value / (props.maxlon - props.minlon)
const startLon = props.minlon - translateX.value / (pixelsPerLon * scale.value)
let val = startLon
while (val <= props.maxlon) {
const contentX = lonToPixel(val)
const pixel = contentX * scale.value + translateX.value
ticks.push({
value: val,
pixel,
display: formatTick((val - props.minlon) * 111000)
})
val += tickValue.value / 111000
}
return ticks
})
// Y
const yTicks = computed(() => {
const ticks = []
const pixelsPerLat = contentHeight.value / (props.maxlat - props.minlat)
const startLat = props.minlat - translateY.value / (pixelsPerLat * scale.value)
let val = startLat
while (val <= props.maxlat) {
const contentY = latToPixel(val)
const pixel = contentY * scale.value + translateY.value
ticks.push({
value: val,
pixel,
display: formatTick((val - props.minlat) * 111000)
})
val += tickValue.value / 111000
}
return ticks
})
// Box/
const boxContentX = computed(() => {
if (!props.boxPos) return 0
return lonToPixel(props.boxPos.x)
})
const boxContentY = computed(() => {
if (!props.boxPos) return 0
return latToPixel(props.boxPos.y)
})
const BOX_SIZE = 30 // .box width/height
const DOT_SIZE = 10 // .dot width/height
const lineCoordinates = computed(() => {
if (!props.boxPos || !props.dots || props.dotIndex == null) return null
const startDot = props.dots[props.dotIndex]
if (!startDot) return null
// dot center
const startX = lonToPixel(startDot.lon)
const startY = latToPixel(startDot.lat) - (DOT_SIZE / 2) * (1 / scale.value)
// box
const boxBottomX = lonToPixel(props.boxPos.x)
const boxBottomY = latToPixel(props.boxPos.y) - 15
// box
const centerX = boxBottomX
const centerY = boxBottomY + (BOX_SIZE / 2) * (1 / scale.value)
const carHeadOffset = BOX_SIZE * 0.5
const localAttachX = 0
const localAttachY = carHeadOffset
const ox = localAttachX * (1 / scale.value)
const oy = localAttachY * (1 / scale.value)
const rotDeg = props.boxPos.rotate || 0
const rad = -rotDeg * Math.PI / 180
const rotatedX = ox * Math.cos(rad) - oy * Math.sin(rad)
const rotatedY = ox * Math.sin(rad) + oy * Math.cos(rad)
const endX = centerX + rotatedX
const endY = centerY + rotatedY
const dx = endX - startX
const dy = endY - startY
const width = Math.hypot(dx, dy)
const angle = -Math.atan2(dy, dx) * 180 / Math.PI
return {startX, startY, width, angle, endX, endY}
})
defineExpose({setZoom})
//
setTimeout(() => {
setZoom({lon: 120.005, lat: 30.005}, 2)
}, 3000)
setTimeout(() => {
setZoom({lon: 120.005, lat: 30.005}, 3)
}, 5000)
// setTimeout(() => {
// setZoom({lon: 120.005, lat: 30.005}, 2)
// }, 8000)
// setTimeout(() => {
// setZoom({lon: 120.005, lat: 30.005}, 1)
// }, 10000)
</script>
<style scoped>
.map-wrapper {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background: #000;
}
.map-content {
position: absolute;
left: 40px; /* 左侧刻度尺宽度 */
bottom: 20px; /* 下方刻度尺高度 */
width: calc(100% - 40px);
height: calc(100% - 20px);
transform-origin: 0 0;
}
.map-area {
width: 100%;
height: 100%;
background: #333;
}
/* 左侧刻度尺 */
.y-ruler {
position: absolute;
left: 0;
top: 0;
bottom: 20px;
width: 40px;
background: #000;
}
.y-ruler .tick {
position: absolute;
width: 100%;
height: 1px;
}
.y-ruler .line {
position: absolute;
right: 0;
width: 10px;
height: 1px;
background: #fff;
}
.y-ruler .label {
position: absolute;
right: 12px;
top: -6px;
color: #fff;
font-size: 10px;
text-align: right;
}
/* 下方刻度尺 */
.x-ruler {
position: absolute;
left: 40px;
right: 0;
bottom: 0;
height: 20px;
background: #000;
}
.x-ruler .tick {
position: absolute;
height: 100%;
width: 1px;
}
.x-ruler .line {
position: absolute;
top: 0;
width: 1px;
height: 5px;
background: #fff;
}
.x-ruler .label {
position: absolute;
top: 5px;
left: -5px;
color: #fff;
font-size: 10px;
}
/* Box元素 */
.box {
position: absolute;
width: 30px;
height: 30px;
background-image: url("../assets/car.png");
background-repeat: no-repeat;
background-size: 100% 100%;
transform-origin: center;
}
.box .scan {
position: absolute;
background-image: url("../assets/scan.gif");
background-size: 100% 100%;
width: 100px;
height: 100px;
top: 50%;
right: 15px;
transform: translateY(-50%);
}
.dot {
width: 10px;
height: 10px;
position: absolute;
background: radial-gradient(circle at center,
#ea1212 0%, rgba(228, 116, 116, 0.2) 100%);
border-radius: 50%;
}
.connection-line {
position: absolute;
height: 2px;
background-color: transparent; /* 背景透明 */
border-top: 1px dashed #fff; /* 虚线样式 */
transform-origin: left center;
z-index: 1;
}
</style>

@ -0,0 +1,248 @@
<template>
<div ref="wrapperRef" class="map-wrapper">
<div class="left">
<div class="baseline">
<div
v-for="(t, index) in leftTicks"
:key="index"
class="tick-wrapper"
:style="{ bottom: t.percent + '%' }"
>
<div v-if="t.major || (zoomType === 1 && !t.major)" class="tick-label">{{ t.value }}</div>
<div v-if="!(zoomType === 2 && !t.major)" :class="t.major ? 'tick major' : 'tick minor'"/>
</div>
</div>
</div>
<div class="bottom">
<div class="baseline">
<div
v-for="t in ticks"
:key="t.value"
class="tick-wrapper"
:style="{ left: t.percent + '%' }"
>
<div v-if="t.major" class="tick-label">{{ t.value }}</div>
<div :class="t.major ? 'tick major' : 'tick minor'"/>
</div>
</div>
</div>
<div class="content"></div>
</div>
</template>
<script setup>
import {ref, computed, onMounted, onBeforeUnmount} from 'vue'
const props = defineProps({
num: {type: Number, required: true, default: 600},
height: {type: Number, required: true, default: 1000},
})
const centerValue = ref(100)
const zoomType = ref(1)
function zoomScale(value, type) {
centerValue.value = value
zoomType.value = type
}
function getAxisRange() {
if (!leftTicks.value || leftTicks.value.length === 0) return {min: 0, max: 0}
const values = leftTicks.value.map(t => t.value)
return {min: Math.min(...values), max: Math.max(...values)}
}
const leftTicks = computed(() => {
const arr = []
if (centerValue.value === 0 || zoomType.value === 0) {
const step = 10
const steps = Math.floor(props.height / step)
for (let i = 0; i <= steps; i++) {
const value = i * step
const isMajor = value % 100 === 0
const percent = Math.floor((value / props.height) * 100)
arr.push({value, major: isMajor, percent})
}
if (arr[arr.length - 1].value < props.height) {
arr.push({value: props.height, major: props.height % 100 === 0, percent: 100})
}
return arr
}
//
const defaultStep = 10
let factor = 1
if (zoomType.value === 1) factor = 10
else if (zoomType.value === 2) factor = 100
const step = defaultStep / factor
const halfRange = Math.floor((props.height / factor) / 2)
console.log(step)
console.log(halfRange)
let startValue = centerValue.value - halfRange
let endValue = centerValue.value + halfRange
if (startValue < 0) {
endValue += -startValue
startValue = 0
}
if (endValue > props.height) {
startValue -= endValue - props.height
endValue = props.height
if (startValue < 0) startValue = 0
}
//
for (let value = startValue; value <= endValue; value += step) {
const intValue = Math.floor(value)
let isMajor = false
if (zoomType.value === 1) {
isMajor = intValue % 10 === 0
} else if (zoomType.value === 2) {
isMajor = true
} else {
isMajor = intValue % 100 === 0
}
const percent = Math.floor(((intValue - startValue) / (endValue - startValue)) * 100)
arr.push({value: intValue, major: isMajor, percent})
}
return arr
})
const ticks = computed(() => {
const arr = []
const step = 10
const steps = Math.ceil(props.num / step) // 使ceil
for (let i = 0; i <= steps; i++) {
let value = i * step
if (value > props.num) value = props.num //
const isMajor = value % 100 === 0
const percent = (value / props.num) * 100
arr.push({value, major: isMajor, percent})
}
return arr
})
</script>
<style scoped lang="less">
.map-wrapper {
position: relative;
width: 100%;
height: 100%;
background-color: #000;
.left {
position: absolute;
width: 40px;
height: calc(100% - 40px);
background-color: #000;
top: 0;
left: 0;
.baseline {
position: relative;
width: 100%;
height: 100%;
border-right: 1px solid #fff;
}
.tick-wrapper {
position: absolute;
right: 0;
display: flex;
flex-direction: row;
align-items: center;
transform: translateY(50%);
}
.tick {
height: 1px;
background-color: #fff;
}
.tick.minor {
width: 12px;
opacity: 0.6;
}
.tick.major {
width: 18px;
background-color: #fff;
}
.tick-label {
font-size: 12px;
color: #fff;
writing-mode: vertical-rl;
margin-left: 4px;
}
}
.bottom {
position: absolute;
width: calc(100% - 40px);
height: 40px;
background-color: #000;
bottom: 0;
left: 40px;
.baseline {
position: relative;
width: 100%;
height: 100%;
border-bottom: 1px solid #fff3;
}
.tick-wrapper {
position: absolute;
top: 0;
display: flex;
flex-direction: column;
align-items: center;
}
.tick {
width: 1px;
background-color: #fff;
}
.tick.minor {
height: 12px;
opacity: 0.6;
}
.tick.major {
height: 18px;
background-color: #fff;
}
.tick-label {
position: absolute;
font-size: 12px;
color: #fff;
top: 100%;
}
}
.content {
position: absolute;
width: calc(100% - 40px);
height: calc(100% - 40px);
background-color: #666;
top: 0;
left: 40px
}
}
</style>

@ -1,410 +1,380 @@
<template>
<div ref="wrapperRef" class="map-wrapper">
<!-- 中心区域 -->
<div
class="map-content"
:style="{ transformOrigin: `${transformOriginX}px ${transformOriginY}px`,transform: ` scale(${scale}) translate(${0}px, ${0}px)` }"
>
<div class="map-area"></div>
<div
v-if="lineCoordinates"
class="connection-line"
:style="{
left: lineCoordinates.startX + 'px',
bottom: lineCoordinates.startY + 'px',
width: lineCoordinates.width + 'px',
transform: `translateY(-50%) rotate(${lineCoordinates.angle}deg) scaleY(${1/scale})`,
transformOrigin: 'left center'
}"
></div>
<!-- Box元素直接用内容坐标不再额外 *scale/+translate -->
<div class="left">
<div class="baseline">
<div
v-for="(t, index) in leftTicks"
:key="index"
class="tick-wrapper"
:style="{ bottom: t.percent + '%' }"
>
<!-- 数字竖向排列在右侧 -->
<div v-if="t.major" class="tick-label">{{ t.value }}</div>
<div :class="t.major ? 'tick major' : 'tick minor'"/>
</div>
</div>
</div>
<div class="bottom">
<div class="baseline">
<div
v-for="t in ticks"
:key="t.value"
class="tick-wrapper"
:style="{ left: t.percent + '%' }"
>
<div v-if="t.major" class="tick-label">{{ t.value }}</div>
<div :class="t.major ? 'tick major' : 'tick minor'"/>
</div>
</div>
</div>
<div class="content">
<div
v-if="boxPos"
class="box"
:style="{
left: boxContentX + 'px',
bottom: boxContentY + 'px',
transform: `translate(-50%, 50%) rotate(${boxPos.rotate}deg) scale(${1 / scale})`
left: boxContentX + '%',
bottom: boxContentY + '%',
transform: `translate(-50%, 50%) rotate(${boxPos.rotate}deg)`
}"
>
<div class="head" id="head">
<div class="line" v-if="lineData" :style="{
width:lineData.distance + 'px',
transform: ` rotate(${lineData.degree}deg)`
}"
></div>
</div>
<div class="scan"></div>
</div>
<div
v-for="(dot, index) in dots"
:key="index"
class="dot"
:id="`dot${index+1}`"
:style="{
left: `${lonToPixel(dot.lon)}px`,
bottom: `${latToPixel(dot.lat)}px`,
transform: `translate(-50%, 50%) scale(${1 / scale})`
left: `${getDotX(dot.x)}%`,
bottom: `${getDotY(dot.y)}%`,
transform: `translate(-50%, 50%) `
}"
></div>
</div>
<!-- 左侧刻度尺 -->
<div class="y-ruler">
<div
v-for="tick in yTicks"
:key="tick.value"
class="tick"
:style="{ bottom: tick.pixel + 'px' }"
>
<div class="line"></div>
<div class="label">{{ tick.display }}</div>
</div>
</div>
<!-- 下方刻度尺 -->
<div class="x-ruler">
<div
v-for="tick in xTicks"
:key="tick.value"
class="tick"
:style="{ left: tick.pixel + 'px' }"
>
<div class="line"></div>
<div class="label">{{ tick.display }}</div>
</div>
</div>
</div>
</template>
<script setup>
import {ref, computed, onMounted} from "vue"
import {ref, computed, onMounted, onBeforeUnmount, watch, nextTick} from 'vue'
const boxRef = ref(null)
const props = defineProps({
minlon: Number,
maxlon: Number,
minlat: Number,
maxlat: Number,
boxPos: Object,// { x, y, rotate }
dots: Array,
dotIndex: Number
width: {type: Number, required: true,},
height: {type: Number, required: true,},
boxPos: {type: Object, required: true,},
dots: {type: Array, required: true,},
dotIndex: {type: Number, required: true,}
})
setInterval(() => {
// props.boxPos.x += (-0.001 + (Math.random() * 0.002))
// props.boxPos.y += (-0.001 + (Math.random() * 0.002))
props.boxPos.rotate += (-0 + (Math.random() * 50))
}, 1000)
const wrapperRef = ref(null)
const parentWidth = ref(0)
const parentHeight = ref(0)
const boxContentX = ref(0)
const boxContentY = ref(0)
watch(() => props.boxPos, (newVal, oldVal) => {
getBoxPos()
})
onMounted(() => {
getBoxPos()
nextTick(() => {
getLineData('head', `dot${props.dotIndex + 1}`)
})
})
const getBoxPos = () => {
centerValue.value = props.boxPos.y
boxContentX.value = (props.boxPos.x / props.width) * 100
let data = getAxisRange()
boxContentY.value = ((props.boxPos.y - data.min) / (data.max - data.min)) * 100
}
const getDotX = (e) => {
return (e / props.width) * 100
}
const getDotY = (e) => {
let data = getAxisRange()
return ((e - data.min) / (data.max - data.min)) * 100
}
const lineData = ref()
const getLineData = (fromId, toId) => {
const fromEl = document.getElementById(fromId);
const toEl = document.getElementById(toId);
//
const rulerWidth = 40
const rulerHeight = 20
//
const zoomLevel = ref(1)
const scale = computed(() =>
zoomLevel.value === 1 ? 1 : zoomLevel.value === 2 ? 10 : 100
)
const tickValue = computed(() =>
zoomLevel.value === 1 ? 100 : zoomLevel.value === 2 ? 10 : 1
)
const translateX = ref(0)
const translateY = ref(0)
//
const updateSize = () => {
if (wrapperRef.value) {
const rect = wrapperRef.value.getBoundingClientRect()
parentWidth.value = rect.width
parentHeight.value = rect.height
if (!fromEl || !toEl) {
console.warn("元素未找到:", fromId, toId);
return null;
}
//
const fromRect = fromEl.getBoundingClientRect();
const toRect = toEl.getBoundingClientRect();
const x1 = Math.floor(fromRect.left) + Math.floor(window.scrollX)
const y1 = Math.floor(fromRect.top) + Math.floor(window.scrollY)
const x2 = Math.floor(toRect.left) + Math.floor(window.scrollX) + 5
const y2 = Math.floor(toRect.top) + Math.floor(window.scrollY) + 5
const dx = x2 - x1;
const dy = y2 - y1;
const distance = Math.sqrt(dx * dx + dy * dy);
let rad = Math.atan2(dy, dx);
let deg = rad * (180 / Math.PI) - props.boxPos.rotate
if (deg < 0) deg += 360;
lineData.value = {distance, degree: deg}
}
onMounted(updateSize)
window.addEventListener("resize", updateSize)
//
const contentWidth = computed(() => parentWidth.value - rulerWidth)
const contentHeight = computed(() => parentHeight.value - rulerHeight)
const centerValue = ref(100)
const zoomType = ref(0)
//
function lonToPixel(lon) {
return ((lon - props.minlon) / (props.maxlon - props.minlon)) * contentWidth.value
function zoomScale(value, type) {
centerValue.value = value
zoomType.value = type
}
function latToPixel(lat) {
return ((lat - props.minlat) / (props.maxlat - props.minlat)) * contentHeight.value
function getAxisRange() {
if (!leftTicks.value || leftTicks.value.length === 0) return {min: 0, max: props.height}
const values = leftTicks.value.map(t => t.value)
return {min: Math.min(...values), max: Math.max(...values)}
}
const transformOriginX = ref(0)
const transformOriginY = ref(0)
const leftTicks = computed(() => {
const arr = []
//
function setZoom(center, level) {
zoomLevel.value = level
const contentX = lonToPixel(center.lon)
const contentY = latToPixel(center.lat)
const scaledX = (contentX + translateX.value) * scale.value
const scaledY = (contentY + translateY.value) * scale.value
transformOriginX.value = contentX
transformOriginY.value = contentHeight.value - contentY
const cx = lonToPixel(center.lon)
const cy = latToPixel(center.lat)
const targetX = contentWidth.value / 2
const targetY = contentHeight.value / 2
translateX.value = targetX - cx * scale.value
translateY.value = targetY - cy * scale.value
}
//
function formatTick(valueMeters) {
if (zoomLevel.value === 1) return Math.round(valueMeters / 100) * 100
if (zoomLevel.value === 2) return Math.round(valueMeters / 10) * 10
return Math.round(valueMeters)
}
// X
const xTicks = computed(() => {
const ticks = []
const pixelsPerLon = contentWidth.value / (props.maxlon - props.minlon)
const startLon = props.minlon - translateX.value / (pixelsPerLon * scale.value)
let val = startLon
while (val <= props.maxlon) {
const contentX = lonToPixel(val)
const pixel = contentX * scale.value + translateX.value
ticks.push({
value: val,
pixel,
display: formatTick((val - props.minlon) * 111000)
})
val += tickValue.value / 111000
if (centerValue.value === 0 || zoomType.value === 0) {
const step = 10
const steps = Math.floor(props.height / step)
for (let i = 0; i <= steps; i++) {
const value = i * step
const isMajor = value % 100 === 0
const percent = Math.floor((value / props.height) * 100)
arr.push({value, major: isMajor, percent})
}
if (arr[arr.length - 1].value < props.height) {
arr.push({value: props.height, major: props.height % 100 === 0, percent: 100})
}
return arr
}
return ticks
})
// Y
const yTicks = computed(() => {
const ticks = []
const pixelsPerLat = contentHeight.value / (props.maxlat - props.minlat)
const startLat = props.minlat - translateY.value / (pixelsPerLat * scale.value)
let factor = zoomType.value === 1 ? 10 : zoomType.value === 2 ? 100 : 1
let step = Math.floor(10 / factor)
if (step < 1) step = 1
let val = startLat
while (val <= props.maxlat) {
const contentY = latToPixel(val)
const pixel = contentY * scale.value + translateY.value
ticks.push({
value: val,
pixel,
display: formatTick((val - props.minlat) * 111000)
})
val += tickValue.value / 111000
const visibleCount = 21
const half = Math.floor((props.height / factor) / 2)
let startValue = centerValue.value - half * step
let endValue = centerValue.value + half * step
if (startValue < 0) {
endValue += -startValue
startValue = 0
}
return ticks
if (endValue > props.height) {
startValue -= endValue - props.height
endValue = props.height
if (startValue < 0) startValue = 0
}
const totalSteps = Math.floor((endValue - startValue) / step)
for (let i = 0; i <= totalSteps; i++) {
let value = startValue + i * step
value = Math.floor(value) //
let isMajor = false
if (zoomType.value === 1) {
isMajor = value % 10 === 0
} else if (zoomType.value === 2) {
isMajor = true // 2
} else {
isMajor = (value * factor) % 100 === 0
}
const percent = Math.floor(((value - startValue) / (endValue - startValue)) * 100)
arr.push({value, major: isMajor, percent})
}
return arr
})
const ticks = computed(() => {
const arr = []
const step = 10
const steps = Math.ceil(props.width / step) // 使ceil
// Box/
const boxContentX = computed(() => {
if (!props.boxPos) return 0
return lonToPixel(props.boxPos.x)
for (let i = 0; i <= steps; i++) {
let value = i * step
if (value > props.width) value = props.width //
const isMajor = value % 100 === 0
const percent = (value / props.width) * 100
arr.push({value, major: isMajor, percent})
}
return arr
})
const boxContentY = computed(() => {
if (!props.boxPos) return 0
return latToPixel(props.boxPos.y)
})
const BOX_SIZE = 30 // .box width/height
const DOT_SIZE = 10 // .dot width/height
const lineCoordinates = computed(() => {
if (!props.boxPos || !props.dots || props.dotIndex == null) return null
const startDot = props.dots[props.dotIndex]
if (!startDot) return null
// dot center
const startX = lonToPixel(startDot.lon)
const startY = latToPixel(startDot.lat) - (DOT_SIZE / 2) * (1 / scale.value)
// box
const boxBottomX = lonToPixel(props.boxPos.x)
const boxBottomY = latToPixel(props.boxPos.y) - 15
// box
const centerX = boxBottomX
const centerY = boxBottomY + (BOX_SIZE / 2) * (1 / scale.value)
const carHeadOffset = BOX_SIZE * 0.5
const localAttachX = 0
const localAttachY = carHeadOffset
const ox = localAttachX * (1 / scale.value)
const oy = localAttachY * (1 / scale.value)
const rotDeg = props.boxPos.rotate || 0
const rad = -rotDeg * Math.PI / 180
const rotatedX = ox * Math.cos(rad) - oy * Math.sin(rad)
const rotatedY = ox * Math.sin(rad) + oy * Math.cos(rad)
const endX = centerX + rotatedX
const endY = centerY + rotatedY
const dx = endX - startX
const dy = endY - startY
const width = Math.hypot(dx, dy)
const angle = -Math.atan2(dy, dx) * 180 / Math.PI
return {startX, startY, width, angle, endX, endY}
})
defineExpose({setZoom})
//
setTimeout(() => {
setZoom({lon: 120.005, lat: 30.005}, 2)
}, 3000)
setTimeout(() => {
setZoom({lon: 120.005, lat: 30.005}, 3)
}, 5000)
// setTimeout(() => {
// setZoom({lon: 120.005, lat: 30.005}, 2)
// }, 8000)
// setTimeout(() => {
// setZoom({lon: 120.005, lat: 30.005}, 1)
// }, 10000)
</script>
<style scoped>
<style scoped lang="less">
.map-wrapper {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background: #000;
}
background-color: #000;
.map-content {
position: absolute;
left: 40px; /* 左侧刻度尺宽度 */
bottom: 20px; /* 下方刻度尺高度 */
width: calc(100% - 40px);
height: calc(100% - 20px);
transform-origin: 0 0;
}
.left {
position: absolute;
width: 40px;
height: calc(100% - 40px);
background-color: #000;
top: 0;
left: 0;
.map-area {
width: 100%;
height: 100%;
background: #333;
}
/* 左侧刻度尺 */
.y-ruler {
position: absolute;
left: 0;
top: 0;
bottom: 20px;
width: 40px;
background: #000;
}
.baseline {
position: relative;
width: 100%;
height: 100%;
border-right: 1px solid #fff;
}
.y-ruler .tick {
position: absolute;
width: 100%;
height: 1px;
}
.tick-wrapper {
position: absolute;
right: 0;
display: flex;
flex-direction: row;
align-items: center;
transform: translateY(50%);
}
.y-ruler .line {
position: absolute;
right: 0;
width: 10px;
height: 1px;
background: #fff;
}
.tick {
height: 1px;
background-color: #fff;
}
.y-ruler .label {
position: absolute;
right: 12px;
top: -6px;
color: #fff;
font-size: 10px;
text-align: right;
}
.tick.minor {
width: 12px;
opacity: 0.6;
}
/* 下方刻度尺 */
.x-ruler {
position: absolute;
left: 40px;
right: 0;
bottom: 0;
height: 20px;
background: #000;
}
.tick.major {
width: 18px;
background-color: #fff;
}
.x-ruler .tick {
position: absolute;
height: 100%;
width: 1px;
}
.tick-label {
font-size: 12px;
color: #fff;
writing-mode: vertical-rl;
margin-left: 4px;
}
}
.x-ruler .line {
position: absolute;
top: 0;
width: 1px;
height: 5px;
background: #fff;
}
.bottom {
position: absolute;
width: calc(100% - 40px);
height: 40px;
background-color: #000;
bottom: 0;
left: 40px;
.x-ruler .label {
position: absolute;
top: 5px;
left: -5px;
color: #fff;
font-size: 10px;
}
/* Box元素 */
.box {
position: absolute;
width: 30px;
height: 30px;
background-image: url("../assets/car.png");
background-repeat: no-repeat;
background-size: 100% 100%;
transform-origin: center;
}
.baseline {
position: relative;
width: 100%;
height: 100%;
border-bottom: 1px solid #fff3;
}
.box .scan {
position: absolute;
background-image: url("../assets/scan.gif");
background-size: 100% 100%;
width: 100px;
height: 100px;
top: 50%;
right: 15px;
transform: translateY(-50%);
}
.tick-wrapper {
position: absolute;
top: 0;
display: flex;
flex-direction: column;
align-items: center;
}
.dot {
width: 10px;
height: 10px;
position: absolute;
background: radial-gradient(circle at center,
#ea1212 0%, rgba(228, 116, 116, 0.2) 100%);
border-radius: 50%;
}
.tick {
width: 1px;
background-color: #fff;
}
.connection-line {
position: absolute;
height: 2px;
background-color: transparent; /* 背景透明 */
border-top: 1px dashed #fff; /* 虚线样式 */
transform-origin: left center;
z-index: 1;
.tick.minor {
height: 12px;
opacity: 0.6;
}
.tick.major {
height: 18px;
background-color: #fff;
}
.tick-label {
position: absolute;
font-size: 12px;
color: #fff;
top: 100%;
}
}
.content {
position: absolute;
width: calc(100% - 40px);
height: calc(100% - 40px);
background-color: #666;
top: 0;
left: 40px;
.dot {
width: 10px;
height: 10px;
position: absolute;
background: radial-gradient(circle at center,
#ea1212 0%, rgba(228, 116, 116, 0.2) 100%);
border-radius: 50%;
}
.box {
position: absolute;
width: 30px;
height: 30px;
background-image: url("../assets/car.png");
background-repeat: no-repeat;
background-size: 100% 100%;
transform-origin: center;
.head {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 1px;
height: 1px;
.line {
position: absolute;
height: 1px;
border-top: 1px dashed #fff;
transform-origin: left center;
}
}
.scan {
position: absolute;
background-image: url("../assets/scan.gif");
background-size: 100% 100%;
width: 100px;
height: 100px;
top: 50%;
right: 15px;
transform: translateY(-50%);
}
}
}
}
</style>

@ -3,6 +3,7 @@ import {createWebHistory, createRouter, RouteRecordRaw} from 'vue-router';
// 公共路由
export const constantRoutes: RouteRecordRaw[] = [
{path: '/serve', component: () => import('@/views/serve/index.vue')},
{path: '/', component: () => import('@/views/index.vue')},
];

@ -1,10 +1,10 @@
import axios from 'axios';
import {ElMessage} from "element-plus";
import { ElMessage } from "element-plus";
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';
const service = axios.create({
baseURL: '/dev-api', timeout: 10000
baseURL: '/dev-api', timeout: 100000
});
service.interceptors.request.use(config => {
@ -16,7 +16,7 @@ service.interceptors.response.use(res => {
return res.data;
}, error => {
console.log('err' + error);
let {message} = error;
let { message } = error;
if (message == 'Network Error') {
message = '后端接口连接异常';
} else if (message.includes('timeout')) {
@ -24,11 +24,11 @@ service.interceptors.response.use(res => {
} else if (message.includes('Request failed with status code')) {
message = '系统接口' + message.substr(message.length - 3) + '异常';
}
ElMessage({message: message, type: 'error', duration: 5 * 1000});
ElMessage({ message: message, type: 'error', duration: 5 * 1000 });
return Promise.reject(error);
});
function request({method = 'get', url, data = {}, params = {}}) {
function request({ method = 'get', url, data = {}, params = {} }) {
return service({
method, url, data, params
});

@ -19,7 +19,7 @@ import {
setParasSignalpro,
getShildList,
addShildData,
delShildData, StartWork, StopWork, RestartWork, ShutDownWork
delShildData, StartWork, StopWork, RestartWork, ShutDownWork, getAllAirPort, getArea, GetAllCData
} from "@/api/api";
const scaleRatio = ref(window.innerWidth / 1080)
@ -69,21 +69,17 @@ const TOP = defineComponent({
const LEFT = defineComponent({
name: 'LEFT',
setup() {
const boxPos = ref({x: 120.005, y: 30.005, rotate: 0})
const boxPos = ref({x: 100, y: 100, rotate: 30})
return () => (
<div class="left">
<Ruler
ref={rulerRef}
index={dotIndex.value}
dots={dots.value}
dotIndex={1}
width={500}
height={1000}
boxPos={boxPos.value}
minlon={120}
maxlon={120.01}
minlat={30}
maxlat={30.01}
rightBottomLocation={{lon: 120.01, lat: 30.01}}
bottomScaleStep={10}
dots={dots.value}
dotIndex={0}
/>
</div>
)
@ -104,10 +100,15 @@ const RIGHT = defineComponent({
placeholder="请选择机场名称"
style={{width: "200px"}}
clearable
onUpdate:modelValue={val => (form1.value.region = val)}
onUpdate:modelValue={val => selectUpdate(val)}
>
<ElOption label="机场1" value="shanghai"/>
<ElOption label="机场2" value="beijing"/>
{
options1.value.map(i => {
return <ElOption label={i.name} value={i.id}/>
})
}
</ElSelect>
</ElFormItem>
@ -119,8 +120,12 @@ const RIGHT = defineComponent({
clearable
onUpdate:modelValue={val => (form1.value.region1 = val)}
>
<ElOption label="区域1" value="shanghai"/>
<ElOption label="区域2" value="beijing"/>
{
options2.value.map(i => {
return <ElOption label={i.name} value={i.id}/>
})
}
</ElSelect>
</ElFormItem>
@ -310,10 +315,13 @@ const RIGHT = defineComponent({
placeholder="请选择机场名称"
style={{width: "200px"}}
clearable
onUpdate:modelValue={val => (form6.value.region = val)}
onUpdate:modelValue={val => select2Update(val)}
>
<ElOption label="机场1" value="shanghai"/>
<ElOption label="机场2" value="beijing"/>
{
options1.value.map(i => {
return <ElOption label={i.name} value={i.id}/>
})
}
</ElSelect>
</ElFormItem>
@ -323,15 +331,18 @@ const RIGHT = defineComponent({
placeholder="请选择区域名称"
style={{width: "200px"}}
clearable
onUpdate:modelValue={val => (form6.value.region1 = val)}
onUpdate:modelValue={val => select3Update(val)}
>
<ElOption label="区域1" value="shanghai"/>
<ElOption label="区域2" value="beijing"/>
{
options2.value.map(i => {
return <ElOption label={i.name} value={i.id}/>
})
}
</ElSelect>
</ElFormItem>
<ElFormItem>
<ElButton type="primary" onClick={onSubmit}>确定</ElButton>
<ElButton type="primary" onClick={getArea2}>确定</ElButton>
</ElFormItem>
</ElForm>
</ElCard>
@ -624,21 +635,21 @@ let area = {
}
const dots = ref([
{
lon: 120.0051,
lat: 30.006
x: 150,
y: 98
},
{
lon: 120.0045,
lat: 30.005
x: 50,
y: 102
},
{
lon: 120.009,
lat: 30.006
x: 75,
y: 75
},
{
lon: 120.0046,
lat: 30.007
},
x: 125,
y: 125
}
])
const points = ref([
@ -771,6 +782,7 @@ const parasPosForm = ref({
})
onMounted(() => {
getTableData()
ad()
getOption()
getShildTableList()
@ -796,7 +808,7 @@ onMounted(() => {
name: '点位4',
},
]
getArea()
getArea1()
// setDot({
// "DarDatas": [{"Lon": 117.213524, "Lat": 36.837967}, {"Lon": 0.0, "Lat": 0.0}, {
@ -902,27 +914,27 @@ const getShildTableList = () => {
const ad = () => {
// 1. WebSocket ws:// wss://
// const socket = new WebSocket("ws://192.168.1.123:7789/ws");
// 1. WebSocket ws:// wss://
// const socket = new WebSocket("ws://192.168.1.123:7789/ws");
const socket = new WebSocket("ws://192.168.1.123:7789/ws");
// 2.
// 2.
socket.addEventListener("open", () => {
console.log("✅ WebSocket 连接成功");
});
// 3.
// 3.
socket.addEventListener("message", (event) => {
console.log(JSON.parse(event.data))
processData(JSON.parse(event.data))
});
// 4.
// 4.
socket.addEventListener("close", () => {
console.log("❌ WebSocket 已关闭");
});
// 5.
// 5.
socket.addEventListener("error", (error) => {
console.error("⚠️ WebSocket 出错:", error);
});
@ -946,7 +958,7 @@ const getPoint = (k) => {
points.value[k].lon = currentPosition.value.lon || 0
points.value[k].lat = currentPosition.value.lat || 0
}
const getArea = () => {
const getArea1 = () => {
if (points.value.length < 4) return
let data = points.value
@ -971,7 +983,7 @@ const getArea = () => {
}
const savePoint = () => {
localStorage.setItem('points', JSON.stringify(points.value))
getArea()
getArea1()
}
const setDot = (e) => {
let dots = e.DarDatas.map((v, k) => {
@ -995,12 +1007,72 @@ const table1Current = (e, v) => {
}
console.log(e)
}
const getBoundingBoxAndSize = (points) => {
if (!points || points.length === 0) return null;
// 1.
let lons = points.map(p => p.lon);
let lats = points.map(p => p.lat);
let maxLon = Math.max(...lons);
let minLon = Math.min(...lons);
let maxLat = Math.max(...lats);
let minLat = Math.min(...lats);
// 2. ()
const R = 6371000; //
const toRad = d => d * Math.PI / 180;
let dLat = toRad(maxLat - minLat);
let dLon = toRad(maxLon - minLon);
//
let midLat = (maxLat + minLat) / 2;
let midLatRad = toRad(midLat);
// 3.
let height = dLat * R;
let width = dLon * R * Math.cos(midLatRad);
return {
maxLon, minLon, maxLat, minLat,
width: Math.abs(width),
height: Math.abs(height)
};
}
const options1 = ref([])
const options2 = ref([])
const getTableData = async () => {
await getAllAirPort().then(e => {
options1.value = e.data
})
}
const selectUpdate = (val) => {
form1.value.region = val
getArea({airId: val}).then(e => {
options2.value = e.data
})
}
const select2Update = (val) => {
form6.value.region = val
getArea({airId: val}).then(e => {
options2.value = e.data
})
}
const select3Update = (val) => {
form6.value.region1 = val
}
const getArea2 = () => {
GetAllCData({airId: form6.value.region})
}
</script>
<style>
.app-container {
width: 1080px;
min-width: 1080px;
height: 200000px;
height: 100vh;
margin: 0 auto;
}
@ -1037,7 +1109,8 @@ const table1Current = (e, v) => {
height: 100%;
}
.item.click span, .item:hover span {
.item.click span,
.item:hover span {
color: #0D52BF;
border-bottom: 2px solid #0D52BF;
}

@ -0,0 +1,325 @@
<template>
<div class="table-container">
<!-- 主表格 -->
<el-card class="main-card">
<template #header>
<div class="card-header">
<div class="operation-buttons">
<el-button type="primary" @click="openAddDialog"></el-button>
</div>
</div>
</template>
<el-table :data="tableData" @selection-change="handleSelectionChange" style="width: 100%">
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="名称" />
<el-table-column label="操作" width=" 300">
<template #default="scope">
<el-button type="primary" size="small" @click="openAreaDialog(scope.row)">
设置区域
</el-button>
<el-button type="danger" size="small" @click="handleDelete(scope.row)">
删除
</el-button>
<el-button type="warning" size="small" @click="handleEdit(scope.row)">
修改
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 添加/修改对话框 -->
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="400px">
<el-form :model="form" label-width="80px">
<el-form-item label="名称">
<el-input v-model="form.name" placeholder="请输入名称" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit"></el-button>
</template>
</el-dialog>
<!-- 添加区域对话框 -->
<el-dialog v-model="areaDialogVisible" title="添加区域" width="800px">
<el-card>
<template #header>
<div class="card-header">
<span>区域管理</span>
<div class="operation-buttons">
<el-button type="primary" @click="openAreaAddDialog"></el-button>
</div>
</div>
</template>
<el-table :data="areaTableData" @selection-change="handleAreaSelectionChange" style="width: 100%">
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="名称" />
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button type="danger" size="small" @click="handleAreaDelete(scope.row)">
删除
</el-button>
<el-button type="warning" size="small" @click="handleAreaEdit(scope.row)">
修改
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<template #footer>
<el-button @click="areaDialogVisible = false">关闭</el-button>
</template>
</el-dialog>
<!-- 区域添加/修改对话框 -->
<el-dialog v-model="areaFormDialogVisible" :title="areaDialogTitle" width="400px">
<el-form :model="areaForm" label-width="80px">
<el-form-item label="名称">
<el-input v-model="areaForm.name" placeholder="请输入名称" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="areaFormDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleAreaSubmit"></el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getAllAirPort, addAirPort, updateAirPort, delAirPort, getArea, addArea, updateArea, delArea } from '@/api/api'
//
const tableData = ref([])
//
const selectedRows = ref([])
//
const dialogVisible = ref(false)
const areaDialogVisible = ref(false)
const areaFormDialogVisible = ref(false)
//
const form = reactive({
id: null,
name: ''
})
const areaForm = reactive({
id: null,
name: ''
})
//
const areaTableData = ref([])
//
const selectedAreaRows = ref([])
//
const currentRow = ref(null)
const currentAreaRow = ref(null)
//
const dialogTitle = computed(() => {
return form.id ? '修改' : '添加'
})
const areaDialogTitle = computed(() => {
return areaForm.id ? '修改' : '添加'
})
//
const handleSelectionChange = (selection) => {
selectedRows.value = selection
}
const handleAreaSelectionChange = (selection) => {
selectedAreaRows.value = selection
}
//
const openAddDialog = () => {
form.id = null
form.name = ''
dialogVisible.value = true
}
//
const openAreaDialog = (row) => {
currentRow.value = row
getArea({ airId: row.id }).then(e => {
areaTableData.value = e.data
areaDialogVisible.value = true
})
}
//
const openAreaAddDialog = (row = null) => {
areaForm.id = null
areaForm.name = ''
currentAreaRow.value = row
areaFormDialogVisible.value = true
}
//
const handleDelete = async (row) => {
try {
await ElMessageBox.confirm('确定要删除这条记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
delAirPort({ id: row.id }).then(e => {
getTableData()
ElMessage.success('删除成功')
})
} catch {
ElMessage.info('已取消删除')
}
}
//
const handleEdit = (row) => {
form.id = row.id
form.name = row.name
dialogVisible.value = true
}
//
const handleAreaDelete = async (row) => {
try {
await ElMessageBox.confirm('确定要删除这条记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
delArea({ id: row.id, fodAirId: currentRow.value.id }).then(async e => {
await getArea({ airId: currentRow.value.id }).then(e => {
areaTableData.value = e.data
ElMessage.success('删除成功')
})
})
} catch {
ElMessage.info('已取消删除')
}
}
const handleAreaEdit = (row) => {
areaForm.id = row.id
areaForm.name = row.name
areaFormDialogVisible.value = true
}
const handleAreaSubmit = () => {
if (!areaForm.name.trim()) {
ElMessage.warning('请输入名称')
return
}
if (areaForm.id) {
updateArea({ name: areaForm.name, id: areaForm.id, fodAirId: currentRow.value.id }).then(async e => {
await getArea({ airId: currentRow.value.id }).then(e => {
areaTableData.value = e.data
ElMessage.success('修改成功')
areaFormDialogVisible.value = false
})
})
} else {
//
addArea({ name: areaForm.name, fodAirId: currentRow.value.id }).then(async e => {
await getArea({ airId: currentRow.value.id }).then(e => {
areaTableData.value = e.data
ElMessage.success('添加成功')
areaFormDialogVisible.value = false
})
})
}
}
onMounted(() => {
getTableData()
})
const getTableData = async () => {
await getAllAirPort().then(e => {
tableData.value = e.data
})
}
//
const handleSubmit = () => {
if (!form.name.trim()) {
ElMessage.warning('请输入名称')
return
}
if (form.id) {
updateAirPort(form).then(e => {
ElMessage.success('修改成功')
getTableData()
dialogVisible.value = false
})
} else {
//
addAirPort({ name: form.name }).then(async e => {
await getTableData()
ElMessage.success('添加成功')
dialogVisible.value = false
})
}
}
</script>
<style scoped>
.table-container {
padding: 20px;
}
.main-card {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.operation-buttons {
display: flex;
gap: 10px;
}
.el-table {
margin-top: 10px;
}
.el-button {
margin-right: 5px;
}
.el-button:last-child {
margin-right: 0;
}
</style>

@ -1514,6 +1514,13 @@ convert-source-map@^2.0.0:
resolved "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
copy-anything@^2.0.1:
version "2.0.6"
resolved "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz#092454ea9584a7b7ad5573062b2a87f5900fc480"
integrity sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==
dependencies:
is-what "^3.14.1"
core-util-is@1.0.2:
version "1.0.2"
resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@ -1777,6 +1784,13 @@ err-code@^2.0.2:
resolved "https://registry.npmmirror.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9"
integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==
errno@^0.1.1:
version "0.1.8"
resolved "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==
dependencies:
prr "~1.0.1"
es-define-property@^1.0.0, es-define-property@^1.0.1:
version "1.0.1"
resolved "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
@ -2112,7 +2126,7 @@ got@^11.7.0, got@^11.8.5:
p-cancelable "^2.0.0"
responselike "^2.0.0"
graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6:
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6:
version "4.2.11"
resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@ -2216,7 +2230,7 @@ iconv-corefoundation@^1.1.7:
cli-truncate "^2.1.0"
node-addon-api "^1.6.3"
iconv-lite@^0.6.2:
iconv-lite@^0.6.2, iconv-lite@^0.6.3:
version "0.6.3"
resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
@ -2228,6 +2242,11 @@ ieee754@^1.1.13:
resolved "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
image-size@~0.5.0:
version "0.5.5"
resolved "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@ -2288,6 +2307,11 @@ is-unicode-supported@^0.1.0:
resolved "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
is-what@^3.14.1:
version "3.14.1"
resolved "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1"
integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==
isbinaryfile@^4.0.8:
version "4.0.10"
resolved "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3"
@ -2386,6 +2410,23 @@ lazy-val@^1.0.5:
resolved "https://registry.npmmirror.com/lazy-val/-/lazy-val-1.0.5.tgz#6cf3b9f5bc31cee7ee3e369c0832b7583dcd923d"
integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==
less@^4.4.1:
version "4.4.1"
resolved "https://registry.npmmirror.com/less/-/less-4.4.1.tgz#2f97168bf887ca6a9957ee69e16cc34f8b007cc7"
integrity sha512-X9HKyiXPi0f/ed0XhgUlBeFfxrlDP3xR4M7768Zl+WXLUViuL9AOPPJP4nCV0tgRWvTYvpNmN0SFhZOQzy16PA==
dependencies:
copy-anything "^2.0.1"
parse-node-version "^1.0.1"
tslib "^2.3.0"
optionalDependencies:
errno "^0.1.1"
graceful-fs "^4.1.2"
image-size "~0.5.0"
make-dir "^2.1.0"
mime "^1.4.1"
needle "^3.1.0"
source-map "~0.6.0"
lodash-es@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
@ -2452,6 +2493,14 @@ magic-string@^0.30.18:
dependencies:
"@jridgewell/sourcemap-codec" "^1.5.5"
make-dir@^2.1.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
dependencies:
pify "^4.0.1"
semver "^5.6.0"
make-fetch-happen@^10.2.1:
version "10.2.1"
resolved "https://registry.npmmirror.com/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz#f5e3835c5e9817b617f2770870d9492d28678164"
@ -2503,6 +2552,11 @@ mime-types@^2.1.12:
dependencies:
mime-db "1.52.0"
mime@^1.4.1:
version "1.6.0"
resolved "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
mime@^2.5.2:
version "2.6.0"
resolved "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
@ -2635,6 +2689,14 @@ nanoid@^3.3.11:
resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
needle@^3.1.0:
version "3.3.1"
resolved "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz#63f75aec580c2e77e209f3f324e2cdf3d29bd049"
integrity sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==
dependencies:
iconv-lite "^0.6.3"
sax "^1.2.4"
negotiator@^0.6.3:
version "0.6.4"
resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7"
@ -2739,6 +2801,11 @@ package-json-from-dist@^1.0.0:
resolved "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505"
integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
parse-node-version@^1.0.1:
version "1.0.1"
resolved "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b"
integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
@ -2777,6 +2844,11 @@ picomatch@^4.0.2, picomatch@^4.0.3:
resolved "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042"
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
pify@^4.0.1:
version "4.0.1"
resolved "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
plist@3.1.0, plist@^3.0.4, plist@^3.0.5, plist@^3.1.0:
version "3.1.0"
resolved "https://registry.npmmirror.com/plist/-/plist-3.1.0.tgz#797a516a93e62f5bde55e0b9cc9c967f860893c9"
@ -2823,6 +2895,11 @@ proxy-from-env@^1.1.0:
resolved "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
prr@~1.0.1:
version "1.0.1"
resolved "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==
pump@^3.0.0:
version "3.0.3"
resolved "https://registry.npmmirror.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d"
@ -2969,7 +3046,7 @@ semver-compare@^1.0.0:
resolved "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==
semver@^5.5.0:
semver@^5.5.0, semver@^5.6.0:
version "5.7.2"
resolved "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
@ -3064,7 +3141,7 @@ source-map-support@^0.5.19:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map@^0.6.0:
source-map@^0.6.0, source-map@~0.6.0:
version "0.6.1"
resolved "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
@ -3193,6 +3270,11 @@ truncate-utf8-bytes@^1.0.0:
dependencies:
utf8-byte-length "^1.0.1"
tslib@^2.3.0:
version "2.8.1"
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
type-fest@^0.13.1:
version "0.13.1"
resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"

Loading…
Cancel
Save