wlzboy
2025-10-26 2c86a8bd60deed0dd0e044bad6fb83f75d19a332
app/pages/task/create-normal.vue
New file
@@ -0,0 +1,595 @@
<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>