| New file |
| | |
| | | <template> |
| | | <scroll-view class="create-welfare-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">创建福祉车任务</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> |
| | | <view class="staff-list"> |
| | | <view class="staff-item"> |
| | | <text>{{ currentUser.name }} ({{ currentUser.position }})</text> |
| | | <uni-icons type="checkmarkempty" size="20" color="#007AFF"></uni-icons> |
| | | </view> |
| | | <view |
| | | class="staff-item" |
| | | v-for="(staff, index) in additionalStaff" |
| | | :key="index" |
| | | @click="removeStaff(index)" |
| | | > |
| | | <text>{{ staff.name }} ({{ staff.position }})</text> |
| | | <uni-icons type="closeempty" size="20" color="#ff4d4f"></uni-icons> |
| | | </view> |
| | | <view class="add-staff" @click="addStaff"> |
| | | <uni-icons type="plusempty" size="20" color="#007AFF"></uni-icons> |
| | | <text>添加人员</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="form-item"> |
| | | <view class="form-label">归属机构</view> |
| | | <picker mode="selector" :range="organizations" @change="onOrganizationChange"> |
| | | <view class="form-input picker-input"> |
| | | {{ selectedOrganization || '请选择归属机构' }} |
| | | <uni-icons type="arrowright" size="16" color="#999"></uni-icons> |
| | | </view> |
| | | </picker> |
| | | </view> |
| | | |
| | | <view class="form-item"> |
| | | <view class="form-label">服务时间</view> |
| | | <uni-datetime-picker |
| | | v-model="taskForm.serviceTime" |
| | | type="datetime" |
| | | :placeholder="'请选择服务时间'" |
| | | class="form-input" |
| | | /> |
| | | </view> |
| | | |
| | | <view class="form-section-title">乘客信息</view> |
| | | <view class="form-item"> |
| | | <view class="form-label">联系人</view> |
| | | <input |
| | | class="form-input" |
| | | placeholder="请输入联系人" |
| | | v-model="taskForm.passenger.contact" |
| | | /> |
| | | </view> |
| | | |
| | | <view class="form-item"> |
| | | <view class="form-label">联系电话</view> |
| | | <input |
| | | class="form-input" |
| | | type="number" |
| | | placeholder="请输入联系电话" |
| | | v-model="taskForm.passenger.phone" |
| | | /> |
| | | </view> |
| | | |
| | | <view class="form-item"> |
| | | <view class="form-label">出发地址</view> |
| | | <view class="form-input picker-input" @click="selectStartAddress"> |
| | | {{ taskForm.startAddress || '请选择出发地址' }} |
| | | <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="selectEndAddress"> |
| | | {{ taskForm.endAddress || '请选择目的地址' }} |
| | | <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> |
| | | <input |
| | | class="form-input" |
| | | type="digit" |
| | | placeholder="请输入成交价" |
| | | v-model="taskForm.price" |
| | | /> |
| | | </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 { addTask } from "@/api/task" |
| | | import { listAvailableVehicles } from "@/api/vehicle" |
| | | import { calculateDistance } from "@/api/map" |
| | | import MapSelector from '@/components/map-selector.vue' |
| | | |
| | | export default { |
| | | components: { |
| | | uniDatetimePicker, |
| | | uniPopup, |
| | | MapSelector |
| | | }, |
| | | data() { |
| | | return { |
| | | selectedVehicle: '', |
| | | selectedVehicleId: null, |
| | | selectedOrganization: '', |
| | | mapSelectorType: '', |
| | | taskForm: { |
| | | serviceTime: '', |
| | | passenger: { |
| | | contact: '', |
| | | phone: '' |
| | | }, |
| | | startAddress: '', |
| | | endAddress: '', |
| | | distance: '', |
| | | price: '' |
| | | }, |
| | | vehicles: [], |
| | | vehicleOptions: [], |
| | | organizations: ['广州分公司', '深圳分公司', '珠海分公司', '佛山分公司'], |
| | | additionalStaff: [], |
| | | loading: false, |
| | | addressCoordinates: { |
| | | startAddress: null, |
| | | endAddress: null |
| | | } |
| | | } |
| | | }, |
| | | computed: { |
| | | ...mapState({ |
| | | currentUser: state => ({ |
| | | name: state.user.nickName || '张三', |
| | | position: '司机', |
| | | deptId: state.user.deptId || 100 |
| | | }) |
| | | }) |
| | | }, |
| | | onLoad(options) { |
| | | this.getAvailableVehicles() |
| | | }, |
| | | methods: { |
| | | getAvailableVehicles() { |
| | | const deptId = this.currentUser.deptId |
| | | return listAvailableVehicles(deptId, 'WELFARE').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 |
| | | }, |
| | | |
| | | onOrganizationChange(e) { |
| | | this.selectedOrganization = this.organizations[e.detail.value] |
| | | }, |
| | | |
| | | selectStartAddress() { |
| | | this.mapSelectorType = 'startAddress' |
| | | this.$refs.mapPopup.open() |
| | | }, |
| | | |
| | | selectEndAddress() { |
| | | this.mapSelectorType = 'endAddress' |
| | | this.$refs.mapPopup.open() |
| | | }, |
| | | |
| | | getInitialAddress() { |
| | | return this.mapSelectorType === 'startAddress' |
| | | ? this.taskForm.startAddress |
| | | : this.taskForm.endAddress |
| | | }, |
| | | |
| | | onAddressSelected(address) { |
| | | if (this.mapSelectorType === 'startAddress') { |
| | | this.taskForm.startAddress = address.title + ' - ' + address.address |
| | | this.addressCoordinates.startAddress = { |
| | | lat: address.lat, |
| | | lng: address.lng |
| | | } |
| | | } else if (this.mapSelectorType === 'endAddress') { |
| | | this.taskForm.endAddress = address.title + ' - ' + address.address |
| | | this.addressCoordinates.endAddress = { |
| | | lat: address.lat, |
| | | lng: address.lng |
| | | } |
| | | } |
| | | |
| | | this.calculateWelfareDistance() |
| | | this.closeMapSelector() |
| | | }, |
| | | |
| | | calculateWelfareDistance() { |
| | | if (this.addressCoordinates.startAddress && this.addressCoordinates.endAddress) { |
| | | this.getDistanceBetweenPoints( |
| | | this.addressCoordinates.startAddress.lat, |
| | | this.addressCoordinates.startAddress.lng, |
| | | this.addressCoordinates.endAddress.lat, |
| | | this.addressCoordinates.endAddress.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 = '' |
| | | }, |
| | | |
| | | addStaff() { |
| | | this.$modal.showToast('添加人员功能开发中') |
| | | }, |
| | | |
| | | removeStaff(index) { |
| | | this.additionalStaff.splice(index, 1) |
| | | }, |
| | | |
| | | validateForm() { |
| | | if (!this.selectedVehicleId) { |
| | | this.$modal.showToast('请选择任务车辆') |
| | | return false |
| | | } |
| | | |
| | | if (!this.taskForm.passenger.contact) { |
| | | this.$modal.showToast('请输入联系人') |
| | | return false |
| | | } |
| | | |
| | | if (!this.taskForm.passenger.phone) { |
| | | this.$modal.showToast('请输入联系电话') |
| | | return false |
| | | } |
| | | |
| | | if (!this.taskForm.startAddress) { |
| | | this.$modal.showToast('请选择出发地址') |
| | | return false |
| | | } |
| | | |
| | | if (!this.taskForm.endAddress) { |
| | | this.$modal.showToast('请选择目的地址') |
| | | return false |
| | | } |
| | | |
| | | return true |
| | | }, |
| | | |
| | | buildSubmitData() { |
| | | const submitData = { |
| | | taskType: 'WELFARE', |
| | | vehicleIds: this.selectedVehicleId ? [this.selectedVehicleId] : [], |
| | | serviceTime: this.taskForm.serviceTime, |
| | | passenger: this.taskForm.passenger, |
| | | startAddress: this.taskForm.startAddress, |
| | | endAddress: this.taskForm.endAddress, |
| | | distance: this.taskForm.distance ? parseFloat(this.taskForm.distance) : null, |
| | | price: this.taskForm.price ? parseFloat(this.taskForm.price) : null |
| | | } |
| | | |
| | | if (this.addressCoordinates.startAddress) { |
| | | submitData.departureLongitude = this.addressCoordinates.startAddress.lng |
| | | submitData.departureLatitude = this.addressCoordinates.startAddress.lat |
| | | } |
| | | |
| | | if (this.addressCoordinates.endAddress) { |
| | | submitData.destinationLongitude = this.addressCoordinates.endAddress.lng |
| | | submitData.destinationLatitude = this.addressCoordinates.endAddress.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(() => { |
| | | // 关闭当前页面,跳转到任务列表 |
| | | uni.redirectTo({ |
| | | url: '/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-welfare-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-section-title { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | margin: 40rpx 0 20rpx 0; |
| | | padding-bottom: 10rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | } |
| | | |
| | | .form-textarea { |
| | | width: 100%; |
| | | min-height: 150rpx; |
| | | padding: 20rpx; |
| | | border: 1rpx solid #eee; |
| | | border-radius: 10rpx; |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .staff-list { |
| | | .staff-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 20rpx; |
| | | background-color: #f9f9f9; |
| | | border-radius: 10rpx; |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .add-staff { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 20rpx; |
| | | border: 1rpx dashed #007AFF; |
| | | border-radius: 10rpx; |
| | | color: #007AFF; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .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> |