From 97db9d11ff425583d2dece82a842a7766bb5e7e4 Mon Sep 17 00:00:00 2001 From: wlzboy <66905212@qq.com> Date: 星期五, 26 九月 2025 21:43:39 +0800 Subject: [PATCH] feat: 添加map --- app/pages/task/create.vue | 161 +++++++++++++++ ruoyi-ui/src/views/task/vehicle/index.vue | 6 app/pages/login.vue | 2 app/static/logo.png | 0 app/components/map-selector.vue | 409 ++++++++++++++++++++++++++++++++++++++++ app/manifest.json | 18 + 6 files changed, 583 insertions(+), 13 deletions(-) diff --git a/app/components/map-selector.vue b/app/components/map-selector.vue new file mode 100644 index 0000000..8d872b9 --- /dev/null +++ b/app/components/map-selector.vue @@ -0,0 +1,409 @@ +<template> + <view class="map-selector-container"> + <view class="search-bar"> + <input + class="search-input" + placeholder="璇疯緭鍏ュ湴鍧�" + v-model="searchKeyword" + @input="onSearchInput" + @focus="onSearchFocus" + /> + <button class="search-btn" @click="searchAddress">鎼滅储</button> + </view> + + <!-- 寰俊灏忕▼搴忎娇鐢ㄥ師鐢焟ap缁勪欢 --> + <view class="map-container" id="mapContainer"> + <!-- #ifdef MP-WEIXIN --> + <map + id="map" + :longitude="longitude" + :latitude="latitude" + :markers="markers" + :polyline="polyline" + :circles="circles" + :controls="controls" + :include-points="includePoints" + :show-location="true" + @markertap="onMarkerTap" + @callouttap="onCalloutTap" + @controltap="onControlTap" + @regionchange="onRegionChange" + @tap="onMapTap" + class="map-webview" + ></map> + <!-- #endif --> + + <!-- H5骞冲彴浣跨敤web-view鍔犺浇鐧惧害鍦板浘 --> + <!-- #ifdef H5 --> + <web-view + v-if="baiduMapUrl" + :src="baiduMapUrl" + class="map-webview" + @message="onWebviewMessage" + ></web-view> + <!-- #endif --> + + <view class="map-placeholder" v-if="!isMapLoaded"> + <text>鍦板浘鍔犺浇涓�...</text> + </view> + </view> + + <view class="address-list" v-if="searchResults.length > 0"> + <view + class="address-item" + v-for="(item, index) in searchResults" + :key="index" + @click="selectAddress(item)" + > + <view class="address-title">{{ item.title }}</view> + <view class="address-detail">{{ item.address }}</view> + </view> + </view> + + <view class="selected-address" v-if="selectedAddress"> + <view class="address-title">閫変腑鍦板潃锛�</view> + <view class="address-detail">{{ selectedAddress.title }}</view> + <view class="address-detail">{{ selectedAddress.address }}</view> + <button class="confirm-btn" @click="confirmAddress">纭閫夋嫨</button> + </view> + </view> +</template> + +<script> + export default { + name: 'MapSelector', + props: { + // 鍒濆鍦板潃 + initialAddress: { + type: String, + default: '' + } + }, + data() { + return { + searchKeyword: '', + searchResults: [], + selectedAddress: null, + isMapLoaded: false, + // H5骞冲彴鐩稿叧 + baiduMapUrl: '', + ak: '鎮ㄧ殑鐧惧害鍦板浘AK', + // 寰俊灏忕▼搴忕浉鍏� + longitude: 113.324520, + latitude: 23.099994, + markers: [], + polyline: [], + circles: [], + controls: [], + includePoints: [] + } + }, + mounted() { + // #ifdef H5 + // 鍒濆鍖栫櫨搴﹀湴鍥� + this.initBaiduMap() + // #endif + + // #ifdef MP-WEIXIN + // 鍒濆鍖栧井淇″皬绋嬪簭鍦板浘 + this.initWechatMap() + // #endif + + if (this.initialAddress) { + this.searchKeyword = this.initialAddress + // 寤惰繜璁剧疆鐒︾偣锛岄伩鍏嶈法鍩熼棶棰� + setTimeout(() => { + // 涓嶈嚜鍔ㄨ仛鐒︼紝璁╃敤鎴锋墜鍔ㄧ偣鍑� + }, 500) + } + }, + methods: { + // 鎼滅储妗嗚幏寰楃劍鐐逛簨浠� + onSearchFocus() { + // 鐢ㄦ埛涓诲姩鑱氱劍锛屼笉浼氳Е鍙戣法鍩熼棶棰� + console.log('鎼滅储妗嗚幏寰楃劍鐐�') + }, + + // #ifdef H5 + // 澶勭悊web-view娑堟伅 + onWebviewMessage(e) { + // 澶勭悊鏉ヨ嚜web-view鐨勬秷鎭� + console.log('鏀跺埌鏉ヨ嚜web-view鐨勬秷鎭�:', e) + }, + + // 鍒濆鍖栫櫨搴﹀湴鍥撅紙H5骞冲彴锛� + initBaiduMap() { + // 鏋勯�犵櫨搴﹀湴鍥綰RL + this.baiduMapUrl = `https://api.map.baidu.com/mapjs?v=3.0&ak=${this.ak}&callback=initMap` + this.isMapLoaded = true + + // 寤惰繜鍔犺浇鍦板浘锛岄伩鍏嶉樆濉� + setTimeout(() => { + this.isMapLoaded = true + }, 1000) + }, + // #endif + + // #ifdef MP-WEIXIN + // 鍒濆鍖栧井淇″皬绋嬪簭鍦板浘 + initWechatMap() { + // 鑾峰彇鐢ㄦ埛浣嶇疆 + uni.getLocation({ + type: 'gcj02', + success: (res) => { + this.longitude = res.longitude + this.latitude = res.latitude + + // 璁剧疆榛樿鏍囪 + this.markers = [{ + id: 0, + longitude: this.longitude, + latitude: this.latitude, + title: '褰撳墠浣嶇疆', + iconPath: '/static/icons/location.png', + width: 30, + height: 30 + }] + + // 寤惰繜璁剧疆鍔犺浇鐘舵�侊紝纭繚鍦板浘瀹屽叏鍒濆鍖� + setTimeout(() => { + this.isMapLoaded = true + }, 500) + }, + fail: () => { + // 寤惰繜璁剧疆鍔犺浇鐘舵�� + setTimeout(() => { + this.isMapLoaded = true + }, 500) + this.$modal.showToast('鑾峰彇浣嶇疆澶辫触') + } + }) + }, + + // 鍦板浘鐐瑰嚮浜嬩欢 + onMapTap(e) { + // 鍦ㄧ偣鍑讳綅缃坊鍔犳爣璁� + const { longitude, latitude } = e.detail + this.markers = [{ + id: Date.now(), + longitude, + latitude, + title: '閫変腑浣嶇疆', + iconPath: '/static/icons/location-selected.png', + width: 30, + height: 30 + }] + + // 閫嗗湴鍧�瑙f瀽鑾峰彇鍦板潃淇℃伅 + this.reverseGeocode(latitude, longitude) + }, + + // 閫嗗湴鍧�瑙f瀽 + reverseGeocode(lat, lng) { + // 杩欓噷搴旇璋冪敤鍚庡彴API杩涜閫嗗湴鍧�瑙f瀽 + // 妯℃嫙鏁版嵁 + this.selectedAddress = { + title: '閫変腑浣嶇疆', + address: `缁忕含搴�: ${lat.toFixed(6)}, ${lng.toFixed(6)}`, + lat: lat, + lng: lng + } + }, + // #endif + + // 鎼滅储鍦板潃 + searchAddress() { + if (!this.searchKeyword) { + this.$modal.showToast('璇疯緭鍏ュ湴鍧�') + return + } + + // 鍦ㄥ疄闄呴」鐩腑锛岃繖閲屽簲璇ヨ皟鐢ㄥ搴斿钩鍙扮殑鍦板浘API + // 渚嬪H5骞冲彴璋冪敤鐧惧害鍦板浘API锛屽井淇″皬绋嬪簭璋冪敤寰俊鍦板浘API + + // 妯℃嫙鎼滅储缁撴灉 + this.searchResults = [ + { + title: this.searchKeyword + '闄勮繎鍦扮偣1', + address: '骞夸笢鐪佸箍宸炲競澶╂渤鍖�' + this.searchKeyword + '123鍙�', + lat: 23.123 + Math.random() * 0.1, + lng: 113.321 + Math.random() * 0.1 + }, + { + title: this.searchKeyword + '闄勮繎鍦扮偣2', + address: '骞夸笢鐪佸箍宸炲競瓒婄鍖�' + this.searchKeyword + '456鍙�', + lat: 23.145 + Math.random() * 0.1, + lng: 113.289 + Math.random() * 0.1 + }, + { + title: this.searchKeyword + '闄勮繎鍦扮偣3', + address: '骞夸笢鐪佸箍宸炲競鐧戒簯鍖�' + this.searchKeyword + '789鍙�', + lat: 23.167 + Math.random() * 0.1, + lng: 113.345 + Math.random() * 0.1 + } + ] + }, + + // 杈撳叆妗嗚緭鍏ヤ簨浠� + onSearchInput() { + // 闃叉姈鎼滅储 + }, + + // 閫夋嫨鍦板潃 + selectAddress(item) { + this.selectedAddress = item + this.markLocation(item.lat, item.lng) + }, + + // 鍦ㄥ湴鍥句笂鏍囪浣嶇疆 + markLocation(lat, lng) { + // #ifdef H5 + // H5骞冲彴鏍囪浣嶇疆閫昏緫 + console.log(`H5骞冲彴鏍囪浣嶇疆: ${lat}, ${lng}`) + this.$modal.showToast(`宸叉爣璁颁綅缃甡) + // #endif + + // #ifdef MP-WEIXIN + // 寰俊灏忕▼搴忔爣璁颁綅缃�昏緫 + this.longitude = lng + this.latitude = lat + this.markers = [{ + id: Date.now(), + longitude: lng, + latitude: lat, + title: item.title, + iconPath: '/static/icons/location-selected.png', + width: 30, + height: 30 + }] + // #endif + }, + + // 纭閫夋嫨鍦板潃 + confirmAddress() { + if (!this.selectedAddress) { + this.$modal.showToast('璇峰厛閫夋嫨鍦板潃') + return + } + + // 瑙﹀彂浜嬩欢锛屽皢閫変腑鐨勫湴鍧�浼犻�掔粰鐖剁粍浠� + this.$emit('addressSelected', { + title: this.selectedAddress.title, + address: this.selectedAddress.address, + lat: this.selectedAddress.lat, + lng: this.selectedAddress.lng + }) + } + } + } +</script> + +<style lang="scss"> + .map-selector-container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + + .search-bar { + display: flex; + padding: 20rpx; + background-color: white; + + .search-input { + flex: 1; + height: 70rpx; + padding: 0 20rpx; + border: 1rpx solid #eee; + border-radius: 10rpx; + font-size: 28rpx; + } + + .search-btn { + width: 120rpx; + height: 70rpx; + margin-left: 20rpx; + background-color: #007AFF; + color: white; + border-radius: 10rpx; + font-size: 28rpx; + } + } + + .map-container { + flex: 1; + height: 400rpx; + + .map-webview { + width: 100%; + height: 100%; + } + + // 寰俊灏忕▼搴忓湴鍥炬牱寮� + map { + width: 100%; + height: 100%; + } + + .map-placeholder { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + background-color: #f5f5f5; + font-size: 28rpx; + color: #999; + } + } + + .address-list { + max-height: 300rpx; + overflow-y: auto; + background-color: white; + + .address-item { + padding: 20rpx 30rpx; + border-bottom: 1rpx solid #f0f0f0; + + .address-title { + font-size: 30rpx; + font-weight: bold; + margin-bottom: 10rpx; + } + + .address-detail { + font-size: 26rpx; + color: #666; + } + } + } + + .selected-address { + padding: 20rpx 30rpx; + background-color: white; + border-top: 1rpx solid #f0f0f0; + + .address-title { + font-size: 30rpx; + font-weight: bold; + margin-bottom: 10rpx; + } + + .address-detail { + font-size: 26rpx; + color: #666; + margin-bottom: 10rpx; + } + + .confirm-btn { + width: 100%; + height: 80rpx; + background-color: #007AFF; + color: white; + border-radius: 10rpx; + font-size: 32rpx; + margin-top: 20rpx; + } + } + } +</style> \ No newline at end of file diff --git a/app/manifest.json b/app/manifest.json index 8a86db5..3810283 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -51,7 +51,14 @@ "optimization" : { "subPackages" : true }, - "usingComponents" : true + "usingComponents" : true, + "sdkConfigs" : { + "maps" : { + "baidu" : { + "appkey" : "n5z5pKfAnaP3fYMR4RJOAQsR1wQ2avAn" + } + } + } }, "vueVersion" : "2", "h5" : { @@ -64,6 +71,13 @@ "router" : { "mode" : "hash", "base" : "./" + }, + "sdkConfigs" : { + "maps" : { + "baidu" : { + "appkey" : "鎮ㄧ殑鐧惧害鍦板浘AK" + } + } } } -} +} \ No newline at end of file diff --git a/app/pages/login.vue b/app/pages/login.vue index f440b1e..bcfc7d1 100644 --- a/app/pages/login.vue +++ b/app/pages/login.vue @@ -3,7 +3,7 @@ <view class="logo-content align-center justify-center flex"> <image style="width: 100rpx;height: 100rpx;" :src="globalConfig.appInfo.logo" mode="widthFix"> </image> - <text class="title">鑻ヤ緷绉诲姩绔櫥褰�</text> + <text class="title">姘戣埅璋冨害绯荤粺</text> </view> <view class="login-form-content"> <view class="input-item flex align-center"> diff --git a/app/pages/task/create.vue b/app/pages/task/create.vue index 1cf4741..0970acd 100644 --- a/app/pages/task/create.vue +++ b/app/pages/task/create.vue @@ -475,16 +475,37 @@ </view> </view> </view> + + <!-- 鍦板浘閫夋嫨鍣ㄥ脊绐� --> + <uni-popup ref="mapPopup" type="bottom" :mask-click="false"> + <view class="map-popup-container"> + <view class="popup-header"> + <view class="popup-title">閫夋嫨鍦板潃</view> + <view class="close-btn" @click="closeMapSelector"> + <uni-icons type="closeempty" size="20" color="#999"></uni-icons> + </view> + </view> + <map-selector + :initial-address="getInitialAddress()" + @addressSelected="onAddressSelected" + ></map-selector> + </view> + </uni-popup> </scroll-view> </template> <script> import { mapState } from 'vuex' import uniDatetimePicker from '@/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue' + import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue' + import { getUserProfile } from "@/api/system/user" + import MapSelector from '@/components/map-selector.vue' export default { components: { - uniDatetimePicker + uniDatetimePicker, + uniPopup, + MapSelector }, data() { return { @@ -492,6 +513,10 @@ selectedVehicle: '', selectedOrganization: '', selectedEmergencyTaskType: '', + boundVehicle: '', // 鐢ㄦ埛缁戝畾鐨勮溅杈� + // 鍦板浘閫夋嫨鐩稿叧 + showMapSelector: false, + mapSelectorType: '', // 鏍囪瘑褰撳墠閫夋嫨鐨勬槸鍝釜鍦板潃瀛楁 taskForm: { description: '', startLocation: '', @@ -575,9 +600,34 @@ }) }) }, + onLoad() { + // 鑾峰彇鐢ㄦ埛缁戝畾鐨勮溅杈嗕俊鎭� + this.getUserBoundVehicle() + }, methods: { + // 鑾峰彇鐢ㄦ埛缁戝畾鐨勮溅杈嗕俊鎭� + getUserBoundVehicle() { + getUserProfile().then(response => { + // 杩欓噷妯℃嫙浠庣敤鎴蜂俊鎭腑鑾峰彇缁戝畾杞﹁締锛屽疄闄呴」鐩腑搴旇浠巖esponse.data涓幏鍙� + // 鍋囪鐢ㄦ埛淇℃伅涓湁boundVehicle瀛楁 + this.boundVehicle = '绮12345' // 妯℃嫙鍊硷紝瀹為檯搴斾粠response.data.boundVehicle鑾峰彇 + + // 濡傛灉鏈夌粦瀹氳溅杈嗭紝鍒欓粯璁ら�変腑 + if (this.boundVehicle) { + this.selectedVehicle = this.boundVehicle + } + }).catch(() => { + // 鑾峰彇鐢ㄦ埛淇℃伅澶辫触鏃朵娇鐢ㄩ粯璁ゅ�� + this.boundVehicle = '' + }) + }, + selectTaskCategory(category) { this.selectedTaskCategory = category + // 褰撻�夋嫨浠诲姟绫诲瀷鏃讹紝濡傛灉鏄櫘閫氫换鍔′笖鐢ㄦ埛鏈夌粦瀹氳溅杈嗭紝鍒欓粯璁ら�変腑缁戝畾杞﹁締 + if (category.type === 'normal' && this.boundVehicle) { + this.selectedVehicle = this.boundVehicle + } }, backToCategory() { @@ -596,28 +646,94 @@ this.selectedEmergencyTaskType = this.emergencyTaskTypes[e.detail.value] }, + // 鏄剧ず鍦板浘閫夋嫨鍣� - 浠诲姟鍑哄彂鍦� selectStartLocation() { - this.$modal.showToast('閫夋嫨鍑哄彂鍦板姛鑳藉紑鍙戜腑') + this.mapSelectorType = 'startLocation' + this.$refs.mapPopup.open() }, + // 鏄剧ず鍦板浘閫夋嫨鍣� - 浠诲姟鐩殑鍦� selectEndLocation() { - this.$modal.showToast('閫夋嫨鐩殑鍦板姛鑳藉紑鍙戜腑') + this.mapSelectorType = 'endLocation' + this.$refs.mapPopup.open() }, + // 鏄剧ず鍦板浘閫夋嫨鍣� - 杞嚭鍖婚櫌鍦板潃 selectHospitalOutAddress() { - this.$modal.showToast('閫夋嫨杞嚭鍖婚櫌鍦板潃鍔熻兘寮�鍙戜腑') + this.mapSelectorType = 'hospitalOutAddress' + this.$refs.mapPopup.open() }, + // 鏄剧ず鍦板浘閫夋嫨鍣� - 杞叆鍖婚櫌鍦板潃 selectHospitalInAddress() { - this.$modal.showToast('閫夋嫨杞叆鍖婚櫌鍦板潃鍔熻兘寮�鍙戜腑') + this.mapSelectorType = 'hospitalInAddress' + this.$refs.mapPopup.open() }, + // 鏄剧ず鍦板浘閫夋嫨鍣� - 绂忕杞﹀嚭鍙戝湴鍧� selectStartAddress() { - this.$modal.showToast('閫夋嫨鍑哄彂鍦板潃鍔熻兘寮�鍙戜腑') + this.mapSelectorType = 'startAddress' + this.$refs.mapPopup.open() }, + // 鏄剧ず鍦板浘閫夋嫨鍣� - 绂忕杞︾洰鐨勫湴鍧� selectEndAddress() { - this.$modal.showToast('閫夋嫨鐩殑鍦板潃鍔熻兘寮�鍙戜腑') + this.mapSelectorType = 'endAddress' + this.$refs.mapPopup.open() + }, + + // 鑾峰彇鍒濆鍦板潃鐢ㄤ簬鍦板浘鎼滅储 + getInitialAddress() { + switch (this.mapSelectorType) { + case 'startLocation': + return this.taskForm.startLocation + case 'endLocation': + return this.taskForm.endLocation + case 'hospitalOutAddress': + return this.taskForm.hospitalOut.address + case 'hospitalInAddress': + return this.taskForm.hospitalIn.address + case 'startAddress': + return this.taskForm.startAddress + case 'endAddress': + return this.taskForm.endAddress + default: + return '' + } + }, + + // 鍦板浘閫夋嫨鍣ㄥ湴鍧�閫夋嫨鍥炶皟 + onAddressSelected(address) { + // 鏍规嵁涓嶅悓鐨勫湴鍧�绫诲瀷璁剧疆瀵瑰簲鐨勮〃鍗曞瓧娈� + switch (this.mapSelectorType) { + case 'startLocation': + this.taskForm.startLocation = address.title + ' - ' + address.address + break + case 'endLocation': + this.taskForm.endLocation = address.title + ' - ' + address.address + break + case 'hospitalOutAddress': + this.taskForm.hospitalOut.address = address.title + ' - ' + address.address + break + case 'hospitalInAddress': + this.taskForm.hospitalIn.address = address.title + ' - ' + address.address + break + case 'startAddress': + this.taskForm.startAddress = address.title + ' - ' + address.address + break + case 'endAddress': + this.taskForm.endAddress = address.title + ' - ' + address.address + break + } + + // 鍏抽棴鍦板浘閫夋嫨鍣� + this.closeMapSelector() + }, + + // 鍏抽棴鍦板浘閫夋嫨鍣� + closeMapSelector() { + this.$refs.mapPopup.close() + this.mapSelectorType = '' }, addStaff() { @@ -852,5 +968,36 @@ } } } + + // 鍦板浘閫夋嫨鍣ㄥ脊绐楁牱寮� + .map-popup-container { + height: 80vh; + background-color: white; + border-top-left-radius: 20rpx; + border-top-right-radius: 20rpx; + overflow: hidden; + + .popup-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20rpx 30rpx; + border-bottom: 1rpx solid #f0f0f0; + + .popup-title { + font-size: 32rpx; + font-weight: bold; + color: #333; + } + + .close-btn { + width: 50rpx; + height: 50rpx; + display: flex; + align-items: center; + justify-content: center; + } + } + } } </style> \ No newline at end of file diff --git a/app/static/logo.png b/app/static/logo.png index d4ebbf0..6c0f3b6 100644 --- a/app/static/logo.png +++ b/app/static/logo.png Binary files differ diff --git a/ruoyi-ui/src/views/task/vehicle/index.vue b/ruoyi-ui/src/views/task/vehicle/index.vue index 314fda7..bebb9f3 100644 --- a/ruoyi-ui/src/views/task/vehicle/index.vue +++ b/ruoyi-ui/src/views/task/vehicle/index.vue @@ -33,7 +33,7 @@ <el-select v-model="queryParams.status" placeholder="璇烽�夋嫨鍏宠仈鐘舵��" clearable> <el-option v-for="dict in dict.type.sys_task_vehicle_status" - :key="dict.value" + :key="'search-' + dict.value" :label="dict.label" :value="dict.value" /> @@ -210,7 +210,7 @@ <el-select v-model="form.status" placeholder="璇烽�夋嫨鍏宠仈鐘舵��" style="width: 100%"> <el-option v-for="dict in dict.type.sys_task_vehicle_status" - :key="dict.value" + :key="'form-' + dict.value" :label="dict.label" :value="dict.value" ></el-option> @@ -236,7 +236,7 @@ <el-select v-model="statusForm.newStatus" placeholder="璇烽�夋嫨鏂扮姸鎬�"> <el-option v-for="dict in dict.type.sys_task_vehicle_status" - :key="dict.value" + :key="'status-' + dict.value" :label="dict.label" :value="dict.value" ></el-option> -- Gitblit v1.9.1