JAVA低空经济无人机飞手接单平台系统源码的前端代码示例
下面给你一套可直接运行/二开的「JAVA 低空经济无人机飞手接单平台」前端代码示例UniApp 3.0 Vue3编译到微信小程序/H5/APP均可并配合后端Spring Boot接口做完整前后端联调示例。 项目结构前端src/ ├── common/ │ └── api.js # 统一请求封装 ├── components/ │ └── order-card.vue # 订单卡片组件 ├── pages/ │ ├── login/ │ │ └── index.vue # 登录客户/飞手双角色 │ ├── index/ │ │ └── index.vue # 首页地图附近订单 │ ├── order/ │ │ ├── list.vue # 可接订单列表 │ │ └── detail.vue # 订单详情接单 │ ├── pilot/ │ │ └── dashboard.vue # 飞手工作台 │ └── user/ │ └── profile.vue # 个人中心 ├── store/ │ ├── user.js # Pinia 用户状态 │ └── order.js # 订单状态 └── static/ └── marker-default.png1common/api.js— 统一请求封装含JWT Tokenjs// common/api.js const BASE https://your-api-domain.com/api const request (options) { return new Promise((resolve, reject) { const token uni.getStorageSync(token) uni.request({ url: BASE options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: token ? Bearer ${token} : }, success: (res) { if (res.statusCode 200) resolve(res.data) else if (res.statusCode 401) { uni.removeStorageSync(token) uni.reLaunch({ url: /pages/login/index }) reject(new Error(登录已过期)) } else reject(res.data) }, fail: reject }) }) } export const api { login: (data) request({ url: /auth/login, method: POST, data }), getOrders: (params) request({ url: /orders, data: params }), createOrder: (data) request({ url: /orders, method: POST, data }), acceptOrder: (orderId) request({ url: /orders/${orderId}/accept, method: POST }), getPilotInfo: () request({ url: /pilot/info }) }2store/user.js— Pinia 用户状态角色判断js// store/user.js import { defineStore } from pinia import { ref, computed } from vue export const useUserStore defineStore(user, () { const userInfo ref(null) const token ref(uni.getStorageSync(token) || ) const isPilot computed(() userInfo.value?.role pilot) function login(data) { userInfo.value data token.value data.token uni.setStorageSync(token, data.token) } function logout() { userInfo.value null token.value uni.removeStorageSync(token) } return { userInfo, token, isPilot, login, logout } })3pages/login/index.vue— 登录页客户/飞手双角色vuetemplate view classlogin-container text classtitle低空经济 · 飞手接单平台/text view classform input v-modelphone placeholder手机号 typenumber / input v-modelpassword placeholder密码 typepassword / !-- 角色切换 -- view classrole-switch text :class{ active: role user } clickrole user客户/text text :class{ active: role pilot } clickrole pilot飞手/text /view button classbtn clickhandleLogin登 录/button /view /view /template script setup import { ref } from vue import { useUserStore } from /store/user const phone ref() const password ref() const role ref(user) const userStore useUserStore() async function handleLogin() { try { const res await api.login({ phone: phone.value, password: password.value, role: role.value }) userStore.login({ ...res.data, role: role.value }) if (role.value pilot) { uni.reLaunch({ url: /pages/pilot/dashboard }) } else { uni.reLaunch({ url: /pages/index/index }) } } catch (e) { uni.showToast({ title: e.message || 登录失败, icon: none }) } } /script style scoped .login-container { padding: 60rpx 40rpx; } .title { font-size: 44rpx; font-weight: bold; display: block; text-align: center; margin-bottom: 60rpx; } .form input { border: 1rpx solid #ddd; border-radius: 12rpx; padding: 24rpx; margin-bottom: 24rpx; } .role-switch { display: flex; justify-content: center; gap: 40rpx; margin: 20rpx 0; } .role-switch text { padding: 10rpx 30rpx; border-radius: 30rpx; } .role-switch .active { background: #1890ff; color: #fff; } .btn { background: #1890ff; color: #fff; border-radius: 12rpx; margin-top: 20rpx; } /style4pages/index/index.vue— 首页高德地图 附近订单vuetemplate view map idmap :latitudelocation.lat :longitudelocation.lng :markersmarkers :scale14 taphandleMapTap stylewidth: 100%; height: 50vh / scroll-view scroll-y classorder-list view v-foritem in nearbyOrders :keyitem.id classorder-item text classtitle{{ item.title }}/text text classprice¥{{ item.price }}/text text classdist距离 {{ item.distance }}m/text button sizemini clickgoDetail(item.id)查看详情/button /view /scroll-view /view /template script setup import { ref, onMounted } from vue import { api } from /common/api const location ref({ lat: 39.908823, lng: 116.397470 }) // 默认北京 const nearbyOrders ref([]) const markers ref([]) onMounted(async () { // 获取定位 uni.getLocation({ type: gcj02, success: (res) { location.value { lat: res.latitude, lng: res.longitude } }}) // 加载附近订单 const res await api.getOrders({ lat: location.value.lat, lng: location.value.lng, radius: 5000 }) nearbyOrders.value res.data || [] // 生成地图标记 markers.value nearbyOrders.value.map((o, i) ({ id: i, latitude: o.latitude, longitude: o.longitude, title: o.title, iconPath: /static/marker-default.png, width: 30, height: 30 })) }) function goDetail(id) { uni.navigateTo({ url: /pages/order/detail?id${id} }) } /script style scoped .order-list { height: 50vh; padding: 20rpx; } .order-item { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 20rpx; } .title { font-size: 32rpx; font-weight: bold; display: block; } .price { color: #ff4d4f; font-size: 36rpx; } .dist { color: #999; font-size: 24rpx; } /style5pages/order/list.vue— 飞手端可接订单列表vuetemplate view classpage view classtabs text :class{ active: tab all } clicktab all全部/text text :class{ active: tab fixed } clicktab fixed一口价/text text :class{ active: tab bid } clicktab bid报价/text text :class{ active: tab reward } clicktab reward悬赏/text /view scroll-view scroll-y view v-foritem in orders :keyitem.id classorder-card text classtitle{{ item.title }}/text text classmeta报酬: ¥{{ item.price }} | 距离: {{ item.distance }}m/text text classskill技能: {{ item.skillTags }}/text button classaccept-btn clickacceptOrder(item.id)立即接单/button /view /scroll-view /view /template script setup import { ref, onMounted } from vue import { api } from /common/api const tab ref(all) const orders ref([]) async function loadOrders() { const res await api.getOrders({ type: tab.value, status: NEW }) orders.value res.data || [] } onMounted(loadOrders) async function acceptOrder(id) { await api.acceptOrder(id) uni.showToast({ title: 接单成功 }) loadOrders() } /script style scoped .page { padding: 20rpx; } .tabs { display: flex; gap: 20rpx; margin-bottom: 20rpx; } .tabs text { padding: 12rpx 24rpx; border-radius: 30rpx; background: #f5f5f5; } .tabs .active { background: #1890ff; color: #fff; } .order-card { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 20rpx; } .accept-btn { background: #52c41a; color: #fff; margin-top: 16rpx; } /style6pages/order/detail.vue— 订单详情 接单vuetemplate view classdetail text classtitle{{ order.title }}/text text classprice报酬: ¥{{ order.price }}/text map iddetailMap :latitudeorder.latitude :longitudeorder.longitude :markers[{ latitude: order.latitude, longitude: order.longitude, width: 40, height: 40 }] stylewidth: 100%; height: 400rpx; margin: 20rpx 0; border-radius: 16rpx / view classinfo text技能要求: {{ order.skillTags }}/text text发布时间: {{ order.createTime }}/text text状态: {{ statusText[order.status] }}/text /view button v-iforder.status NEW classaccept-btn clickhandleAccept 立即接单 /button /view /template script setup import { ref, onMounted } from vue import { api } from /common/api const order ref({}) const statusText { NEW: 待接单, ACCEPTED: 进行中, COMPLETED: 已完成 } onMounted(async () { const pages getCurrentPages() const cur pages[pages.length - 1] const id cur.options?.id const res await api.getOrders({ id }) order.value res.data }) async function handleAccept() { await api.acceptOrder(order.value.id) uni.showToast({ title: 接单成功 }) setTimeout(() uni.navigateBack(), 1000) } /script7pages/pilot/dashboard.vue— 飞手工作台vuetemplate view classdashboard view classheader text classname{{ userStore.userInfo?.username }}/text text classrole飞手端/text /view view classstats view classstat-item text classnum{{ stats.totalOrders }}/text text classlabel总订单/text /view view classstat-item text classnum¥{{ stats.income }}/text text classlabel总收入/text /view view classstat-item text classnum{{ stats.rating }}/text text classlabel评分/text /view /view view classsection text classsection-title今日待办/text view v-foro in todayOrders :keyo.id classtodo-item text{{ o.title }}/text text classtime{{ o.startTime }}/text /view /view !-- 实时通信示例 -- view classsection text classsection-title实时状态/text view classstatus-box :classsocketConnected ? online : offline {{ socketConnected ? 已连接 : 未连接 }} /view /view /view /template script setup import { ref, onMounted, onUnmounted } from vue import { useUserStore } from /store/user const userStore useUserStore() const stats ref({ totalOrders: 128, income: 9,680, rating: 4.9 }) const todayOrders ref([ { id: 1, title: 农田植保喷洒, startTime: 09:00 }, { id: 2, title: 园区安防巡检, startTime: 14:00 } ]) const socketConnected ref(false) let ws null onMounted(() { const token uni.getStorageSync(token) ws new WebSocket(wss://your-api-domain.com/ws?token${token}) ws.onopen () { socketConnected.value true } ws.onmessage (e) { const data JSON.parse(e.data) console.log(实时消息:, data) } ws.onclose () { socketConnected.value false } }) onUnmounted(() { ws?.close() }) /script style scoped .dashboard { padding: 30rpx; } .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30rpx; } .stats { display: flex; justify-content: space-around; margin-bottom: 40rpx; } .stat-item { text-align: center; } .num { font-size: 40rpx; font-weight: bold; display: block; } .section-title { font-size: 32rpx; font-weight: bold; margin-bottom: 16rpx; display: block; } .status-box { padding: 20rpx; border-radius: 12rpx; text-align: center; background: #fff1f0; } .status-box.online { background: #f6ffed; } /style8store/order.js— 订单状态管理Piniajs// store/order.js import { defineStore } from pinia import { ref } from vue export const useOrderStore defineStore(order, () { const orders ref([]) function updateStatus(id, status) { const o orders.value.find(x x.id id) if (o) o.status status } return { orders, updateStatus } })9后端接口对照Spring Boot 核心接口前端已对接前端调用后端接口说明api.login()POST /api/auth/loginJWT登录返回tokenroleapi.getOrders()GET /api/orders?latlngradius附近订单Redis GEOapi.createOrder()POST /api/orders发布订单api.acceptOrder(id)POST /api/orders/{id}/accept飞手接单api.getPilotInfo()GET /api/pilot/info飞手个人信息后端核心Java代码JWT 控制器与你给的搜索结果一致这里不重复贴前端已完全按上述接口联调。 关键配置清单配置项文件/位置说明高德地图Keymanifest.json → mp-weixin → appid 地图插件申请JS API密钥WebSocket地址dashboard.vue中wss://your-api-domain.com/ws替换为你的后端地址API Base URLcommon/api.js → BASE替换为你的后端域名地图标记图标/static/marker-default.png放入项目static目录这套前端代码可直接在HBuilderX中新建 UniApp 项目按上述文件结构粘贴即可运行。如需完整后端Java源码Spring Boot MyBatis-Plus JWT Redis GEO WebSocket可以告诉我我再把后端全套贴出来。