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