Replace xlsx with exceljs, unify CRS/DMS logic, move pages to views with shared map/toast composables, and add Vitest/ESLint/Prettier plus docs. Co-authored-by: Cursor <cursoragent@cursor.com>
244 lines
6.6 KiB
Vue
244 lines
6.6 KiB
Vue
<template>
|
||
<div class="container">
|
||
<div class="tool-header">
|
||
<router-link to="/" class="back-link">← 返回首页</router-link>
|
||
<h2>🧭 方位角计算工具</h2>
|
||
<p>根据两点坐标计算方位角和象限角</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="grid grid-2">
|
||
<div>
|
||
<h3 class="card-title">起点坐标</h3>
|
||
<div class="input-group">
|
||
<label class="input-label">X坐标(或经度)</label>
|
||
<input type="number" v-model.number="point1.x" class="input" step="0.0001" placeholder="例:0">
|
||
</div>
|
||
<div class="input-group">
|
||
<label class="input-label">Y坐标(或纬度)</label>
|
||
<input type="number" v-model.number="point1.y" class="input" step="0.0001" placeholder="例:0">
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h3 class="card-title">终点坐标</h3>
|
||
<div class="input-group">
|
||
<label class="input-label">X坐标(或经度)</label>
|
||
<input type="number" v-model.number="point2.x" class="input" step="0.0001" placeholder="例:100">
|
||
</div>
|
||
<div class="input-group">
|
||
<label class="input-label">Y坐标(或纬度)</label>
|
||
<input type="number" v-model.number="point2.y" class="input" step="0.0001" placeholder="例:100">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button @click="calculate" class="btn btn-primary mt-lg">计算方位角</button>
|
||
|
||
<div v-if="result" class="result mt-xl">
|
||
<div class="result-label">计算结果</div>
|
||
|
||
<div class="bearing-display">
|
||
<div class="bearing-circle">
|
||
<div class="compass-marker north">N</div>
|
||
<div class="compass-marker east">E</div>
|
||
<div class="compass-marker south">S</div>
|
||
<div class="compass-marker west">W</div>
|
||
<div class="bearing-arrow" :style="{ transform: `rotate(${result.azimuth}deg)` }"></div>
|
||
</div>
|
||
|
||
<div class="bearing-values">
|
||
<div class="value-item">
|
||
<span class="value-label">真方位角</span>
|
||
<span class="value-number">{{ result.azimuth.toFixed(4) }}°</span>
|
||
</div>
|
||
<div class="value-item">
|
||
<span class="value-label">象限角</span>
|
||
<span class="value-number">{{ result.quadrant }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="info-box mt-lg">
|
||
<p><strong>说明:</strong></p>
|
||
<p>真方位角:从正北方向顺时针旋转到目标方向的角度(0°-360°)</p>
|
||
<p>象限角:从南北方向量起,向东或向西的角度</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref } from 'vue'
|
||
import { calculateBearing } from '../utils/geometry'
|
||
|
||
const point1 = ref({ x: 0, y: 0 })
|
||
const point2 = ref({ x: 100, y: 100 })
|
||
const result = ref(null)
|
||
|
||
const calculate = () => {
|
||
result.value = calculateBearing(
|
||
point1.value.x,
|
||
point1.value.y,
|
||
point2.value.x,
|
||
point2.value.y
|
||
)
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.tool-header {
|
||
text-align: center;
|
||
margin-bottom: var(--spacing-xl);
|
||
}
|
||
|
||
.back-link {
|
||
display: inline-block;
|
||
color: var(--text-secondary);
|
||
text-decoration: none;
|
||
margin-bottom: var(--spacing-md);
|
||
transition: color var(--transition-base);
|
||
}
|
||
|
||
.back-link:hover {
|
||
color: var(--primary);
|
||
}
|
||
|
||
.bearing-display {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: var(--spacing-xl);
|
||
align-items: center;
|
||
margin-top: var(--spacing-lg);
|
||
}
|
||
|
||
.bearing-circle {
|
||
position: relative;
|
||
width: 200px;
|
||
height: 200px;
|
||
margin: 0 auto;
|
||
border: 3px solid var(--border-color);
|
||
border-radius: 50%;
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
.compass-marker {
|
||
position: absolute;
|
||
font-weight: 700;
|
||
color: var(--text-secondary);
|
||
font-size: var(--font-size-sm);
|
||
}
|
||
|
||
.compass-marker.north {
|
||
top: 10px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
color: var(--danger);
|
||
}
|
||
|
||
.compass-marker.east {
|
||
right: 10px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
}
|
||
|
||
.compass-marker.south {
|
||
bottom: 10px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
}
|
||
|
||
.compass-marker.west {
|
||
left: 10px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
}
|
||
|
||
.bearing-arrow {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
width: 4px;
|
||
height: 80px;
|
||
background: var(--gradient-primary);
|
||
transform-origin: bottom center;
|
||
margin-left: -2px;
|
||
margin-top: -80px;
|
||
border-radius: 2px;
|
||
transition: transform 0.5s ease;
|
||
}
|
||
|
||
.bearing-arrow::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: -10px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 0;
|
||
height: 0;
|
||
border-left: 8px solid transparent;
|
||
border-right: 8px solid transparent;
|
||
border-bottom: 12px solid var(--primary);
|
||
}
|
||
|
||
.bearing-values {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--spacing-lg);
|
||
}
|
||
|
||
.value-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: var(--spacing-md);
|
||
background: var(--bg-secondary);
|
||
border-radius: var(--border-radius-sm);
|
||
}
|
||
|
||
.value-label {
|
||
font-size: var(--font-size-sm);
|
||
color: var(--text-secondary);
|
||
margin-bottom: var(--spacing-xs);
|
||
}
|
||
|
||
.value-number {
|
||
font-size: var(--font-size-2xl);
|
||
font-weight: 700;
|
||
background: var(--gradient-primary);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
}
|
||
|
||
.info-box {
|
||
padding: var(--spacing-lg);
|
||
background: var(--bg-secondary);
|
||
border-radius: var(--border-radius-sm);
|
||
border-left: 4px solid var(--primary);
|
||
}
|
||
|
||
.info-box p {
|
||
margin-bottom: var(--spacing-xs);
|
||
font-size: var(--font-size-sm);
|
||
color: var(--text-secondary);
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.info-box p:first-child {
|
||
font-weight: 600;
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.bearing-display {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.bearing-circle {
|
||
width: 180px;
|
||
height: 180px;
|
||
}
|
||
}
|
||
</style>
|