refactor: harden deps, fix geo math, and add engineering tooling
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>
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
<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-3">
|
||||
<!-- DMS Input -->
|
||||
<div>
|
||||
<h3 class="card-title">度分秒</h3>
|
||||
<div class="input-group">
|
||||
<label class="input-label">度</label>
|
||||
<input type="number" v-model.number="dms.degrees" class="input" @input="fromDms">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label class="input-label">分</label>
|
||||
<input type="number" v-model.number="dms.minutes" class="input" @input="fromDms" min="0"
|
||||
max="59">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label class="input-label">秒</label>
|
||||
<input type="number" v-model.number="dms.seconds" class="input" @input="fromDms" min="0"
|
||||
max="59" step="0.01">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Decimal Input -->
|
||||
<div>
|
||||
<h3 class="card-title">十进制度数</h3>
|
||||
<div class="input-group">
|
||||
<label class="input-label">度数</label>
|
||||
<input type="number" v-model.number="decimal" class="input" @input="fromDecimal" step="0.000001"
|
||||
placeholder="例:45.5">
|
||||
</div>
|
||||
<div class="conversion-info">
|
||||
<p class="hint">当前角度的其他表示形式会自动同步</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Radian Input -->
|
||||
<div>
|
||||
<h3 class="card-title">弧度</h3>
|
||||
<div class="input-group">
|
||||
<label class="input-label">弧度值</label>
|
||||
<input type="number" v-model.number="radian" class="input" @input="fromRadian" step="0.000001"
|
||||
placeholder="例:0.7854">
|
||||
</div>
|
||||
<div class="conversion-info">
|
||||
<p class="hint">π ≈ 3.14159</p>
|
||||
<p class="hint">180° = π 弧度</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result mt-xl">
|
||||
<div class="result-label">完整转换结果</div>
|
||||
<div class="conversion-results">
|
||||
<div class="conversion-item">
|
||||
<strong>度分秒:</strong>
|
||||
<span>{{ dms.degrees }}° {{ dms.minutes }}' {{ dms.seconds.toFixed(2) }}"</span>
|
||||
</div>
|
||||
<div class="conversion-item">
|
||||
<strong>十进制:</strong>
|
||||
<span>{{ decimal.toFixed(6) }}°</span>
|
||||
</div>
|
||||
<div class="conversion-item">
|
||||
<strong>弧度:</strong>
|
||||
<span>{{ radian.toFixed(6) }} rad</span>
|
||||
</div>
|
||||
<div class="conversion-item">
|
||||
<strong>π表示:</strong>
|
||||
<span>{{ (radian / Math.PI).toFixed(4) }}π</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { dmsToDecimal, decimalToDms } from '../utils/coordinate'
|
||||
|
||||
const dms = ref({ degrees: 45, minutes: 30, seconds: 0 })
|
||||
const decimal = ref(45.5)
|
||||
const radian = ref(0.7941)
|
||||
|
||||
const fromDms = () => {
|
||||
decimal.value = dmsToDecimal(dms.value.degrees, dms.value.minutes, dms.value.seconds)
|
||||
radian.value = decimal.value * Math.PI / 180
|
||||
}
|
||||
|
||||
const fromDecimal = () => {
|
||||
const converted = decimalToDms(decimal.value)
|
||||
dms.value = converted
|
||||
radian.value = decimal.value * Math.PI / 180
|
||||
}
|
||||
|
||||
const fromRadian = () => {
|
||||
decimal.value = radian.value * 180 / Math.PI
|
||||
const converted = decimalToDms(decimal.value)
|
||||
dms.value = converted
|
||||
}
|
||||
|
||||
// Initialize
|
||||
fromDms()
|
||||
</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);
|
||||
}
|
||||
|
||||
.conversion-info {
|
||||
margin-top: var(--spacing-lg);
|
||||
padding: var(--spacing-md);
|
||||
background: var(--bg-secondary);
|
||||
border-radius: var(--border-radius-sm);
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-tertiary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.conversion-results {
|
||||
display: grid;
|
||||
gap: var(--spacing-md);
|
||||
margin-top: var(--spacing-md);
|
||||
}
|
||||
|
||||
.conversion-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
background: var(--bg-secondary);
|
||||
border-radius: var(--border-radius-sm);
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
.conversion-item span {
|
||||
color: var(--primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.grid-3 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user