From 2c86a8bd60deed0dd0e044bad6fb83f75d19a332 Mon Sep 17 00:00:00 2001
From: wlzboy <66905212@qq.com>
Date: 星期日, 26 十月 2025 15:05:50 +0800
Subject: [PATCH] Merge branch 'feature-task'

---
 app/pages/task/create-welfare.vue |  546 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 546 insertions(+), 0 deletions(-)

diff --git a/app/pages/task/create-welfare.vue b/app/pages/task/create-welfare.vue
new file mode 100644
index 0000000..9a5d4d9
--- /dev/null
+++ b/app/pages/task/create-welfare.vue
@@ -0,0 +1,546 @@
+<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>

--
Gitblit v1.9.1