| | |
| | | |
| | | <view class="form-item"> |
| | | <view class="form-label">任务出发地</view> |
| | | <view class="form-input picker-input" @click="selectStartLocation"> |
| | | {{ taskForm.startLocation || '请选择任务出发地' }} |
| | | <uni-icons type="arrowright" size="16" color="#999"></uni-icons> |
| | | <view class="address-input-container"> |
| | | <input |
| | | class="form-input" |
| | | placeholder="请输入任务出发地" |
| | | v-model="taskForm.startLocation" |
| | | @input="onStartLocationInput" |
| | | @focus="onStartLocationFocus" |
| | | /> |
| | | <view class="address-suggestions" v-if="showStartSuggestions && startSuggestions.length > 0"> |
| | | <view |
| | | class="address-suggestion-item" |
| | | v-for="(item, index) in startSuggestions" |
| | | :key="index" |
| | | @click="selectStartSuggestion(item)" |
| | | > |
| | | <view class="suggestion-name">{{ item.name }}</view> |
| | | <view class="suggestion-address">{{ item.address }}</view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="form-item"> |
| | | <view class="form-label">任务目的地</view> |
| | | <view class="form-input picker-input" @click="selectEndLocation"> |
| | | {{ taskForm.endLocation || '请选择任务目的地' }} |
| | | <uni-icons type="arrowright" size="16" color="#999"></uni-icons> |
| | | <view class="address-input-container"> |
| | | <input |
| | | class="form-input" |
| | | placeholder="请输入任务目的地" |
| | | v-model="taskForm.endLocation" |
| | | @input="onEndLocationInput" |
| | | @focus="onEndLocationFocus" |
| | | /> |
| | | <view class="address-suggestions" v-if="showEndSuggestions && endSuggestions.length > 0"> |
| | | <view |
| | | class="address-suggestion-item" |
| | | v-for="(item, index) in endSuggestions" |
| | | :key="index" |
| | | @click="selectEndSuggestion(item)" |
| | | > |
| | | <view class="suggestion-name">{{ item.name }}</view> |
| | | <view class="suggestion-address">{{ item.address }}</view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </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 { addTask } from "@/api/task" |
| | | import { listAvailableVehicles } from "@/api/vehicle" |
| | | import { calculateDistance } from "@/api/map" |
| | | import { baiduPlaceSuggestion, baiduGeocoding, baiduDistanceByAddress } from "@/api/map" |
| | | import { getDicts } from "@/api/dict" |
| | | import MapSelector from '@/components/map-selector.vue' |
| | | |
| | | export default { |
| | | components: { |
| | | uniDatetimePicker, |
| | | uniPopup, |
| | | MapSelector |
| | | uniDatetimePicker |
| | | }, |
| | | data() { |
| | | return { |
| | |
| | | boundVehicleId: null, |
| | | taskTypeOptions: [], |
| | | taskTypeLabels: [], |
| | | mapSelectorType: '', |
| | | // 地址搜索相关 |
| | | startSuggestions: [], |
| | | endSuggestions: [], |
| | | showStartSuggestions: false, |
| | | showEndSuggestions: false, |
| | | startSearchTimer: null, |
| | | endSearchTimer: null, |
| | | taskForm: { |
| | | taskDescription: '', |
| | | taskType: '', |
| | |
| | | vehicleOptions: [], |
| | | loading: false, |
| | | addressCoordinates: { |
| | | startLocation: null, |
| | | endLocation: null |
| | | } |
| | | startLocation: { lon: null, lat: null }, |
| | | endLocation: { lon: null, lat: null } |
| | | }, |
| | | // 搜索区域(可根据用户所在城市调整) |
| | | searchRegion: '广州市' |
| | | } |
| | | }, |
| | | computed: { |
| | | ...mapState({ |
| | | currentUser: state => ({ |
| | | id: state.user.userId, |
| | | name: state.user.nickName || '张三', |
| | | position: '司机', |
| | | deptId: state.user.deptId || 100 |
| | |
| | | this.taskForm.vehicleId = this.selectedVehicleId |
| | | }, |
| | | |
| | | selectStartLocation() { |
| | | this.mapSelectorType = 'startLocation' |
| | | this.$refs.mapPopup.open() |
| | | }, |
| | | |
| | | selectEndLocation() { |
| | | this.mapSelectorType = 'endLocation' |
| | | this.$refs.mapPopup.open() |
| | | }, |
| | | |
| | | getInitialAddress() { |
| | | return this.mapSelectorType === 'startLocation' ? this.taskForm.startLocation : this.taskForm.endLocation |
| | | }, |
| | | |
| | | onAddressSelected(address) { |
| | | if (this.mapSelectorType === 'startLocation') { |
| | | this.taskForm.startLocation = address.title + ' - ' + address.address |
| | | this.addressCoordinates.startLocation = { |
| | | lat: address.lat, |
| | | lng: address.lng |
| | | } |
| | | } else if (this.mapSelectorType === 'endLocation') { |
| | | this.taskForm.endLocation = address.title + ' - ' + address.address |
| | | this.addressCoordinates.endLocation = { |
| | | lat: address.lat, |
| | | lng: address.lng |
| | | } |
| | | // 出发地输入监听 |
| | | onStartLocationInput(e) { |
| | | const keyword = e.detail.value |
| | | this.taskForm.startLocation = keyword |
| | | |
| | | if (this.startSearchTimer) { |
| | | clearTimeout(this.startSearchTimer) |
| | | } |
| | | |
| | | this.calculateDistance() |
| | | this.closeMapSelector() |
| | | }, |
| | | |
| | | calculateDistance() { |
| | | if (this.addressCoordinates.startLocation && this.addressCoordinates.endLocation) { |
| | | this.getDistanceBetweenPoints( |
| | | this.addressCoordinates.startLocation.lat, |
| | | this.addressCoordinates.startLocation.lng, |
| | | this.addressCoordinates.endLocation.lat, |
| | | this.addressCoordinates.endLocation.lng |
| | | ).then(distance => { |
| | | this.taskForm.distance = distance.toFixed(2) |
| | | }).catch(error => { |
| | | console.error('距离计算失败:', error) |
| | | }) |
| | | if (!keyword || keyword.trim() === '') { |
| | | this.startSuggestions = [] |
| | | this.showStartSuggestions = false |
| | | return |
| | | } |
| | | |
| | | // 防抖处理 |
| | | this.startSearchTimer = setTimeout(() => { |
| | | this.searchStartAddress(keyword) |
| | | }, 300) |
| | | }, |
| | | |
| | | getDistanceBetweenPoints(lat1, lng1, lat2, lng2) { |
| | | return new Promise((resolve, reject) => { |
| | | calculateDistance(lat1, lng1, lat2, lng2).then(response => { |
| | | if (response.code === 200) { |
| | | const responseData = typeof response.data === 'string' ? JSON.parse(response.data) : response.data |
| | | if (responseData && responseData.status === 0 && responseData.result && responseData.result.elements && responseData.result.elements.length > 0) { |
| | | const distanceInKm = responseData.result.elements[0].distance / 1000 |
| | | resolve(distanceInKm) |
| | | } else { |
| | | reject(new Error('距离计算接口返回数据格式不正确')) |
| | | } |
| | | } else { |
| | | reject(new Error('距离计算接口调用失败')) |
| | | } |
| | | }).catch(error => { |
| | | reject(error) |
| | | }) |
| | | // 搜索出发地地址 |
| | | searchStartAddress(keyword) { |
| | | baiduPlaceSuggestion(keyword, this.searchRegion).then(response => { |
| | | if (response.code === 200 && response.data) { |
| | | this.startSuggestions = response.data |
| | | this.showStartSuggestions = true |
| | | } else { |
| | | this.startSuggestions = [] |
| | | this.showStartSuggestions = false |
| | | } |
| | | }).catch(error => { |
| | | console.error('搜索出发地地址失败:', error) |
| | | this.startSuggestions = [] |
| | | this.showStartSuggestions = false |
| | | }) |
| | | }, |
| | | |
| | | closeMapSelector() { |
| | | this.$refs.mapPopup.close() |
| | | this.mapSelectorType = '' |
| | | // 出发地输入框获得焦点 |
| | | onStartLocationFocus() { |
| | | if (this.taskForm.startLocation && this.startSuggestions.length > 0) { |
| | | this.showStartSuggestions = true |
| | | } |
| | | }, |
| | | |
| | | // 选择出发地地址建议 |
| | | selectStartSuggestion(item) { |
| | | this.taskForm.startLocation = item.name |
| | | this.showStartSuggestions = false |
| | | this.startSuggestions = [] |
| | | |
| | | // 获取地址坐标 |
| | | if (item.location && item.location.lng && item.location.lat) { |
| | | this.addressCoordinates.startLocation = { |
| | | lon: item.location.lng, |
| | | lat: item.location.lat |
| | | } |
| | | } else { |
| | | // 如果没有坐标,需要通过地址获取坐标 |
| | | this.getCoordinatesByAddress(item.name, 'start') |
| | | } |
| | | |
| | | // 如果两个地址都已选择,自动计算距离 |
| | | if (this.taskForm.endLocation && this.addressCoordinates.endLocation.lon) { |
| | | this.calculateDistance() |
| | | } |
| | | }, |
| | | |
| | | // 目的地输入监听 |
| | | onEndLocationInput(e) { |
| | | const keyword = e.detail.value |
| | | this.taskForm.endLocation = keyword |
| | | |
| | | if (this.endSearchTimer) { |
| | | clearTimeout(this.endSearchTimer) |
| | | } |
| | | |
| | | if (!keyword || keyword.trim() === '') { |
| | | this.endSuggestions = [] |
| | | this.showEndSuggestions = false |
| | | return |
| | | } |
| | | |
| | | // 防抖处理 |
| | | this.endSearchTimer = setTimeout(() => { |
| | | this.searchEndAddress(keyword) |
| | | }, 300) |
| | | }, |
| | | |
| | | // 搜索目的地地址 |
| | | searchEndAddress(keyword) { |
| | | baiduPlaceSuggestion(keyword, this.searchRegion).then(response => { |
| | | if (response.code === 200 && response.data) { |
| | | this.endSuggestions = response.data |
| | | this.showEndSuggestions = true |
| | | } else { |
| | | this.endSuggestions = [] |
| | | this.showEndSuggestions = false |
| | | } |
| | | }).catch(error => { |
| | | console.error('搜索目的地地址失败:', error) |
| | | this.endSuggestions = [] |
| | | this.showEndSuggestions = false |
| | | }) |
| | | }, |
| | | |
| | | // 目的地输入框获得焦点 |
| | | onEndLocationFocus() { |
| | | if (this.taskForm.endLocation && this.endSuggestions.length > 0) { |
| | | this.showEndSuggestions = true |
| | | } |
| | | }, |
| | | |
| | | // 选择目的地地址建议 |
| | | selectEndSuggestion(item) { |
| | | this.taskForm.endLocation = item.name |
| | | this.showEndSuggestions = false |
| | | this.endSuggestions = [] |
| | | |
| | | // 获取地址坐标 |
| | | if (item.location && item.location.lng && item.location.lat) { |
| | | this.addressCoordinates.endLocation = { |
| | | lon: item.location.lng, |
| | | lat: item.location.lat |
| | | } |
| | | } else { |
| | | // 如果没有坐标,需要通过地址获取坐标 |
| | | this.getCoordinatesByAddress(item.name, 'end') |
| | | } |
| | | |
| | | // 如果两个地址都已选择,自动计算距离 |
| | | if (this.taskForm.startLocation && this.addressCoordinates.startLocation.lon) { |
| | | this.calculateDistance() |
| | | } |
| | | }, |
| | | |
| | | // 通过地址获取坐标 |
| | | getCoordinatesByAddress(address, type) { |
| | | baiduGeocoding(address, this.searchRegion).then(response => { |
| | | if (response.code === 200 && response.data && response.data.location) { |
| | | if (type === 'start') { |
| | | this.addressCoordinates.startLocation = { |
| | | lon: response.data.location.lng, |
| | | lat: response.data.location.lat |
| | | } |
| | | } else if (type === 'end') { |
| | | this.addressCoordinates.endLocation = { |
| | | lon: response.data.location.lng, |
| | | lat: response.data.location.lat |
| | | } |
| | | } |
| | | |
| | | // 如果两个地址都已有坐标,自动计算距离 |
| | | if (this.addressCoordinates.startLocation.lon && this.addressCoordinates.endLocation.lon) { |
| | | this.calculateDistance() |
| | | } |
| | | } |
| | | }).catch(error => { |
| | | console.error('获取地址坐标失败:', error) |
| | | }) |
| | | }, |
| | | |
| | | // 计算两地之间的距离 |
| | | calculateDistance() { |
| | | if (!this.taskForm.startLocation || !this.taskForm.endLocation) { |
| | | return |
| | | } |
| | | |
| | | // 使用百度地图计算距离(组合接口) |
| | | baiduDistanceByAddress(this.taskForm.startLocation, this.searchRegion, this.taskForm.endLocation, this.searchRegion).then(response => { |
| | | if (response.code === 200 && response.data) { |
| | | // 百度地图返回的距离单位是米,需要转换为公里 |
| | | const distanceInMeters = response.data.distance |
| | | const distanceInKm = distanceInMeters / 1000 |
| | | this.taskForm.distance = distanceInKm.toFixed(2) |
| | | |
| | | console.log('距离计算成功:', distanceInMeters, '米 =', distanceInKm, '公里') |
| | | |
| | | // 同时更新坐标信息 |
| | | if (response.data.fromLocation) { |
| | | this.addressCoordinates.startLocation = { |
| | | lon: response.data.fromLocation.lng, |
| | | lat: response.data.fromLocation.lat |
| | | } |
| | | } |
| | | if (response.data.toLocation) { |
| | | this.addressCoordinates.endLocation = { |
| | | lon: response.data.toLocation.lng, |
| | | lat: response.data.toLocation.lat |
| | | } |
| | | } |
| | | } |
| | | }).catch(error => { |
| | | console.error('计算距离失败:', error) |
| | | this.$modal.showToast('计算距离失败,请手动输入') |
| | | }) |
| | | }, |
| | | |
| | | validateForm() { |
| | |
| | | }, |
| | | |
| | | buildSubmitData() { |
| | | // 调试:打印当前用户信息 |
| | | console.log('当前用户信息:', this.currentUser) |
| | | console.log('用户ID:', this.currentUser.id) |
| | | console.log('Vuex State:', this.$store.state.user) |
| | | |
| | | const submitData = { |
| | | taskDescription: this.taskForm.taskDescription, |
| | | taskType: this.taskForm.taskType, |
| | | vehicleIds: this.taskForm.vehicleId ? [this.taskForm.vehicleId] : [], |
| | | assigneeId: this.currentUser.id || this.$store.state.user.userId, // 主要执行人 |
| | | assigneeIds: (this.currentUser.id || this.$store.state.user.userId) ? [this.currentUser.id || this.$store.state.user.userId] : [], // 执行人员ID列表 |
| | | plannedStartTime: this.taskForm.plannedStartTime, |
| | | plannedEndTime: this.taskForm.plannedEndTime, |
| | | departureAddress: this.taskForm.startLocation, |
| | |
| | | remark: this.taskForm.remark |
| | | } |
| | | |
| | | if (this.addressCoordinates.startLocation) { |
| | | submitData.departureLongitude = this.addressCoordinates.startLocation.lng |
| | | // 调试:打印提交数据 |
| | | console.log('提交数据:', submitData) |
| | | |
| | | if (this.addressCoordinates.startLocation && this.addressCoordinates.startLocation.lon) { |
| | | submitData.departureLongitude = this.addressCoordinates.startLocation.lon |
| | | submitData.departureLatitude = this.addressCoordinates.startLocation.lat |
| | | } |
| | | |
| | | if (this.addressCoordinates.endLocation) { |
| | | submitData.destinationLongitude = this.addressCoordinates.endLocation.lng |
| | | if (this.addressCoordinates.endLocation && this.addressCoordinates.endLocation.lon) { |
| | | submitData.destinationLongitude = this.addressCoordinates.endLocation.lon |
| | | submitData.destinationLatitude = this.addressCoordinates.endLocation.lat |
| | | } |
| | | |
| | |
| | | addTask(submitData).then(response => { |
| | | this.loading = false |
| | | this.$modal.showToast('任务创建成功') |
| | | |
| | | // 延迟跳转,让用户看到成功提示 |
| | | setTimeout(() => { |
| | | this.$tab.navigateTo('/pages/task/index') |
| | | }, 1500) |
| | | // 跳转到任务列表并触发刷新 |
| | | uni.switchTab({ |
| | | url: '/pages/task/index', |
| | | success: () => { |
| | | // 使用事件总线通知任务列表页面刷新 |
| | | uni.$emit('refreshTaskList') |
| | | } |
| | | }) |
| | | }, 1000) |
| | | }).catch(error => { |
| | | this.loading = false |
| | | console.error('任务创建失败:', error) |
| | |
| | | border-radius: 10rpx; |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | // 地址输入容器 |
| | | .address-input-container { |
| | | position: relative; |
| | | |
| | | .form-input { |
| | | width: 100%; |
| | | height: 70rpx; |
| | | padding: 0 20rpx; |
| | | border: 1rpx solid #eee; |
| | | border-radius: 10rpx; |
| | | font-size: 28rpx; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .address-suggestions { |
| | | position: absolute; |
| | | top: 75rpx; |
| | | left: 0; |
| | | right: 0; |
| | | max-height: 400rpx; |
| | | overflow-y: auto; |
| | | background-color: white; |
| | | border: 1rpx solid #eee; |
| | | border-radius: 10rpx; |
| | | box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); |
| | | z-index: 100; |
| | | |
| | | .address-suggestion-item { |
| | | padding: 20rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | |
| | | &:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | &:active { |
| | | background-color: #f5f5f5; |
| | | } |
| | | |
| | | .suggestion-name { |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | margin-bottom: 8rpx; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .suggestion-address { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .form-actions { |
| | |
| | | } |
| | | } |
| | | |
| | | .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> |