Web Tool for Comparing Commute Distances Using AMap API, Vue, and ECharts
This article describes a personal project that automates the calculation of commuting distances and times between multiple residential areas and two workplace locations by leveraging the AMap API within a Vue‑based frontend, generating Excel reports and visual charts for easy comparison.
The author needed to find a rental house midway between two workplaces and decided to automate the commute calculation using AMap’s mapping API.
Requirements include inputting multiple addresses, selecting two target locations, displaying distance and time in a table, exporting results to Excel, and visualizing them with charts.
The solution is built as a single‑page web application using Vue 2, Element UI, the AMap JavaScript SDK, xlsx for Excel generation, and ECharts for bar‑line charts. The code creates separate AMap plugins for transit, driving, and riding, queries routes asynchronously, and uses a generator to throttle requests.
Challenges encountered are daily API call limits and plugin conflicts, which are mitigated by limiting request frequency and loading one plugin at a time.
A demo shows the interface, generated tables, and charts; the full source code is provided below.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width" />
<link rel="stylesheet" href="https://a.amap.com/jsapi_demos/static/demo-center/css/demo-center.css" />
<script src="https://a.amap.com/jsapi_demos/static/demo-center/js/demoutils.js"></script>
<script type="text/javascript">
window._AMapSecurityConfig = { securityJsCode: "你的高德Token" };
</script>
<script type="text/javascript" src="https://webapi.amap.com/maps?v=2.0&key=你的高德Key"></script>
<script type="text/javascript" src="https://cache.amap.com/lbs/static/addToolbar.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css" />
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://unpkg.com/[email protected]/dist/xlsx.full.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>
</head>
<body>
<div id="container" style="display: none"></div>
<div id="panel" style="display: none"></div>
<div id="vueContainer" style="padding: 16px">
<table id="myTable" style="display: none">
<thead>
<tr>
<th>小区名称</th>
<th>{{formData.target1}}最短距离</th>
<th>{{formData.target1}}最快用时</th>
<template v-if="formData.target2">
<th>{{formData.target2}}最短距离</th>
<th>{{formData.target2}}最快用时</th>
</template>
</tr>
</thead>
<tbody>
<tr v-for="item in excelData">
<td>{{item.name}}</td>
<td>{{item.distance1}}</td>
<td>{{item.time1}}</td>
<template v-if="formData.target2">
<td>{{item.distance2}}</td>
<td>{{item.time2}}</td>
</template>
</tr>
</tbody>
</table>
<el-form :model="formData">
<el-form-item label="所在城市">
<el-input v-model="formData.city" />
</el-form-item>
<el-form-item label="起点">
<el-input type="textarea" :rows="5" v-model="formData.areas" />
</el-form-item>
<el-form-item label="终点1">
<el-input v-model="formData.target1" />
</el-form-item>
<el-form-item label="终点2">
<el-input v-model="formData.target2" />
</el-form-item>
<el-form-item label="出行方式">
<el-select v-model="formData.type">
<el-option value="Transfer" label="公交地铁"></el-option>
<el-option value="Driving" label="驾车"></el-option>
<el-option value="Riding" label="骑行"></el-option>
</el-select>
</el-form-item>
</el-form>
<el-button type="primary" @click="search" :disabled="loading">查询</el-button>
<el-button @click="exportToExcel">生成Excel</el-button>
<el-table border :data="dataList">
<el-table-column prop="from" label="小区名称"></el-table-column>
<el-table-column :label="formData.target1 || '-'">
<el-table-column prop="minDistance1" label="最短距离">
<template #default="{row}">{{row[formData.target1].minDistance}}</template>
</el-table-column>
<el-table-column prop="minTime1" label="最短时间">
<template #default="{row}">{{row[formData.target1].minTime}}</template>
</el-table-column>
<el-table-column prop="name" label="具体路径">
<template #default="{row}">
<div v-for="(plan,pIndex) in row[formData.target1].plans" :key="pIndex">
方案{{pIndex +1}}: {{plan.instructions?.join(',')}}
<b style="color: burlywood">总用时: {{plan.timeTotal || plan.time || 0}} 总距离:{{plan.distanceTotal || plan.distance || 0}}</b>
<el-tag type="primary" v-if="plan.shortest"> 最短距离 </el-tag>
<el-tag type="danger" v-if="plan.fastest">最快用时</el-tag>
</div>
</template>
</el-table-column>
</el-table-column>
<el-table-column :label="formData.target2 || '-'">
<el-table-column prop="minDistance2" label="最短距离">
<template #default="{row}">{{row[formData.target2].minDistance}}</template>
</el-table-column>
<el-table-column prop="minTime2" label="最短时间">
<template #default="{row}">{{row[formData.target2].minTime}}</template>
</el-table-column>
<el-table-column prop="name" label="具体路径">
<template #default="{row}">
<div v-for="(plan,pIndex) in row[formData.target2].plans" :key="pIndex">
方案{{pIndex +1}}: {{plan.instructions?.join(',')}}
<b style="color: burlywood">总用时: {{plan.timeTotal || plan.time || 0}} 总距离:{{plan.distanceTotal || plan.distance || 0}}</b>
<el-tag type="primary" v-if="plan.shortest"> 最短距离 </el-tag>
<el-tag type="danger" v-if="plan.fastest">最快用时</el-tag>
</div>
</template>
</el-table-column>
</el-table-column>
</el-table>
<div id="chart-container" style="margin-top: 32px; height: 500px; overflow: hidden"></div>
</div>
<script type="text/javascript">
map = new AMap.Map("container", { resizeEnable: true, zoom: 13 });
</script>
<script type="text/javascript" src="https://webapi.amap.com/maps?v=2.0&key=你的高德Key&plugin=AMap.Transfer"></script>
<script type="text/javascript"> TransferAMap = AMap; </script>
<script type="text/javascript" src="https://webapi.amap.com/maps?v=2.0&key=你的高德Key&plugin=AMap.Driving"></script>
<script type="text/javascript"> DrivingAMap = AMap; </script>
<script type="text/javascript" src="https://webapi.amap.com/maps?v=2.0&key=你的高德Key&plugin=AMap.Riding "></script>
<script type="text/javascript"> RidingAMap = AMap; </script>
<script type="text/javascript">
const vm = new Vue({
el: "#vueContainer",
data() { return { myChart: null, loading: false, dataList: [], formData: { city: "杭州", type: "Transfer", areas: "", target1: "平安金融中心", target2: "金沙中心" } }; },
computed: {
usefulAreas() { return this.formData.areas.split("\n").filter(item => !!item); },
excelData() { return; }
},
mounted() {
const dom = document.getElementById("chart-container");
this.myChart = echarts.init(dom, null, { renderer: "canvas", useDirtyRect: false });
window.addEventListener("resize", this.myChart.resize);
},
methods: {
formatDistance(distance) { return (distance / 1000).toFixed(2) + "公里"; },
formatTime(time) { return Math.ceil(time / 60) + "分钟"; },
searchFn(source, from) { /* omitted for brevity */ },
*generateRequests(arr) { for (let from of arr) { yield from; } },
async search() { /* omitted for brevity */ },
exportToExcel() { var table = document.getElementById("myTable"); var wb = XLSX.utils.table_to_book(table, { sheet: "Sheet JS" }); XLSX.writeFile(wb, "租房.xlsx"); },
draw() { /* chart option construction omitted for brevity */ if (option && typeof option === "object") { this.myChart.setOption(option); } }
}
});
</script>
</body>
</html>Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.