index.vue:
注意:这里的
return 'https://www.******.com/bus_admin_java'
这里是区分 H5和apk调用接口地址的不同而设置的。之后凡是调用接口的方法就使用封装后的request()
<template>
<view class="container">
<view class="title">实时位置追踪器</view>
<view
class="btn"
:class="{ active: tracking }"
@click="toggleTracking"
>
{{ tracking ? '停止追踪' : '点击开始' }}
</view>
<view v-if="latitude !== null && longitude !== null" class="location-info">
当前经度: {{ longitude }} <br />
当前纬度: {{ latitude }}
</view>
<!-- 雷达扫描效果 -->
<view class="radar" v-if="tracking">
<view class="circle circle1"></view>
<view class="circle circle2"></view>
<view class="circle circle3"></view>
<view class="pulse"></view>
</view>
</view>
</template>
<script>
const BASE_URL = (() => {
// #ifdef H5
return '/api'
// #endif
// #ifdef APP-PLUS
return 'https://www.******.com/bus_admin_java'
// #endif
return '/api'
})()
function request(options) {
return new Promise((resolve, reject) => {
uni.request({
url: BASE_URL + options.url,
method: options.method || 'GET',
data: options.data || {},
header: {
'content-type': 'application/json',
...(options.header || {})
},
timeout: 15000,
success: (res) => {
resolve(res)
},
fail: (err) => {
reject(err)
}
})
})
}
export default {
data() {
return {
latitude: null,
longitude: null,
tracking: false,
timer: null
}
},
methods: {
toggleTracking() {
if (this.tracking) {
this.tracking = false
this.stopTracking()
} else {
this.startTracking()
}
},
startTracking() {
this.stopTracking()
// 先尝试获取一次位置
this.getLocation(true)
},
stopTracking() {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
},
getLocation(isFirst = false) {
uni.getLocation({
type: 'wgs84',
success: (res) => {
this.latitude = res.latitude
this.longitude = res.longitude
console.log('当前位置', res)
uni.showToast({
title: `经度:${res.longitude.toFixed(4)} 纬度:${res.latitude.toFixed(4)}`,
icon: 'none',
duration: 3000
})
this.sendPosition(res.longitude, res.latitude)
// 第一次成功后再开启轮询
if (isFirst) {
this.tracking = true
this.timer = setInterval(() => {
this.getLocation(false)
}, 3000)
}
},
fail: (err) => {
console.error('获取位置失败', err)
this.tracking = false
this.stopTracking()
this.handleLocationFail(err)
}
})
},
handleLocationFail(err) {
let content = '获取位置失败,请检查定位服务是否开启'
// #ifdef APP-PLUS
content = '请先开启系统定位服务,并允许当前应用使用定位权限,是否前往设置?'
// #endif
// #ifdef H5
content = 'H5 获取定位失败,请确认浏览器已允许定位,并且当前页面运行在 HTTPS 或 localhost 环境下'
// #endif
uni.showModal({
title: '定位失败',
content,
success: (res) => {
if (!res.confirm) return
// App 端支持打开设置
// #ifdef APP-PLUS
uni.openSetting({
success: (settingRes) => {
console.log('设置页返回', settingRes)
uni.showToast({
title: '请重新点击开始定位',
icon: 'none'
})
},
fail: (settingErr) => {
console.error('打开设置失败', settingErr)
uni.showToast({
title: '无法打开设置页',
icon: 'none'
})
}
})
// #endif
// H5 没有统一的 openSetting,给提示即可
// #ifdef H5
uni.showToast({
title: '请在浏览器地址栏附近开启定位权限',
icon: 'none',
duration: 3000
})
// #endif
}
})
},
sendPosition(longitude, latitude) {
request({
url: '/bus/position/save',
method: 'POST',
data: {
longitude: longitude,
latitude: latitude
},
success: (res) => {
console.log('上传成功', res.data)
},
fail: (err) => {
console.error('上传失败', err)
}
})
}
},
beforeDestroy() {
this.stopTracking()
},
onUnload() {
this.stopTracking()
}
}
</script>
<style>
.container {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
padding: 40px 20px;
background: radial-gradient(circle at center, #0a0a0a, #001010);
min-height: 100vh;
color: #00ffff;
font-family: "Microsoft YaHei", sans-serif;
}
.title {
font-size: 22px;
font-weight: bold;
margin-bottom: 20px;
}
.location-info {
margin-top: 20px;
font-size: 16px;
text-align: center;
line-height: 1.8;
}
.btn {
margin-top: 20px;
padding: 12px 24px;
background: #002f2f;
color: #00ffff;
border: 1px solid #00ffff;
border-radius: 8px;
font-size: 16px;
text-align: center;
transition: all 0.3s ease;
}
.btn.active {
background: #00ffff;
color: #001010;
box-shadow: 0 0 10px #00ffff;
}
.btn:active {
transform: scale(0.96);
}
/* 雷达扫描效果 */
.radar {
position: relative;
width: 200px;
height: 200px;
margin-top: 40px;
}
.circle {
position: absolute;
border: 2px solid rgba(0, 255, 255, 0.3);
border-radius: 50%;
animation: pulseScale 3s linear infinite;
}
.circle1 {
width: 60px;
height: 60px;
top: 70px;
left: 70px;
animation-delay: 0s;
}
.circle2 {
width: 120px;
height: 120px;
top: 40px;
left: 40px;
animation-delay: 1s;
}
.circle3 {
width: 180px;
height: 180px;
top: 10px;
left: 10px;
animation-delay: 2s;
}
.pulse {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 255, 255, 0.1);
border-radius: 50%;
animation: rotateRadar 4s linear infinite;
}
@keyframes pulseScale {
0% {
transform: scale(0.2);
opacity: 0.5;
}
50% {
transform: scale(1);
opacity: 0.2;
}
100% {
transform: scale(0.2);
opacity: 0.5;
}
}
@keyframes rotateRadar {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
main.js:
import App from './App.vue'
// #ifndef VUE3
import Vue from 'vue'
import './uni.promisify.adaptor'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
return {
app
}
}
// #endif
mainifest.json:
{
"name" : "bus_uniapp",
"appid" : "__UNI__7E751FC",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App特有相关 */
"app-plus" : {
"permissions" : {
"Location" : {
"desc" : "用于获取当前位置"
}
},
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* 模块配置 */
"modules" : {},
/* 应用发布信息 */
"distribute" : {
/* android打包配置 */
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios打包配置 */
"ios" : {
"dSYMs" : false
},
/* SDK配置 */
"sdkConfigs" : {}
}
},
/* 快应用特有相关 */
"quickapp" : {},
/* 小程序特有相关 */
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3"
}
注意开通:
vite.config.js: (解决H5的跨域的问题)
// vite.config.js
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
uni(),
],
server: {
// 1. 配置代理规则
proxy: {
// 2. '/api' 是你自定义的请求前缀
'/api': {
// 3. target 是你的目标后端接口的真实地址
// target: 'http://localhost:9002',
target: 'https://www.*****.com/bus_admin_java',
// 4. 改变请求源头,解决大部分跨域问题,必须为 true
changeOrigin: true,
// 5. (可选) 重写路径,移除 '/api' 前缀
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})