| New file |
| | |
| | | <template> |
| | | <scroll-view class="create-normal-task-container" scroll-y="true"> |
| | | <view class="form-header"> |
| | | <view class="back-btn" @click="goBack"> |
| | | <uni-icons type="arrowleft" size="20"></uni-icons> |
| | | </view> |
| | | <view class="title">创建{{ categoryName }}任务</view> |
| | | </view> |
| | | |
| | | <view class="form-section"> |
| | | <view class="form-item"> |
| | | <view class="form-label">任务车辆</view> |
| | | <picker mode="selector" :range="vehicles" @change="onVehicleChange"> |
| | | <view class="form-input picker-input"> |
| | | {{ selectedVehicle || '请选择任务车辆' }} |
| | | <uni-icons type="arrowright" size="16" color="#999"></uni-icons> |
| | | </view> |
| | | </picker> |
| | | </view> |
| | | |
| | | <view class="form-item"> |
| | | <view class="form-label">任务类型</view> |
| | | <picker mode="selector" :range="taskTypeLabels" :value="getTaskTypeIndex()" @change="onTaskTypeChange"> |
| | | <view class="form-input picker-input"> |
| | | {{ selectedTaskTypeLabel || '请选择任务类型' }} |
| | | <uni-icons type="arrowright" size="16" color="#999"></uni-icons> |
| | | </view> |
| | | </picker> |
| | | </view> |
| | | |
| | | <view class="form-item"> |
| | | <view class="form-label">任务描述</view> |
| | | <textarea |
| | | class="form-textarea" |
| | | placeholder="请输入任务描述" |
| | | v-model="taskForm.taskDescription" |
| | | /> |
| | | </view> |
| | | |
| | | <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> |
| | | </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> |
| | | </view> |
| | | |
| | | <view class="form-item"> |
| | | <view class="form-label">行驶公里数</view> |
| | | <input |
| | | class="form-input" |
| | | type="digit" |
| | | placeholder="请输入行驶公里数" |
| | | v-model="taskForm.distance" |
| | | /> |
| | | </view> |
| | | |
| | | <view class="form-item"> |
| | | <view class="form-label">计划开始时间</view> |
| | | <uni-datetime-picker |
| | | v-model="taskForm.plannedStartTime" |
| | | type="datetime" |
| | | :placeholder="'请选择计划开始时间'" |
| | | class="form-input" |
| | | /> |
| | | </view> |
| | | |
| | | <view class="form-item"> |
| | | <view class="form-label">计划结束时间</view> |
| | | <uni-datetime-picker |
| | | v-model="taskForm.plannedEndTime" |
| | | type="datetime" |
| | | :placeholder="'请选择计划结束时间'" |
| | | class="form-input" |
| | | /> |
| | | </view> |
| | | |
| | | <view class="form-item"> |
| | | <view class="form-label">执行人</view> |
| | | <input |
| | | class="form-input" |
| | | :value="currentUser.name" |
| | | disabled |
| | | /> |
| | | </view> |
| | | |
| | | <view class="form-item"> |
| | | <view class="form-label">备注</view> |
| | | <textarea |
| | | class="form-textarea" |
| | | placeholder="请输入备注信息(最多200字)" |
| | | v-model="taskForm.remark" |
| | | maxlength="200" |
| | | /> |
| | | </view> |
| | | |
| | | <view class="form-actions"> |
| | | <button class="submit-btn" @click="submitTask" :disabled="loading"> |
| | | {{ loading ? '保存中...' : '保存' }} |
| | | </button> |
| | | </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 { getDicts } from "@/api/dict" |
| | | import MapSelector from '@/components/map-selector.vue' |
| | | |
| | | export default { |
| | | components: { |
| | | uniDatetimePicker, |
| | | uniPopup, |
| | | MapSelector |
| | | }, |
| | | data() { |
| | | return { |
| | | categoryName: '', |
| | | categoryType: '', |
| | | defaultTaskType: '', |
| | | selectedVehicle: '', |
| | | selectedVehicleId: null, |
| | | selectedTaskType: '', |
| | | selectedTaskTypeLabel: '', |
| | | boundVehicle: '', |
| | | boundVehicleId: null, |
| | | taskTypeOptions: [], |
| | | taskTypeLabels: [], |
| | | mapSelectorType: '', |
| | | taskForm: { |
| | | taskDescription: '', |
| | | taskType: '', |
| | | vehicleId: null, |
| | | plannedStartTime: '', |
| | | plannedEndTime: '', |
| | | startLocation: '', |
| | | endLocation: '', |
| | | distance: '', |
| | | remark: '' |
| | | }, |
| | | vehicles: [], |
| | | vehicleOptions: [], |
| | | loading: false, |
| | | addressCoordinates: { |
| | | startLocation: null, |
| | | endLocation: null |
| | | } |
| | | } |
| | | }, |
| | | computed: { |
| | | ...mapState({ |
| | | currentUser: state => ({ |
| | | name: state.user.nickName || '张三', |
| | | position: '司机', |
| | | deptId: state.user.deptId || 100 |
| | | }) |
| | | }) |
| | | }, |
| | | onLoad(options) { |
| | | // 获取任务类型信息 |
| | | if (options.categoryName) { |
| | | this.categoryName = options.categoryName |
| | | } |
| | | if (options.categoryType) { |
| | | this.categoryType = options.categoryType |
| | | } |
| | | if (options.taskType) { |
| | | this.defaultTaskType = options.taskType |
| | | this.selectedTaskType = options.taskType |
| | | } |
| | | |
| | | // 加载数据 |
| | | this.loadTaskTypes().then(() => { |
| | | this.getAvailableVehicles().then(() => { |
| | | this.getUserBoundVehicle() |
| | | }) |
| | | }) |
| | | }, |
| | | methods: { |
| | | // 加载任务类型字典 |
| | | loadTaskTypes() { |
| | | return getDicts('sys_task_type').then(response => { |
| | | const dictData = response.data || [] |
| | | this.taskTypeOptions = dictData.map(item => ({ |
| | | label: item.dictLabel, |
| | | value: item.dictValue |
| | | })) |
| | | this.taskTypeLabels = this.taskTypeOptions.map(item => item.label) |
| | | |
| | | if (this.selectedTaskType) { |
| | | const taskTypeOption = this.taskTypeOptions.find(item => item.value === this.selectedTaskType) |
| | | if (taskTypeOption) { |
| | | this.selectedTaskTypeLabel = taskTypeOption.label |
| | | this.taskForm.taskType = taskTypeOption.value |
| | | } |
| | | } |
| | | }).catch(error => { |
| | | console.error('加载任务类型字典失败:', error) |
| | | this.taskTypeOptions = [ |
| | | { label: '维修保养', value: 'MAINTENANCE' }, |
| | | { label: '加油任务', value: 'FUEL' }, |
| | | { label: '其他', value: 'OTHER' } |
| | | ] |
| | | this.taskTypeLabels = this.taskTypeOptions.map(item => item.label) |
| | | }) |
| | | }, |
| | | |
| | | // 获取任务类型在列表中的索引 |
| | | getTaskTypeIndex() { |
| | | if (!this.selectedTaskType) return -1 |
| | | return this.taskTypeOptions.findIndex(item => item.value === this.selectedTaskType) |
| | | }, |
| | | |
| | | // 任务类型选择变化 |
| | | onTaskTypeChange(e) { |
| | | const index = e.detail.value |
| | | const selectedOption = this.taskTypeOptions[index] |
| | | if (selectedOption) { |
| | | this.selectedTaskType = selectedOption.value |
| | | this.selectedTaskTypeLabel = selectedOption.label |
| | | this.taskForm.taskType = selectedOption.value |
| | | } |
| | | }, |
| | | |
| | | // 获取用户绑定的车辆信息 |
| | | getUserBoundVehicle() { |
| | | getUserProfile().then(response => { |
| | | const userInfo = response.data || response |
| | | if (userInfo.boundVehicle) { |
| | | const boundVehicleNo = userInfo.boundVehicle.vehicleNumber |
| | | const boundVehicleId = userInfo.boundVehicle.vehicleId |
| | | |
| | | const vehicleIndex = this.vehicleOptions.findIndex(v => v.id === boundVehicleId || v.name === boundVehicleNo) |
| | | |
| | | if (vehicleIndex !== -1) { |
| | | this.boundVehicle = this.vehicleOptions[vehicleIndex].name |
| | | this.boundVehicleId = this.vehicleOptions[vehicleIndex].id |
| | | this.selectedVehicle = this.boundVehicle |
| | | this.selectedVehicleId = this.boundVehicleId |
| | | this.taskForm.vehicleId = this.boundVehicleId |
| | | } |
| | | } |
| | | }).catch(error => { |
| | | console.error('获取用户绑定车辆信息失败:', error) |
| | | }) |
| | | }, |
| | | |
| | | // 获取可用车辆列表 |
| | | getAvailableVehicles() { |
| | | const deptId = this.currentUser.deptId |
| | | return listAvailableVehicles(deptId, 'GENERAL').then(response => { |
| | | const vehicleList = response.data || response.rows || [] |
| | | this.vehicleOptions = vehicleList.map(vehicle => ({ |
| | | id: vehicle.vehicleId, |
| | | name: vehicle.vehicleNo, |
| | | type: vehicle.vehicleType, |
| | | status: vehicle.status |
| | | })) |
| | | this.vehicles = this.vehicleOptions.map(v => v.name) |
| | | }).catch(() => { |
| | | this.vehicles = [] |
| | | }) |
| | | }, |
| | | |
| | | onVehicleChange(e) { |
| | | const index = e.detail.value |
| | | this.selectedVehicle = this.vehicles[index] |
| | | this.selectedVehicleId = this.vehicleOptions[index]?.id |
| | | 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 |
| | | } |
| | | } |
| | | |
| | | 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) |
| | | }) |
| | | } |
| | | }, |
| | | |
| | | 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) |
| | | }) |
| | | }) |
| | | }, |
| | | |
| | | closeMapSelector() { |
| | | this.$refs.mapPopup.close() |
| | | this.mapSelectorType = '' |
| | | }, |
| | | |
| | | validateForm() { |
| | | if (!this.taskForm.vehicleId) { |
| | | this.$modal.showToast('请选择任务车辆') |
| | | return false |
| | | } |
| | | |
| | | if (!this.taskForm.taskType) { |
| | | this.$modal.showToast('请选择任务类型') |
| | | return false |
| | | } |
| | | |
| | | if (!this.taskForm.taskDescription) { |
| | | this.$modal.showToast('请输入任务描述') |
| | | return false |
| | | } |
| | | |
| | | if (!this.taskForm.startLocation) { |
| | | this.$modal.showToast('请选择任务出发地') |
| | | return false |
| | | } |
| | | |
| | | if (!this.taskForm.endLocation) { |
| | | this.$modal.showToast('请选择任务目的地') |
| | | return false |
| | | } |
| | | |
| | | if (!this.taskForm.plannedStartTime) { |
| | | this.$modal.showToast('请选择开始时间') |
| | | return false |
| | | } |
| | | |
| | | if (!this.taskForm.plannedEndTime) { |
| | | this.$modal.showToast('请选择结束时间') |
| | | return false |
| | | } |
| | | |
| | | return true |
| | | }, |
| | | |
| | | buildSubmitData() { |
| | | const submitData = { |
| | | taskDescription: this.taskForm.taskDescription, |
| | | taskType: this.taskForm.taskType, |
| | | vehicleIds: this.taskForm.vehicleId ? [this.taskForm.vehicleId] : [], |
| | | plannedStartTime: this.taskForm.plannedStartTime, |
| | | plannedEndTime: this.taskForm.plannedEndTime, |
| | | departureAddress: this.taskForm.startLocation, |
| | | destinationAddress: this.taskForm.endLocation, |
| | | estimatedDistance: this.taskForm.distance ? parseFloat(this.taskForm.distance) : null, |
| | | remark: this.taskForm.remark |
| | | } |
| | | |
| | | if (this.addressCoordinates.startLocation) { |
| | | submitData.departureLongitude = this.addressCoordinates.startLocation.lng |
| | | submitData.departureLatitude = this.addressCoordinates.startLocation.lat |
| | | } |
| | | |
| | | if (this.addressCoordinates.endLocation) { |
| | | submitData.destinationLongitude = this.addressCoordinates.endLocation.lng |
| | | submitData.destinationLatitude = this.addressCoordinates.endLocation.lat |
| | | } |
| | | |
| | | return submitData |
| | | }, |
| | | |
| | | submitTask() { |
| | | if (!this.validateForm()) { |
| | | return |
| | | } |
| | | |
| | | this.$modal.confirm('确定要保存任务吗?').then(() => { |
| | | this.loading = true |
| | | const submitData = this.buildSubmitData() |
| | | |
| | | addTask(submitData).then(response => { |
| | | this.loading = false |
| | | this.$modal.showToast('任务创建成功') |
| | | setTimeout(() => { |
| | | this.$tab.navigateTo('/pages/task/index') |
| | | }, 1500) |
| | | }).catch(error => { |
| | | this.loading = false |
| | | console.error('任务创建失败:', error) |
| | | this.$modal.showToast('任务创建失败,请重试') |
| | | }) |
| | | }).catch(() => {}) |
| | | }, |
| | | |
| | | goBack() { |
| | | uni.navigateBack() |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .create-normal-task-container { |
| | | padding: 20rpx; |
| | | background-color: #f5f5f5; |
| | | min-height: 100vh; |
| | | |
| | | .form-header { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 20rpx 0; |
| | | margin-bottom: 30rpx; |
| | | |
| | | .back-btn { |
| | | width: 60rpx; |
| | | height: 60rpx; |
| | | border-radius: 50%; |
| | | background-color: #f0f0f0; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-right: 20rpx; |
| | | } |
| | | |
| | | .title { |
| | | font-size: 36rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | } |
| | | |
| | | .form-section { |
| | | background-color: white; |
| | | border-radius: 15rpx; |
| | | padding: 30rpx; |
| | | box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05); |
| | | |
| | | .form-item { |
| | | margin-bottom: 40rpx; |
| | | |
| | | .form-label { |
| | | font-size: 28rpx; |
| | | margin-bottom: 15rpx; |
| | | color: #333; |
| | | } |
| | | |
| | | .form-input { |
| | | height: 70rpx; |
| | | padding: 0 20rpx; |
| | | border: 1rpx solid #eee; |
| | | border-radius: 10rpx; |
| | | font-size: 28rpx; |
| | | |
| | | &.picker-input { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | &[disabled] { |
| | | background-color: #f5f5f5; |
| | | color: #999; |
| | | } |
| | | } |
| | | |
| | | .form-textarea { |
| | | width: 100%; |
| | | min-height: 150rpx; |
| | | padding: 20rpx; |
| | | border: 1rpx solid #eee; |
| | | border-radius: 10rpx; |
| | | font-size: 28rpx; |
| | | } |
| | | } |
| | | |
| | | .form-actions { |
| | | margin-top: 50rpx; |
| | | text-align: center; |
| | | |
| | | .submit-btn { |
| | | width: 80%; |
| | | height: 80rpx; |
| | | background-color: #007AFF; |
| | | color: white; |
| | | border-radius: 10rpx; |
| | | font-size: 32rpx; |
| | | |
| | | &[disabled] { |
| | | background-color: #ccc; |
| | | color: #999; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .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> |