| New file |
| | |
| | | <template> |
| | | <view class="staff-selector-component"> |
| | | <view class="form-item"> |
| | | <view class="form-label" :class="{ required: required }">{{ label }}</view> |
| | | <view class="staff-list"> |
| | | <view class="staff-item" v-for="(staff, index) in selectedStaff" :key="staff.userId"> |
| | | <view class="staff-info"> |
| | | <text class="staff-name">{{ staff.nickName }}</text> |
| | | </view> |
| | | <uni-icons |
| | | v-if="canRemove(index)" |
| | | type="closeempty" |
| | | size="20" |
| | | color="#ff4d4f" |
| | | @click="removeStaff(index)" |
| | | ></uni-icons> |
| | | <uni-icons |
| | | v-else |
| | | type="checkmarkempty" |
| | | size="20" |
| | | color="#007AFF" |
| | | ></uni-icons> |
| | | </view> |
| | | <view class="add-staff" @click="showStaffSelector"> |
| | | <uni-icons type="plusempty" size="20" color="#007AFF"></uni-icons> |
| | | <text>添加人员</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 人员选择弹窗 --> |
| | | <uni-popup ref="staffPopup" type="bottom" :safe-area="true"> |
| | | <view class="staff-selector-popup"> |
| | | <view class="popup-header"> |
| | | <view class="popup-title">选择执行人员</view> |
| | | <view class="popup-close" @click="closeStaffSelector"> |
| | | <uni-icons type="closeempty" size="24" color="#333"></uni-icons> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="search-box"> |
| | | <uni-icons type="search" size="18" color="#999"></uni-icons> |
| | | <input |
| | | class="search-input" |
| | | placeholder="搜索姓名、手机号" |
| | | v-model="staffSearchKeyword" |
| | | @input="onStaffSearch" |
| | | /> |
| | | </view> |
| | | |
| | | <view class="staff-filter"> |
| | | <view |
| | | class="filter-item" |
| | | :class="{ active: staffFilterType === 'driver' }" |
| | | @click="filterStaff('driver')" |
| | | >司机</view> |
| | | <view |
| | | class="filter-item" |
| | | :class="{ active: staffFilterType === 'doctor' }" |
| | | @click="filterStaff('doctor')" |
| | | >医生</view> |
| | | <view |
| | | class="filter-item" |
| | | :class="{ active: staffFilterType === 'nurse' }" |
| | | @click="filterStaff('nurse')" |
| | | >护士</view> |
| | | </view> |
| | | |
| | | <scroll-view class="staff-list-popup" scroll-y="true"> |
| | | <view |
| | | class="staff-item-popup" |
| | | v-for="staff in filteredStaffList" |
| | | :key="staff.userId" |
| | | @click="toggleStaffSelection(staff)" |
| | | > |
| | | <view class="staff-info"> |
| | | <view class="staff-name-row"> |
| | | <text class="staff-name">{{ staff.nickName }}</text> |
| | | <text class="staff-phone">{{ staff.phonenumber }}</text> |
| | | </view> |
| | | <view class="staff-detail-row"> |
| | | <text class="staff-dept">{{ staff.deptName }}</text> |
| | | </view> |
| | | </view> |
| | | <uni-icons |
| | | v-if="isStaffSelected(staff.userId)" |
| | | type="checkmarkempty" |
| | | size="24" |
| | | color="#007AFF" |
| | | ></uni-icons> |
| | | <view v-else class="checkbox-empty"></view> |
| | | </view> |
| | | |
| | | <view class="no-data" v-if="filteredStaffList.length === 0"> |
| | | <uni-icons type="info" size="40" color="#ccc"></uni-icons> |
| | | <text>暂无人员数据</text> |
| | | </view> |
| | | </scroll-view> |
| | | |
| | | <view class="popup-footer"> |
| | | <button class="cancel-btn" @click="closeStaffSelector">取消</button> |
| | | <button class="confirm-btn" @click="confirmStaffSelection">确定(已选{{ selectedStaff.length }})</button> |
| | | </view> |
| | | </view> |
| | | </uni-popup> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import { mapState } from 'vuex' |
| | | import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue' |
| | | import { listBranchUsers } from "@/api/system/user" |
| | | |
| | | export default { |
| | | name: 'StaffSelector', |
| | | components: { |
| | | uniPopup |
| | | }, |
| | | props: { |
| | | // 已选择的人员列表 |
| | | value: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | // 标签文本 |
| | | label: { |
| | | type: String, |
| | | default: '执行任务人员' |
| | | }, |
| | | // 是否必填 |
| | | required: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // 是否自动添加当前用户 |
| | | autoAddCurrentUser: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // 当前用户是否可移除 |
| | | currentUserRemovable: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }, |
| | | data() { |
| | | return { |
| | | selectedStaff: [], |
| | | allStaffList: [], |
| | | filteredStaffList: [], |
| | | staffSearchKeyword: '', |
| | | staffFilterType: 'driver' // 默认选中司机 |
| | | } |
| | | }, |
| | | computed: { |
| | | ...mapState({ |
| | | currentUser: state => ({ |
| | | userId: state.user.userId, |
| | | nickName: state.user.nickName || '张三', |
| | | phonenumber: state.user.phonenumber || '', |
| | | deptId: state.user.deptId || 100, |
| | | posts: state.user.posts || [], |
| | | roles: state.user.roles || [], |
| | | dept: state.user.dept || null |
| | | }) |
| | | }) |
| | | }, |
| | | watch: { |
| | | value: { |
| | | handler(newVal) { |
| | | if (newVal && Array.isArray(newVal)) { |
| | | this.selectedStaff = [...newVal] |
| | | } |
| | | }, |
| | | immediate: true, |
| | | deep: true |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.loadStaffList() |
| | | |
| | | // 如果需要自动添加当前用户且选中人员为空 |
| | | if (this.autoAddCurrentUser && this.selectedStaff.length === 0) { |
| | | this.initWithCurrentUser() |
| | | } |
| | | }, |
| | | methods: { |
| | | // 初始化选中的人员(默认包含当前用户) |
| | | initWithCurrentUser() { |
| | | const currentUserStaff = { |
| | | userId: this.currentUser.userId, |
| | | nickName: this.currentUser.nickName, |
| | | phonenumber: this.currentUser.phonenumber, |
| | | deptId: this.currentUser.deptId, |
| | | posts: this.currentUser.posts || [], |
| | | roles: this.currentUser.roles || [], |
| | | dept: this.currentUser.dept || null |
| | | } |
| | | |
| | | // 获取当前用户的所有角色类型(可能有多个) |
| | | currentUserStaff.types = this.getUserTypes(currentUserStaff) |
| | | currentUserStaff.type = currentUserStaff.types[0] || 'driver' // 主要类型 |
| | | |
| | | this.selectedStaff = [currentUserStaff] |
| | | this.emitChange() |
| | | }, |
| | | |
| | | // 加载人员列表 |
| | | loadStaffList() { |
| | | listBranchUsers().then(response => { |
| | | const userList = response.data || [] |
| | | |
| | | this.allStaffList = userList.map(user => ({ |
| | | userId: user.userId, |
| | | nickName: user.nickName, |
| | | phonenumber: user.phonenumber, |
| | | deptName: user.dept?.deptName || '', |
| | | postName: user.posts && user.posts.length > 0 ? user.posts[0].postName : '', |
| | | roleName: user.roles && user.roles.length > 0 ? user.roles[0].roleName : '', |
| | | posts: user.posts || [], |
| | | roles: user.roles || [], |
| | | dept: user.dept || null, |
| | | // 支持多种类型 |
| | | types: this.getUserTypes(user), |
| | | type: this.getUserTypes(user)[0] || 'driver' // 主要类型(用于向后兼容) |
| | | })) |
| | | |
| | | this.filterStaffList() |
| | | }).catch(error => { |
| | | console.error('加载人员列表失败:', error) |
| | | this.$modal.showToast('加载人员列表失败') |
| | | }) |
| | | }, |
| | | |
| | | // 根据用户的岗位或角色判断所有类型(支持多种身份) |
| | | getUserTypes(user) { |
| | | const types = [] |
| | | const postNames = user.posts ? user.posts.map(p => p.postName).join('') : '' |
| | | const roleNames = user.roles ? user.roles.map(r => r.roleName).join('') : '' |
| | | const deptName = user.dept?.deptName || '' |
| | | |
| | | // 判断是否为司机 |
| | | if (postNames.includes('司机') || roleNames.includes('司机') || |
| | | deptName.includes('车队') || deptName.includes('司机')) { |
| | | types.push('driver') |
| | | } |
| | | |
| | | // 判断是否为医生 |
| | | if (postNames.includes('医生') || roleNames.includes('医生') || |
| | | deptName.includes('医生') || deptName.includes('医护')) { |
| | | types.push('doctor') |
| | | } |
| | | |
| | | // 判断是否为护士 |
| | | if (postNames.includes('护士') || roleNames.includes('护士') || deptName.includes('护士')) { |
| | | types.push('nurse') |
| | | } |
| | | |
| | | // 如果没有匹配到任何类型,默认为司机 |
| | | if (types.length === 0) { |
| | | types.push('driver') |
| | | } |
| | | |
| | | return types |
| | | }, |
| | | |
| | | // 获取类型名称 |
| | | getUserTypeName(staffType) { |
| | | const typeMap = { |
| | | 'driver': '司机', |
| | | 'doctor': '医生', |
| | | 'nurse': '护士' |
| | | } |
| | | return typeMap[staffType] || staffType || '司机' |
| | | }, |
| | | |
| | | // 显示人员选择弹窗 |
| | | showStaffSelector() { |
| | | this.$refs.staffPopup.open() |
| | | this.filterStaffList() |
| | | }, |
| | | |
| | | // 关闭人员选择弹窗 |
| | | closeStaffSelector() { |
| | | this.$refs.staffPopup.close() |
| | | this.staffSearchKeyword = '' |
| | | this.staffFilterType = 'driver' // 重置为默认司机 |
| | | }, |
| | | |
| | | // 人员搜索 |
| | | onStaffSearch(e) { |
| | | this.staffSearchKeyword = e.detail.value |
| | | this.filterStaffList() |
| | | }, |
| | | |
| | | // 筛选人员类型 |
| | | filterStaff(type) { |
| | | this.staffFilterType = type |
| | | this.filterStaffList() |
| | | }, |
| | | |
| | | // 过滤人员列表 |
| | | filterStaffList() { |
| | | let list = [...this.allStaffList] |
| | | |
| | | // 按关键词搜索 |
| | | if (this.staffSearchKeyword && this.staffSearchKeyword.trim() !== '') { |
| | | const keyword = this.staffSearchKeyword.trim().toLowerCase() |
| | | list = list.filter(staff => { |
| | | return staff.nickName.toLowerCase().includes(keyword) || |
| | | (staff.phonenumber && staff.phonenumber.includes(keyword)) |
| | | }) |
| | | } |
| | | |
| | | // 根据选中的类型,将有该身份的人排在前面 |
| | | list.sort((a, b) => { |
| | | const aHasType = a.types.includes(this.staffFilterType) |
| | | const bHasType = b.types.includes(this.staffFilterType) |
| | | |
| | | if (aHasType && !bHasType) return -1 // a有该身份,排前面 |
| | | if (!aHasType && bHasType) return 1 // b有该身份,排前面 |
| | | return 0 // 都有或都没有,保持原顺序 |
| | | }) |
| | | |
| | | this.filteredStaffList = list |
| | | }, |
| | | |
| | | // 切换人员选中状态 |
| | | toggleStaffSelection(staff) { |
| | | const index = this.selectedStaff.findIndex(s => s.userId === staff.userId) |
| | | |
| | | if (index > -1) { |
| | | // 如果是第一个且不可移除(当前用户),不允许移除 |
| | | if (!this.canRemove(index)) { |
| | | this.$modal.showToast('当前用户不能移除') |
| | | return |
| | | } |
| | | // 已选中,移除 |
| | | this.selectedStaff.splice(index, 1) |
| | | } else { |
| | | // 未选中,添加 |
| | | this.selectedStaff.push({ ...staff }) |
| | | } |
| | | }, |
| | | |
| | | // 判断人员是否已选中 |
| | | isStaffSelected(userId) { |
| | | return this.selectedStaff.some(staff => staff.userId === userId) |
| | | }, |
| | | |
| | | // 判断是否可以移除 |
| | | canRemove(index) { |
| | | // 如果是第一个且当前用户不可移除 |
| | | if (index === 0 && !this.currentUserRemovable) { |
| | | return false |
| | | } |
| | | return true |
| | | }, |
| | | |
| | | // 确认人员选择 |
| | | confirmStaffSelection() { |
| | | if (this.selectedStaff.length === 0) { |
| | | this.$modal.showToast('请至少选择一名人员') |
| | | return |
| | | } |
| | | this.emitChange() |
| | | this.closeStaffSelector() |
| | | }, |
| | | |
| | | // 移除人员 |
| | | removeStaff(index) { |
| | | if (!this.canRemove(index)) { |
| | | this.$modal.showToast('当前用户不能移除') |
| | | return |
| | | } |
| | | this.selectedStaff.splice(index, 1) |
| | | this.emitChange() |
| | | }, |
| | | |
| | | // 触发change事件 |
| | | emitChange() { |
| | | this.$emit('input', this.selectedStaff) |
| | | this.$emit('change', this.selectedStaff) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .staff-selector-component { |
| | | .form-item { |
| | | margin-bottom: 40rpx; |
| | | |
| | | .form-label { |
| | | font-size: 28rpx; |
| | | margin-bottom: 15rpx; |
| | | color: #333; |
| | | |
| | | &.required::before { |
| | | content: '*'; |
| | | color: #ff4d4f; |
| | | margin-right: 4rpx; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | |
| | | .staff-list { |
| | | .staff-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 20rpx; |
| | | background-color: #f9f9f9; |
| | | border-radius: 10rpx; |
| | | margin-bottom: 20rpx; |
| | | |
| | | .staff-info { |
| | | flex: 1; |
| | | |
| | | .staff-name { |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | margin-right: 10rpx; |
| | | display: block; |
| | | margin-bottom: 8rpx; |
| | | } |
| | | |
| | | .staff-roles { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8rpx; |
| | | |
| | | .staff-role { |
| | | font-size: 22rpx; |
| | | color: white; |
| | | background-color: #007AFF; |
| | | padding: 4rpx 12rpx; |
| | | border-radius: 6rpx; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .add-staff { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 20rpx; |
| | | border: 1rpx dashed #007AFF; |
| | | border-radius: 10rpx; |
| | | color: #007AFF; |
| | | |
| | | text { |
| | | margin-left: 10rpx; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 人员选择弹窗样式 |
| | | .staff-selector-popup { |
| | | background-color: white; |
| | | border-radius: 20rpx 20rpx 0 0; |
| | | max-height: 80vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .popup-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 30rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | flex-shrink: 0; |
| | | |
| | | .popup-title { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | |
| | | .popup-close { |
| | | padding: 10rpx; |
| | | } |
| | | } |
| | | |
| | | .search-box { |
| | | display: flex; |
| | | align-items: center; |
| | | margin: 20rpx 30rpx; |
| | | padding: 15rpx 20rpx; |
| | | background-color: #f5f5f5; |
| | | border-radius: 10rpx; |
| | | flex-shrink: 0; |
| | | |
| | | .search-input { |
| | | flex: 1; |
| | | margin-left: 10rpx; |
| | | font-size: 28rpx; |
| | | } |
| | | } |
| | | |
| | | .staff-filter { |
| | | display: flex; |
| | | padding: 0 30rpx 20rpx; |
| | | gap: 15rpx; |
| | | flex-shrink: 0; |
| | | |
| | | .filter-item { |
| | | flex: 1; |
| | | text-align: center; |
| | | padding: 15rpx 0; |
| | | background-color: #f5f5f5; |
| | | border-radius: 10rpx; |
| | | font-size: 26rpx; |
| | | color: #666; |
| | | |
| | | &.active { |
| | | background-color: #007AFF; |
| | | color: white; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .staff-list-popup { |
| | | flex: 1; |
| | | overflow-y: auto; |
| | | padding: 0 30rpx; |
| | | |
| | | .staff-item-popup { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 25rpx 20rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | |
| | | &:active { |
| | | background-color: #f5f5f5; |
| | | } |
| | | |
| | | .staff-info { |
| | | flex: 1; |
| | | |
| | | .staff-name-row { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 10rpx; |
| | | |
| | | .staff-name { |
| | | font-size: 30rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | margin-right: 20rpx; |
| | | } |
| | | |
| | | .staff-phone { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | } |
| | | } |
| | | |
| | | .staff-detail-row { |
| | | display: flex; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | |
| | | .staff-dept { |
| | | font-size: 24rpx; |
| | | color: #666; |
| | | margin-right: 15rpx; |
| | | } |
| | | |
| | | .staff-types { |
| | | display: flex; |
| | | gap: 8rpx; |
| | | |
| | | .type-tag { |
| | | font-size: 20rpx; |
| | | color: white; |
| | | padding: 4rpx 10rpx; |
| | | border-radius: 6rpx; |
| | | |
| | | &.type-driver { |
| | | background-color: #007AFF; |
| | | } |
| | | |
| | | &.type-doctor { |
| | | background-color: #34C759; |
| | | } |
| | | |
| | | &.type-nurse { |
| | | background-color: #AF52DE; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .checkbox-empty { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | border: 2rpx solid #ddd; |
| | | border-radius: 50%; |
| | | } |
| | | } |
| | | |
| | | .no-data { |
| | | text-align: center; |
| | | padding: 100rpx 0; |
| | | color: #999; |
| | | |
| | | text { |
| | | display: block; |
| | | margin-top: 20rpx; |
| | | font-size: 28rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .popup-footer { |
| | | display: flex; |
| | | padding: 20rpx 30rpx; |
| | | border-top: 1rpx solid #f0f0f0; |
| | | gap: 20rpx; |
| | | flex-shrink: 0; |
| | | |
| | | button { |
| | | flex: 1; |
| | | height: 80rpx; |
| | | border-radius: 10rpx; |
| | | font-size: 30rpx; |
| | | } |
| | | |
| | | .cancel-btn { |
| | | background-color: #f5f5f5; |
| | | color: #666; |
| | | } |
| | | |
| | | .confirm-btn { |
| | | background-color: #007AFF; |
| | | color: white; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | </view> |
| | | </picker> |
| | | </view> |
| | | <view class="form-item"> |
| | | <view class="form-label">执行任务人员</view> |
| | | <view class="staff-list"> |
| | | <view class="staff-item" v-for="(staff, index) in selectedStaff" :key="staff.userId"> |
| | | <view class="staff-info"> |
| | | <text class="staff-name">{{ staff.nickName }}</text> |
| | | <text class="staff-role">({{ getUserTypeName(staff.type) || '未知职位' }})</text> |
| | | </view> |
| | | <uni-icons |
| | | v-if="index > 0" |
| | | type="closeempty" |
| | | size="20" |
| | | color="#ff4d4f" |
| | | @click="removeStaff(index)" |
| | | ></uni-icons> |
| | | <uni-icons |
| | | v-else |
| | | type="checkmarkempty" |
| | | size="20" |
| | | color="#007AFF" |
| | | ></uni-icons> |
| | | </view> |
| | | <view class="add-staff" @click="showStaffSelector"> |
| | | <uni-icons type="plusempty" size="20" color="#007AFF"></uni-icons> |
| | | <text>添加人员</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <StaffSelector |
| | | v-model="selectedStaff" |
| | | label="执行任务人员" |
| | | :required="false" |
| | | :auto-add-current-user="true" |
| | | :current-user-removable="false" |
| | | @change="onStaffChange" |
| | | /> |
| | | |
| | | |
| | | |
| | |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 人员选择弹窗 --> |
| | | <uni-popup ref="staffPopup" type="bottom" :safe-area="true"> |
| | | <view class="staff-selector-popup"> |
| | | <view class="popup-header"> |
| | | <view class="popup-title">选择执行人员</view> |
| | | <view class="popup-close" @click="closeStaffSelector"> |
| | | <uni-icons type="closeempty" size="24" color="#333"></uni-icons> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="search-box"> |
| | | <uni-icons type="search" size="18" color="#999"></uni-icons> |
| | | <input |
| | | class="search-input" |
| | | placeholder="搜索姓名、手机号" |
| | | v-model="staffSearchKeyword" |
| | | @input="onStaffSearch" |
| | | /> |
| | | </view> |
| | | |
| | | <view class="staff-filter"> |
| | | <view |
| | | class="filter-item" |
| | | :class="{ active: staffFilterType === 'driver' }" |
| | | @click="filterStaff('driver')" |
| | | >司机</view> |
| | | <view |
| | | class="filter-item" |
| | | :class="{ active: staffFilterType === 'doctor' }" |
| | | @click="filterStaff('doctor')" |
| | | >医生</view> |
| | | <view |
| | | class="filter-item" |
| | | :class="{ active: staffFilterType === 'nurse' }" |
| | | @click="filterStaff('nurse')" |
| | | >护士</view> |
| | | </view> |
| | | |
| | | <scroll-view class="staff-list-popup" scroll-y="true"> |
| | | <view |
| | | class="staff-item-popup" |
| | | v-for="staff in filteredStaffList" |
| | | :key="staff.userId" |
| | | @click="toggleStaffSelection(staff)" |
| | | > |
| | | <view class="staff-info"> |
| | | <view class="staff-name-row"> |
| | | <text class="staff-name">{{ staff.nickName }}</text> |
| | | <text class="staff-phone">{{ staff.phonenumber }}</text> |
| | | </view> |
| | | <view class="staff-detail-row"> |
| | | <text class="staff-dept">{{ staff.deptName }}</text> |
| | | <text class="staff-post">{{ staff.postName || staff.roleName || '未知职位' }}</text> |
| | | </view> |
| | | </view> |
| | | <uni-icons |
| | | v-if="isStaffSelected(staff.userId)" |
| | | type="checkmarkempty" |
| | | size="24" |
| | | color="#007AFF" |
| | | ></uni-icons> |
| | | <view v-else class="checkbox-empty"></view> |
| | | </view> |
| | | |
| | | <view class="no-data" v-if="filteredStaffList.length === 0"> |
| | | <uni-icons type="info" size="40" color="#ccc"></uni-icons> |
| | | <text>暂无人员数据</text> |
| | | </view> |
| | | </scroll-view> |
| | | |
| | | <view class="popup-footer"> |
| | | <button class="cancel-btn" @click="closeStaffSelector">取消</button> |
| | | <button class="confirm-btn" @click="confirmStaffSelection">确定(已选{{ selectedStaff.length }})</button> |
| | | </view> |
| | | </view> |
| | | </uni-popup> |
| | | |
| | | <!-- 智能识别弹窗 --> |
| | | <uni-popup ref="smartParsePopup" type="bottom" :safe-area="true"> |
| | |
| | | import OrganizationSelector from './components/OrganizationSelector.vue' |
| | | import HospitalSelector from './components/HospitalSelector.vue' |
| | | import DiseaseSelector from './components/DiseaseSelector.vue' |
| | | import StaffSelector from './components/StaffSelector.vue' |
| | | |
| | | export default { |
| | | components: { |
| | |
| | | OrganizationSelector, |
| | | HospitalSelector, |
| | | DiseaseSelector, |
| | | DepartureSelector |
| | | DepartureSelector, |
| | | StaffSelector |
| | | }, |
| | | data() { |
| | | return { |
| | |
| | | mapSelectorType: '', |
| | | // 人员选择相关 |
| | | selectedStaff: [], // 已选择的人员列表 |
| | | allStaffList: [], // 所有人员列表 |
| | | filteredStaffList: [], // 过滤后的人员列表 |
| | | staffSearchKeyword: '', // 人员搜索关键词 |
| | | staffFilterType: 'driver', // 人员筛选类型:driver/doctor/nurse,默认选中司机 |
| | | // 病情选择相关 |
| | | selectedDiseases: [], // 已选择的病情列表 |
| | | taskForm: { |
| | |
| | | this.getAvailableVehicles().then(() => { |
| | | this.getUserBoundVehicleInfo() |
| | | }) |
| | | this.initSelectedStaff() |
| | | this.loadDeptStaff() |
| | | // 加载科室字典数据 |
| | | this.loadDepartments() |
| | | // 加载任务类型数据 |
| | |
| | | this.selectedEmergencyTaskType = selected.text |
| | | this.selectedEmergencyTaskTypeId = selected.id |
| | | }, |
| | | getUserTypeName(staffType){ |
| | | switch(staffType){ |
| | | case "nurse": |
| | | return "护士"; |
| | | case "doctor": |
| | | return "医生"; |
| | | case "driver": |
| | | return "司机"; |
| | | default: |
| | | return "司机"; |
| | | } |
| | | }, |
| | | |
| | | |
| | | // 加载单据类型数据 |
| | | loadDocumentTypes() { |
| | |
| | | } |
| | | }, |
| | | |
| | | // 初始化选中的人员(默认包含当前用户) |
| | | initSelectedStaff() { |
| | | // 构建当前用户对象,包含完整的角色信息 |
| | | const currentUserStaff = { |
| | | userId: this.currentUser.userId, |
| | | nickName: this.currentUser.nickName, |
| | | phonenumber: this.currentUser.phonenumber, |
| | | postName: this.currentUser.position, |
| | | deptId: this.currentUser.deptId, |
| | | posts: this.currentUser.posts || [], |
| | | roles: this.currentUser.roles || [], |
| | | dept: this.currentUser.dept || null |
| | | } |
| | | |
| | | // 为当前用户设置角色类型 |
| | | currentUserStaff.type = this.getUserType(currentUserStaff) |
| | | |
| | | this.selectedStaff = [currentUserStaff] |
| | | }, |
| | | |
| | | // 加载当前用户所在分公司的所有人员 |
| | | loadDeptStaff() { |
| | | console.log('开始加载人员列表') |
| | | |
| | | // 调用新接口,自动根据当前用户的oaOrderClass获取分公司下的用户 |
| | | listBranchUsers().then(response => { |
| | | console.log('人员列表API响应:', response) |
| | | const userList = response.data || [] |
| | | console.log('解析出的用户列表:', userList, '数量:', userList.length) |
| | | |
| | | this.allStaffList = userList.map(user => ({ |
| | | userId: user.userId, |
| | | nickName: user.nickName, |
| | | phonenumber: user.phonenumber, |
| | | deptName: user.dept?.deptName || '', |
| | | postName: user.posts && user.posts.length > 0 ? user.posts[0].postName : '', |
| | | roleName: user.roles && user.roles.length > 0 ? user.roles[0].roleName : '', |
| | | // 根据岗位名称或角色名称判断类型 |
| | | type: this.getUserType(user) |
| | | })) |
| | | |
| | | console.log('处理后的人员列表:', this.allStaffList, '数量:', this.allStaffList.length) |
| | | |
| | | // 初始化过滤列表 |
| | | this.filterStaffList() |
| | | }).catch(error => { |
| | | console.error('加载人员列表失败:', error) |
| | | this.$modal.showToast('加载人员列表失败') |
| | | }) |
| | | }, |
| | | |
| | | // 根据用户的岗位或角色判断类型 |
| | | getUserType(user) { |
| | | const postName = user.posts && user.posts.length > 0 ? user.posts[0].postName : '' |
| | | const roleName = user.roles && user.roles.length > 0 ? user.roles[0].roleName : '' |
| | | const deptName = user.dept?.deptName || '' |
| | | // console.log("获取用户类型:", postName, roleName,user) |
| | | // 判断是否为司机 |
| | | if (postName.includes('司机') || roleName.includes('司机') || deptName.includes('车队') || deptName.includes('司机')) { |
| | | return 'driver' |
| | | } |
| | | // 判断是否为护士 |
| | | if (postName.includes('护士') || roleName.includes('护士') || deptName.includes('护士')) { |
| | | return 'nurse' |
| | | } |
| | | // 判断是否为医生 |
| | | if (postName.includes('医生') || roleName.includes('医生') || deptName.includes('医生') ) { |
| | | return 'doctor' |
| | | } |
| | | if( deptName.includes("医护")){ |
| | | return 'doctor' |
| | | } |
| | | |
| | | // 其他类型,默认为司机 |
| | | return 'driver' |
| | | }, |
| | | |
| | | // 显示人员选择弹窗 |
| | | showStaffSelector() { |
| | | this.$refs.staffPopup.open() |
| | | this.filterStaffList() |
| | | }, |
| | | |
| | | // 关闭人员选择弹窗 |
| | | closeStaffSelector() { |
| | | this.$refs.staffPopup.close() |
| | | this.staffSearchKeyword = '' |
| | | this.staffFilterType = 'driver' // 重置为默认的司机类型 |
| | | }, |
| | | |
| | | // 人员搜索 |
| | | onStaffSearch(e) { |
| | | this.staffSearchKeyword = e.detail.value |
| | | this.filterStaffList() |
| | | }, |
| | | |
| | | // 筛选人员类型 |
| | | filterStaff(type) { |
| | | this.staffFilterType = type |
| | | this.filterStaffList() |
| | | }, |
| | | |
| | | // 过滤人员列表 |
| | | filterStaffList() { |
| | | console.log('开始过滤人员列表,原始数量:', this.allStaffList.length) |
| | | let list = [...this.allStaffList] |
| | | |
| | | // 按类型过滤 |
| | | if (this.staffFilterType === 'driver') { |
| | | list = list.filter(staff => staff.type === 'driver') |
| | | } else if (this.staffFilterType === 'doctor') { |
| | | list = list.filter(staff => staff.type === 'doctor') |
| | | } else if (this.staffFilterType === 'nurse') { |
| | | list = list.filter(staff => staff.type === 'nurse') |
| | | } |
| | | |
| | | console.log('按类型过滤后:', this.staffFilterType, '数量:', list.length) |
| | | |
| | | // 按关键词搜索 |
| | | if (this.staffSearchKeyword && this.staffSearchKeyword.trim() !== '') { |
| | | const keyword = this.staffSearchKeyword.trim().toLowerCase() |
| | | list = list.filter(staff => { |
| | | return staff.nickName.toLowerCase().includes(keyword) || |
| | | (staff.phonenumber && staff.phonenumber.includes(keyword)) |
| | | }) |
| | | } |
| | | |
| | | console.log('按关键词过滤后,数量:', list.length) |
| | | |
| | | this.filteredStaffList = list |
| | | // console.log('最终过滤结果:', this.filteredStaffList) |
| | | }, |
| | | |
| | | // 切换人员选中状态 |
| | | toggleStaffSelection(staff) { |
| | | const index = this.selectedStaff.findIndex(s => s.userId === staff.userId) |
| | | |
| | | if (index > -1) { |
| | | // 如果是第一个(当前用户),不允许移除 |
| | | if (index === 0) { |
| | | this.$modal.showToast('当前用户不能移除') |
| | | return |
| | | } |
| | | // 已选中,移除 |
| | | this.selectedStaff.splice(index, 1) |
| | | } else { |
| | | // 未选中,添加 |
| | | this.selectedStaff.push(staff) |
| | | } |
| | | }, |
| | | |
| | | // 判断人员是否已选中 |
| | | isStaffSelected(userId) { |
| | | return this.selectedStaff.some(staff => staff.userId === userId) |
| | | }, |
| | | |
| | | // 确认人员选择 |
| | | confirmStaffSelection() { |
| | | if (this.selectedStaff.length === 0) { |
| | | this.$modal.showToast('请至少选择一名人员') |
| | | return |
| | | } |
| | | this.closeStaffSelector() |
| | | }, |
| | | |
| | | // 移除人员 |
| | | removeStaff(index) { |
| | | if (index === 0) { |
| | | this.$modal.showToast('当前用户不能移除') |
| | | return |
| | | } |
| | | this.selectedStaff.splice(index, 1) |
| | | }, |
| | | |
| | | addStaff() { |
| | | this.showStaffSelector() |
| | | // 人员变化事件 |
| | | onStaffChange(staff) { |
| | | console.log('选中人员变化:', staff) |
| | | // 组件已经通过 v-model 更新了 selectedStaff |
| | | }, |
| | | calculateDistanceByManualAddress() { |
| | | const fromAddress = this.taskForm.hospitalOut.address |
| | |
| | | color: #999; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 人员选择弹窗样式 |
| | | .staff-selector-popup { |
| | | background-color: white; |
| | | border-radius: 20rpx 20rpx 0 0; |
| | | max-height: 80vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .popup-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 30rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | flex-shrink: 0; |
| | | |
| | | .popup-title { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | |
| | | .popup-close { |
| | | padding: 10rpx; |
| | | } |
| | | } |
| | | |
| | | .search-box { |
| | | display: flex; |
| | | align-items: center; |
| | | margin: 20rpx 30rpx; |
| | | padding: 15rpx 20rpx; |
| | | background-color: #f5f5f5; |
| | | border-radius: 10rpx; |
| | | flex-shrink: 0; |
| | | |
| | | .search-input { |
| | | flex: 1; |
| | | margin-left: 10rpx; |
| | | font-size: 28rpx; |
| | | } |
| | | } |
| | | |
| | | .staff-filter { |
| | | display: flex; |
| | | padding: 0 30rpx 20rpx; |
| | | gap: 20rpx; |
| | | flex-shrink: 0; |
| | | |
| | | .filter-item { |
| | | flex: 1; |
| | | text-align: center; |
| | | padding: 15rpx 0; |
| | | background-color: #f5f5f5; |
| | | border-radius: 10rpx; |
| | | font-size: 28rpx; |
| | | color: #666; |
| | | |
| | | &.active { |
| | | background-color: #007AFF; |
| | | color: white; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .staff-list-popup { |
| | | flex: 1; |
| | | overflow-y: auto; |
| | | padding: 0 30rpx; |
| | | |
| | | .staff-item-popup { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 25rpx 20rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | |
| | | &:active { |
| | | background-color: #f5f5f5; |
| | | } |
| | | |
| | | .staff-info { |
| | | flex: 1; |
| | | |
| | | .staff-name-row { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 10rpx; |
| | | |
| | | .staff-name { |
| | | font-size: 30rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | margin-right: 20rpx; |
| | | } |
| | | |
| | | .staff-phone { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | } |
| | | } |
| | | |
| | | .staff-detail-row { |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .staff-dept { |
| | | font-size: 24rpx; |
| | | color: #666; |
| | | margin-right: 20rpx; |
| | | } |
| | | |
| | | .staff-post { |
| | | font-size: 24rpx; |
| | | color: #007AFF; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .checkbox-empty { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | border: 2rpx solid #ddd; |
| | | border-radius: 50%; |
| | | } |
| | | } |
| | | |
| | | .no-data { |
| | | text-align: center; |
| | | padding: 100rpx 0; |
| | | color: #999; |
| | | |
| | | text { |
| | | display: block; |
| | | margin-top: 20rpx; |
| | | font-size: 28rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .popup-footer { |
| | | display: flex; |
| | | padding: 20rpx 30rpx; |
| | | border-top: 1rpx solid #f0f0f0; |
| | | gap: 20rpx; |
| | | flex-shrink: 0; |
| | | |
| | | button { |
| | | flex: 1; |
| | | height: 80rpx; |
| | | border-radius: 10rpx; |
| | | font-size: 30rpx; |
| | | } |
| | | |
| | | .cancel-btn { |
| | | background-color: #f5f5f5; |
| | | color: #666; |
| | | } |
| | | |
| | | .confirm-btn { |
| | | background-color: #007AFF; |
| | | color: white; |
| | | } |
| | | } |
| | | } |
| | |
| | | <view class="label">执行车辆</view> |
| | | <view class="value">{{ getVehicleInfo(taskDetail) }}</view> |
| | | </view> |
| | | <view class="info-item"> |
| | | <view class="label">执行人员</view> |
| | | <view class="value">{{ taskDetail.assigneeName || '未分配' }}</view> |
| | | </view> |
| | | |
| | | <!-- 执行人员列表 --> |
| | | <view class="detail-section"> |
| | | <view class="section-title">执行人员</view> |
| | | <view v-if="taskDetail.assignees && taskDetail.assignees.length > 0" class="assignee-list"> |
| | | <view |
| | | class="assignee-item" |
| | | v-for="(assignee, index) in taskDetail.assignees" |
| | | :key="assignee.userId || index" |
| | | > |
| | | <view class="assignee-index">{{ index + 1 }}</view> |
| | | <view class="assignee-info"> |
| | | <view class="assignee-name"> |
| | | {{ assignee.userName }} |
| | | <view v-if="assignee.isPrimary === '1'" class="primary-badge"> |
| | | <uni-icons type="star-filled" size="12" color="#ff9500"></uni-icons> |
| | | <text>负责人</text> |
| | | </view> |
| | | </view> |
| | | <view class="assignee-role"> |
| | | <view |
| | | class="role-tag" |
| | | :class="{ |
| | | 'role-driver': assignee.userType === 'driver', |
| | | 'role-doctor': assignee.userType === 'doctor', |
| | | 'role-nurse': assignee.userType === 'nurse' |
| | | }" |
| | | > |
| | | {{ getUserTypeLabel(assignee.userType) }} |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else class="empty-assignee"> |
| | | <uni-icons type="info" size="40" color="#ccc"></uni-icons> |
| | | <text>暂无执行人员</text> |
| | | </view> |
| | | </view> |
| | | |
| | |
| | | getTask(this.taskId).then(response => { |
| | | this.taskDetail = response.data || response |
| | | // 调试:打印返回的数据 |
| | | console.log('任务详情完整数据:', JSON.stringify(this.taskDetail, null, 2)) |
| | | console.log('任务类型字段值:', this.taskDetail.taskType) |
| | | console.log('任务状态字段值:', this.taskDetail.taskStatus) |
| | | console.log('出发地址:', this.taskDetail.departureAddress) |
| | | console.log('目的地址:', this.taskDetail.destinationAddress) |
| | | console.log('转运任务信息 (emergencyInfo):', this.taskDetail.emergencyInfo) |
| | | // console.log('任务详情完整数据:', JSON.stringify(this.taskDetail, null, 2)) |
| | | // console.log('任务类型字段值:', this.taskDetail.taskType) |
| | | // console.log('任务状态字段值:', this.taskDetail.taskStatus) |
| | | // console.log('出发地址:', this.taskDetail.departureAddress) |
| | | // console.log('目的地址:', this.taskDetail.destinationAddress) |
| | | // console.log('转运任务信息 (emergencyInfo):', this.taskDetail.emergencyInfo) |
| | | |
| | | // 如果是转运任务,加载支付信息 |
| | | if (this.taskDetail.taskType === 'EMERGENCY_TRANSFER') { |
| | |
| | | return remaining > 0 ? remaining.toFixed(2) : '0.00' |
| | | }, |
| | | |
| | | // 获取车辆信息 |
| | | // 获取车辆信息(修复:防止 assignedVehicles 为 null) |
| | | getVehicleInfo(task) { |
| | | if (task.assignedVehicles && task.assignedVehicles.length > 0) { |
| | | if (task.assignedVehicles && Array.isArray(task.assignedVehicles) && task.assignedVehicles.length > 0) { |
| | | const firstVehicle = task.assignedVehicles[0] |
| | | let vehicleInfo = firstVehicle.vehicleNo || '未知车牌' |
| | | if (task.assignedVehicles.length > 1) { |
| | |
| | | return typeMap[type] || '未知类型' |
| | | }, |
| | | |
| | | // 获取用户类型标签 |
| | | getUserTypeLabel(userType) { |
| | | const typeMap = { |
| | | 'driver': '司机', |
| | | 'doctor': '医生', |
| | | 'nurse': '护士' |
| | | } |
| | | return typeMap[userType] || userType || '未知' |
| | | }, |
| | | |
| | | // 处理结算 |
| | | handleSettlement() { |
| | | uni.navigateTo({ |
| | |
| | | |
| | | const activeTasks = response.data || []; |
| | | |
| | | // 过滤掉当前任务本身 |
| | | const otherActiveTasks = activeTasks.filter(task => task.taskId !== this.taskId); |
| | | // 过滤掉当前任务本身(修复:防止 activeTasks 为 null) |
| | | const otherActiveTasks = (activeTasks && Array.isArray(activeTasks)) ? activeTasks.filter(task => task.taskId !== this.taskId) : []; |
| | | |
| | | if (otherActiveTasks.length > 0) { |
| | | // 车辆有其他正在进行中的任务 |
| | |
| | | } |
| | | } |
| | | |
| | | // 执行人员列表样式 |
| | | .assignee-list { |
| | | .assignee-item { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 20rpx; |
| | | margin-bottom: 15rpx; |
| | | background-color: #f9f9f9; |
| | | border-radius: 10rpx; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .assignee-index { |
| | | width: 50rpx; |
| | | height: 50rpx; |
| | | border-radius: 50%; |
| | | background-color: #007AFF; |
| | | color: white; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 24rpx; |
| | | font-weight: bold; |
| | | margin-right: 20rpx; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .assignee-info { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10rpx; |
| | | |
| | | .assignee-name { |
| | | display: flex; |
| | | align-items: center; |
| | | font-size: 30rpx; |
| | | color: #333; |
| | | font-weight: 500; |
| | | |
| | | .primary-badge { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 4rpx; |
| | | margin-left: 12rpx; |
| | | padding: 4rpx 12rpx; |
| | | background-color: #fff3e0; |
| | | border-radius: 6rpx; |
| | | |
| | | text { |
| | | font-size: 20rpx; |
| | | color: #ff9500; |
| | | font-weight: normal; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .assignee-role { |
| | | .role-tag { |
| | | display: inline-block; |
| | | padding: 4rpx 12rpx; |
| | | border-radius: 6rpx; |
| | | font-size: 22rpx; |
| | | color: white; |
| | | |
| | | &.role-driver { |
| | | background-color: #007AFF; |
| | | } |
| | | |
| | | &.role-doctor { |
| | | background-color: #34C759; |
| | | } |
| | | |
| | | &.role-nurse { |
| | | background-color: #AF52DE; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .empty-assignee { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 60rpx 0; |
| | | color: #999; |
| | | |
| | | text { |
| | | margin-top: 20rpx; |
| | | font-size: 28rpx; |
| | | } |
| | | } |
| | | |
| | | .info-item { |
| | | display: flex; |
| | | margin-bottom: 20rpx; |
| | |
| | | /> |
| | | </view> |
| | | |
| | | <DepartureSelector |
| | | :address.sync="departureAddress" |
| | | :longitude.sync="departureLongitude" |
| | | :latitude.sync="departureLatitude" |
| | | :region="selectedRegion" |
| | | :required="false" |
| | | @address-selected="onDepartureAddressSelected" |
| | | @location-success="onDepartureLocationSuccess" |
| | | /> |
| | | |
| | | <view class="form-item"> |
| | | <view class="form-label required">转运时间</view> |
| | | <uni-datetime-picker |
| | |
| | | /> |
| | | </view> |
| | | |
| | | <view class="form-item"> |
| | | <view class="form-label">执行任务人员</view> |
| | | <view class="staff-list"> |
| | | <view class="staff-item" v-for="(staff, index) in selectedStaff" :key="staff.userId"> |
| | | <view class="staff-info"> |
| | | <text class="staff-name">{{ staff.nickName }}</text> |
| | | <text class="staff-role">({{ getUserTypeName(staff.type) || '未知职位' }})</text> |
| | | </view> |
| | | <uni-icons |
| | | type="closeempty" |
| | | size="20" |
| | | color="#ff4d4f" |
| | | @click="removeStaff(index)" |
| | | ></uni-icons> |
| | | </view> |
| | | <view class="add-staff" @click="showStaffSelector"> |
| | | <uni-icons type="plusempty" size="20" color="#007AFF"></uni-icons> |
| | | <text>添加人员</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <StaffSelector |
| | | v-model="selectedStaff" |
| | | label="执行任务人员" |
| | | :required="false" |
| | | :auto-add-current-user="false" |
| | | :current-user-removable="true" |
| | | @change="onStaffChange" |
| | | /> |
| | | |
| | | <view class="form-section-title">患者信息</view> |
| | | <view class="form-item"> |
| | |
| | | ></map-selector> |
| | | </view> |
| | | </uni-popup> |
| | | |
| | | <!-- 人员选择器弹窗 --> |
| | | <uni-popup ref="staffPopup" type="bottom" :mask-click="false"> |
| | | <view class="staff-popup-container"> |
| | | <view class="popup-header"> |
| | | <view class="popup-title">选择执行人员</view> |
| | | <view class="close-btn" @click="closeStaffSelector"> |
| | | <uni-icons type="closeempty" size="20" color="#999"></uni-icons> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="search-bar"> |
| | | <input |
| | | class="search-input" |
| | | placeholder="搜索姓名或电话" |
| | | v-model="staffSearchKeyword" |
| | | @input="onStaffSearch" |
| | | /> |
| | | </view> |
| | | |
| | | <view class="filter-tabs"> |
| | | <view |
| | | class="filter-tab" |
| | | :class="{ active: staffFilterType === 'driver' }" |
| | | @click="filterStaff('driver')" |
| | | > |
| | | 司机 |
| | | </view> |
| | | <view |
| | | class="filter-tab" |
| | | :class="{ active: staffFilterType === 'doctor' }" |
| | | @click="filterStaff('doctor')" |
| | | > |
| | | 医生 |
| | | </view> |
| | | <view |
| | | class="filter-tab" |
| | | :class="{ active: staffFilterType === 'nurse' }" |
| | | @click="filterStaff('nurse')" |
| | | > |
| | | 护士 |
| | | </view> |
| | | </view> |
| | | |
| | | <scroll-view class="staff-list-scroll" scroll-y="true"> |
| | | <view |
| | | class="staff-list-item" |
| | | v-for="staff in filteredStaffList" |
| | | :key="staff.userId" |
| | | @click="toggleStaffSelection(staff)" |
| | | > |
| | | <view class="staff-item-info"> |
| | | <text class="staff-item-name">{{ staff.nickName }}</text> |
| | | <text class="staff-item-dept">{{ staff.deptName }}</text> |
| | | </view> |
| | | <view class="staff-item-check"> |
| | | <uni-icons |
| | | v-if="isStaffSelected(staff.userId)" |
| | | type="checkmarkempty" |
| | | size="24" |
| | | color="#007AFF" |
| | | ></uni-icons> |
| | | </view> |
| | | </view> |
| | | <view v-if="filteredStaffList.length === 0" class="empty-tip"> |
| | | 暂无人员数据 |
| | | </view> |
| | | </scroll-view> |
| | | |
| | | <view class="popup-footer"> |
| | | <button class="confirm-btn" @click="confirmStaffSelection">确定(已选{{ selectedStaff.length }})</button> |
| | | </view> |
| | | </view> |
| | | </uni-popup> |
| | | </scroll-view> |
| | | </template> |
| | | |
| | |
| | | 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 { getTask, updateTask } from "@/api/task" |
| | | import { listBranchUsers } from "@/api/system/user" |
| | | import { baiduDistanceByAddress } from "@/api/map" |
| | | import { calculateTransferPrice } from "@/api/price" |
| | | import MapSelector from './components/map-selector.vue' |
| | |
| | | import OrganizationSelector from './components/OrganizationSelector.vue' |
| | | import HospitalSelector from './components/HospitalSelector.vue' |
| | | import DiseaseSelector from './components/DiseaseSelector.vue' |
| | | import DepartureSelector from './components/DepartureSelector.vue' |
| | | import StaffSelector from './components/StaffSelector.vue' |
| | | import distanceCalculator from '@/mixins/distanceCalculator.js' |
| | | |
| | | export default { |
| | |
| | | VehicleSelector, |
| | | OrganizationSelector, |
| | | HospitalSelector, |
| | | DiseaseSelector |
| | | DiseaseSelector, |
| | | DepartureSelector, |
| | | StaffSelector |
| | | }, |
| | | mixins: [distanceCalculator], |
| | | data() { |
| | |
| | | selectedOrganizationId: null, |
| | | selectedRegion: '', |
| | | mapSelectorType: '', |
| | | // 扩展 addressCoordinates 支持多种键名 |
| | | addressCoordinates: { |
| | | start: null, |
| | | end: null, |
| | | hospitalOutAddress: null, |
| | | hospitalInAddress: null |
| | | }, |
| | | // 出发地信息 |
| | | departureAddress: '', |
| | | departureLongitude: null, |
| | | departureLatitude: null, |
| | | selectedDiseases: [], // 已选择的病情列表 |
| | | selectedStaff: [], // 已选择的人员列表 |
| | | allStaffList: [], // 所有人员列表 |
| | | filteredStaffList: [], // 过滤后的人员列表 |
| | | staffSearchKeyword: '', // 人员搜索关键词 |
| | | staffFilterType: 'driver', // 人员筛选类型 |
| | | selectedDiseases: [], // 已选择的病情列表(确保初始化为空数组) |
| | | selectedStaff: [], // 已选择的人员列表(确保初始化为空数组) |
| | | taskForm: { |
| | | transferTime: '', |
| | | patient: { |
| | |
| | | if (options.id) { |
| | | this.taskId = options.id |
| | | this.loadTaskDetail() |
| | | this.loadDeptStaff() // 加载人员列表 |
| | | } else { |
| | | this.$modal.showToast('任务ID不能为空') |
| | | setTimeout(() => { |
| | |
| | | this.taskForm.patient.idCard = info.patientIdCard || '' |
| | | this.taskForm.patient.condition = info.patientCondition || '' |
| | | |
| | | // 解析病情信息 |
| | | this.parseDiseaseInfo(info.patientCondition, info.diseaseIds) |
| | | // 解析病情信息(修复:使用与创建界面一致的逻辑) |
| | | this.parseDiseaseInfo(info.patientCondition, info.diseases) |
| | | |
| | | // 转出医院信息 |
| | | this.taskForm.hospitalOut.id = info.hospitalOutId || null |
| | |
| | | console.log('设置目标地坐标:', this.taskDetail.destinationLongitude, this.taskDetail.destinationLatitude) |
| | | } |
| | | |
| | | // 设置执行人员 |
| | | if (this.taskDetail.assignees && this.taskDetail.assignees.length > 0) { |
| | | // 设置执行人员(修复:确保 assignees 不为 null) |
| | | if (this.taskDetail.assignees && Array.isArray(this.taskDetail.assignees) && this.taskDetail.assignees.length > 0) { |
| | | console.log('原始执行人员数据:', this.taskDetail.assignees) |
| | | this.selectedStaff = this.taskDetail.assignees.map(assignee => ({ |
| | | userId: assignee.userId, |
| | |
| | | } else { |
| | | console.warn('任务没有分配执行人员或assignees为空') |
| | | console.log('taskDetail.assignees:', this.taskDetail.assignees) |
| | | // 确保 selectedStaff 初始化为空数组 |
| | | this.selectedStaff = [] |
| | | } |
| | | |
| | | console.log('表单数据填充完成:', this.taskForm) |
| | |
| | | |
| | | // 归属机构选择变化 |
| | | onOrganizationChange(orgData) { |
| | | // orgData 包含:deptId, deptName, serviceOrderClass, region |
| | | // orgData 包含:deptId, deptName, serviceOrderClass, region, departureAddress, departureLongitude, departureLatitude |
| | | this.selectedOrganizationId = orgData.deptId |
| | | console.log('选中归属机构:', orgData.deptName, '部门ID:', orgData.deptId) |
| | | this.selectedRegion = orgData.region |
| | | |
| | | // 自动填充出发地信息(机构的地址和坐标) |
| | | if (orgData.departureAddress) { |
| | | this.departureAddress = orgData.departureAddress |
| | | this.departureLongitude = orgData.departureLongitude || null |
| | | this.departureLatitude = orgData.departureLatitude || null |
| | | console.log('自动填充机构出发地:', this.departureAddress, '坐标:', this.departureLongitude, this.departureLatitude) |
| | | } |
| | | |
| | | console.log('选中归属机构:', orgData.deptName, '部门ID:', orgData.deptId, '地域:', orgData.region) |
| | | }, |
| | | |
| | | // 出发地地址选择(从地图建议中选择) |
| | | onDepartureAddressSelected(data) { |
| | | // data 包含: address, longitude, latitude, location |
| | | console.log('出发地地址选择:', data) |
| | | // 组件已经通过 .sync 更新了 departureAddress, departureLongitude, departureLatitude |
| | | }, |
| | | |
| | | // 出发地GPS定位成功 |
| | | onDepartureLocationSuccess(data) { |
| | | // data 包含: address, longitude, latitude |
| | | console.log('出发地GPS定位成功:', data) |
| | | // 组件已经通过 .sync 更新了 departureAddress, departureLongitude, departureLatitude |
| | | }, |
| | | |
| | | // 转出医院变化 |
| | |
| | | // 组件已经通过 v-model 更新了 selectedDiseases |
| | | }, |
| | | |
| | | // 加载当前用户所在分公司的所有人员 |
| | | loadDeptStaff() { |
| | | console.log('开始加载人员列表') |
| | | // 人员变化 |
| | | onStaffChange(staff) { |
| | | console.log('选中人员变化:', staff) |
| | | // 组件已经通过 v-model 更新了 selectedStaff |
| | | }, |
| | | |
| | | // 解析病情信息(从字符串解析出ICD-10疾病列表)- 修复:与创建界面保持一致 |
| | | parseDiseaseInfo(conditionText, diseases) { |
| | | console.log('========== 开始解析病情信息 ==========') |
| | | console.log('conditionText:', conditionText) |
| | | console.log('diseases数组:', diseases) |
| | | |
| | | listBranchUsers().then(response => { |
| | | console.log('人员列表API响应:', response) |
| | | const userList = response.data || [] |
| | | console.log('解析出的用户列表:', userList, '数量:', userList.length) |
| | | |
| | | this.allStaffList = userList.map(user => ({ |
| | | userId: user.userId, |
| | | nickName: user.nickName, |
| | | phonenumber: user.phonenumber, |
| | | deptName: user.dept?.deptName || '', |
| | | postName: user.posts && user.posts.length > 0 ? user.posts[0].postName : '', |
| | | roleName: user.roles && user.roles.length > 0 ? user.roles[0].roleName : '', |
| | | type: this.getUserType(user) |
| | | })) |
| | | |
| | | console.log('处理后的人员列表:', this.allStaffList, '数量:', this.allStaffList.length) |
| | | |
| | | this.filterStaffList() |
| | | }).catch(error => { |
| | | console.error('加载人员列表失败:', error) |
| | | this.$modal.showToast('加载人员列表失败') |
| | | }) |
| | | }, |
| | | |
| | | // 根据用户的岗位或角色判断类型 |
| | | getUserType(user) { |
| | | const postName = user.posts && user.posts.length > 0 ? user.posts[0].postName : '' |
| | | const roleName = user.roles && user.roles.length > 0 ? user.roles[0].roleName : '' |
| | | const deptName = user.dept?.deptName || '' |
| | | |
| | | if (postName.includes('司机') || roleName.includes('司机') || deptName.includes('车队') || deptName.includes('司机')) { |
| | | return 'driver' |
| | | } |
| | | if (postName.includes('护士') || roleName.includes('护士') || deptName.includes('护士')) { |
| | | return 'nurse' |
| | | } |
| | | if (postName.includes('医生') || roleName.includes('医生') || deptName.includes('医生')) { |
| | | return 'doctor' |
| | | } |
| | | if (deptName.includes('医护')) { |
| | | return 'doctor' |
| | | } |
| | | |
| | | return 'driver' |
| | | }, |
| | | |
| | | getUserTypeName(staffType) { |
| | | switch(staffType) { |
| | | case 'nurse': |
| | | return '护士' |
| | | case 'doctor': |
| | | return '医生' |
| | | case 'driver': |
| | | return '司机' |
| | | default: |
| | | return '司机' |
| | | } |
| | | }, |
| | | |
| | | // 显示人员选择弹窗 |
| | | showStaffSelector() { |
| | | this.$refs.staffPopup.open() |
| | | this.filterStaffList() |
| | | }, |
| | | |
| | | // 关闭人员选择弹窗 |
| | | closeStaffSelector() { |
| | | this.$refs.staffPopup.close() |
| | | this.staffSearchKeyword = '' |
| | | this.staffFilterType = 'driver' |
| | | }, |
| | | |
| | | // 人员搜索 |
| | | onStaffSearch(e) { |
| | | this.staffSearchKeyword = e.detail.value |
| | | this.filterStaffList() |
| | | }, |
| | | |
| | | // 筛选人员类型 |
| | | filterStaff(type) { |
| | | this.staffFilterType = type |
| | | this.filterStaffList() |
| | | }, |
| | | |
| | | // 过滤人员列表 |
| | | filterStaffList() { |
| | | console.log('开始过滤人员列表,原始数量:', this.allStaffList.length) |
| | | let list = [...this.allStaffList] |
| | | |
| | | // 按类型过滤 |
| | | if (this.staffFilterType === 'driver') { |
| | | list = list.filter(staff => staff.type === 'driver') |
| | | } else if (this.staffFilterType === 'doctor') { |
| | | list = list.filter(staff => staff.type === 'doctor') |
| | | } else if (this.staffFilterType === 'nurse') { |
| | | list = list.filter(staff => staff.type === 'nurse') |
| | | } |
| | | |
| | | console.log('按类型过滤后:', this.staffFilterType, '数量:', list.length) |
| | | |
| | | // 按关键词搜索 |
| | | if (this.staffSearchKeyword && this.staffSearchKeyword.trim() !== '') { |
| | | const keyword = this.staffSearchKeyword.trim().toLowerCase() |
| | | list = list.filter(staff => { |
| | | return staff.nickName.toLowerCase().includes(keyword) || |
| | | (staff.phonenumber && staff.phonenumber.includes(keyword)) |
| | | }) |
| | | } |
| | | |
| | | console.log('按关键词过滤后,数量:', list.length) |
| | | |
| | | this.filteredStaffList = list |
| | | }, |
| | | |
| | | // 切换人员选中状态 |
| | | toggleStaffSelection(staff) { |
| | | const index = this.selectedStaff.findIndex(s => s.userId === staff.userId) |
| | | |
| | | if (index > -1) { |
| | | // 已选中,移除 |
| | | this.selectedStaff.splice(index, 1) |
| | | } else { |
| | | // 未选中,添加 |
| | | this.selectedStaff.push(staff) |
| | | } |
| | | }, |
| | | |
| | | // 判断人员是否已选中 |
| | | isStaffSelected(userId) { |
| | | return this.selectedStaff.some(staff => staff.userId === userId) |
| | | }, |
| | | |
| | | // 确认人员选择 |
| | | confirmStaffSelection() { |
| | | if (this.selectedStaff.length === 0) { |
| | | this.$modal.showToast('请至少选择一名人员') |
| | | return |
| | | } |
| | | this.closeStaffSelector() |
| | | }, |
| | | |
| | | // 移除人员 |
| | | removeStaff(index) { |
| | | this.selectedStaff.splice(index, 1) |
| | | }, |
| | | |
| | | // 解析病情信息(从字符串解析出ICD-10疾病列表) |
| | | parseDiseaseInfo(conditionText, diseaseIds) { |
| | | if (!conditionText) { |
| | | console.log('病情文本为空,清空选中病情') |
| | | this.selectedDiseases = [] |
| | | this.taskForm.patient.otherCondition = '' |
| | | return |
| | | } |
| | | |
| | | // 解析diseaseIds(逗号分隔的字符串转为数组) |
| | | const diseaseIdArray = diseaseIds ? diseaseIds.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id)) : [] |
| | | |
| | | // 如果包含"其他:"分隔符,拆分病情和其他描述 |
| | | if (conditionText.includes('\n其他:')) { |
| | | const parts = conditionText.split('\n其他:') |
| | | const diseasePart = parts[0] |
| | | this.taskForm.patient.otherCondition = parts[1] || '' |
| | | console.log('病情部分:', diseasePart) |
| | | console.log('其他描述:', this.taskForm.patient.otherCondition) |
| | | |
| | | // 解析病情部分 |
| | | this.parseDiseaseList(diseasePart, diseaseIdArray) |
| | | this.parseDiseaseList(diseasePart, diseases) |
| | | } else { |
| | | // 没有"其他"部分,全部作为其他描述 |
| | | this.taskForm.patient.otherCondition = conditionText |
| | | this.selectedDiseases = [] |
| | | // 没有"其他"部分,尝试解析是否有疾病列表 |
| | | const hasDiseasesFormat = /([^(]+)\(([^)]+)\)/.test(conditionText) |
| | | console.log('是否包含疾病格式:', hasDiseasesFormat) |
| | | |
| | | if (hasDiseasesFormat) { |
| | | // 有疾病格式,解析疾病列表 |
| | | this.parseDiseaseList(conditionText, diseases) |
| | | this.taskForm.patient.otherCondition = '' |
| | | } else { |
| | | // 没有疾病格式,全部作为其他描述 |
| | | console.log('无疾病格式,作为其他描述') |
| | | this.taskForm.patient.otherCondition = conditionText |
| | | this.selectedDiseases = [] |
| | | } |
| | | } |
| | | |
| | | console.log('解析完成,selectedDiseases:', JSON.stringify(this.selectedDiseases)) |
| | | console.log('解析完成,otherCondition:', this.taskForm.patient.otherCondition) |
| | | console.log('========================================\n') |
| | | }, |
| | | |
| | | // 解析病情列表(格式:疾病名(编码)、疾病名(编码)) |
| | | parseDiseaseList(diseasePart, diseaseIdArray) { |
| | | parseDiseaseList(diseasePart, diseasesData) { |
| | | console.log('--- parseDiseaseList 开始 ---') |
| | | console.log('diseasePart:', diseasePart) |
| | | console.log('diseasesData:', diseasesData) |
| | | |
| | | if (!diseasePart) { |
| | | console.log('病情部分为空') |
| | | this.selectedDiseases = [] |
| | | return |
| | | } |
| | | |
| | | // 如果后端已经返回了diseases数组,直接使用 |
| | | if (diseasesData && Array.isArray(diseasesData) && diseasesData.length > 0) { |
| | | console.log('使用后端返回的diseases数组,数量:', diseasesData.length) |
| | | this.selectedDiseases = diseasesData.map(d => ({ |
| | | id: d.icdId || null, |
| | | icdCode: d.icdCode || '', |
| | | icdName: d.icdName || '' |
| | | })) |
| | | console.log('解析后的selectedDiseases:', JSON.stringify(this.selectedDiseases)) |
| | | return |
| | | } |
| | | |
| | | // 否则,从字符串解析 |
| | | console.log('从字符串解析病情') |
| | | // 匹配格式:疾病名(编码) |
| | | const regex = /([^(]+)\(([^)]+)\)/g |
| | | const diseases = [] |
| | | let match |
| | | let index = 0 |
| | | |
| | | while ((match = regex.exec(diseasePart)) !== null) { |
| | | const icdName = match[1].replace(/[、,\s]+$/, '').replace(/^[、,\s]+/, '').trim() |
| | | const icdCode = match[2].trim() |
| | | |
| | | console.log('匹配到病情 - 名称:', icdName, '编码:', icdCode) |
| | | |
| | | // 只添加病情名称不为空的项 |
| | | if (icdName) { |
| | | diseases.push({ |
| | | icdName: icdName, |
| | | id: null, // 从字符串解析时,没有ID |
| | | icdCode: icdCode, |
| | | id: diseaseIdArray && index < diseaseIdArray.length ? diseaseIdArray[index] : null |
| | | icdName: icdName |
| | | }) |
| | | index++ |
| | | } |
| | | } |
| | | |
| | | console.log('从字符串解析完成,数量:', diseases.length) |
| | | this.selectedDiseases = diseases |
| | | console.log('--- parseDiseaseList 结束 ---\n') |
| | | }, |
| | | |
| | | // 选择转出医院地址 |
| | |
| | | }, |
| | | |
| | | buildSubmitData() { |
| | | // 确保 selectedDiseases 和 selectedStaff 不为 null(修复:防止迭代 null 导致错误) |
| | | if (!this.selectedDiseases) { |
| | | this.selectedDiseases = [] |
| | | } |
| | | if (!this.selectedStaff) { |
| | | this.selectedStaff = [] |
| | | } |
| | | |
| | | // 合并病情信息:选中的ICD-10疾病 + 其他描述 |
| | | let conditionText = '' |
| | | if (this.selectedDiseases.length > 0) { |
| | |
| | | } |
| | | } |
| | | |
| | | // 构建提交数据 - 使用与 TaskCreateVO 一致的结构 |
| | | const submitData = { |
| | | taskId: this.taskId, |
| | | taskType: 'EMERGENCY_TRANSFER', |
| | | deptId: this.selectedOrganizationId, |
| | | vehicleIds: this.selectedVehicleId ? [this.selectedVehicleId] : [], |
| | | plannedStartTime: this.taskForm.transferTime, |
| | | // 出发地使用 departureAddress(如果有),否则使用转出医院地址 |
| | | |
| | | // 出发地地址和坐标(使用转出医院地址) |
| | | departureAddress: this.departureAddress || this.taskForm.hospitalOut.address, |
| | | // 目标地使用转入医院地址 |
| | | departureLongitude: this.departureLongitude || (this.addressCoordinates.hospitalOutAddress ? this.addressCoordinates.hospitalOutAddress.lng : null), |
| | | departureLatitude: this.departureLatitude || (this.addressCoordinates.hospitalOutAddress ? this.addressCoordinates.hospitalOutAddress.lat : null), |
| | | |
| | | // 目标地地址和坐标(使用转入医院地址) |
| | | destinationAddress: this.taskForm.hospitalIn.address, |
| | | destinationLongitude: this.addressCoordinates.hospitalInAddress ? this.addressCoordinates.hospitalInAddress.lng : null, |
| | | destinationLatitude: this.addressCoordinates.hospitalInAddress ? this.addressCoordinates.hospitalInAddress.lat : null, |
| | | |
| | | // 转运距离和价格(主任务字段) |
| | | distance: this.taskForm.transferDistance ? parseFloat(this.taskForm.transferDistance) : null, |
| | | price: this.taskForm.price ? parseFloat(this.taskForm.price) : null, |
| | | |
| | | // 病情ID列表(用于同步调度单的OrdICD_ID参数) |
| | | diseaseIds: this.selectedDiseases.map(d => d.id).filter(id => id !== null), |
| | | // 执行人员列表 |
| | | assignees: this.selectedStaff.map(staff => ({ |
| | | |
| | | // 执行人员列表(确保不为 null) |
| | | assignees: (this.selectedStaff && Array.isArray(this.selectedStaff)) ? this.selectedStaff.map(staff => ({ |
| | | userId: staff.userId, |
| | | userName: staff.nickName, |
| | | userType: staff.type |
| | | })), |
| | | emergencyInfo: { |
| | | patientContact: this.taskForm.patient.contact, |
| | | patientPhone: this.taskForm.patient.phone, |
| | | patientName: this.taskForm.patient.name, |
| | | patientGender: this.taskForm.patient.gender, |
| | | patientIdCard: this.taskForm.patient.idCard, |
| | | patientCondition: conditionText, // 使用合并后的病情信息 |
| | | hospitalOutName: this.taskForm.hospitalOut.name, |
| | | hospitalOutDepartment: this.taskForm.hospitalOut.department, |
| | | hospitalOutBedNumber: this.taskForm.hospitalOut.bedNumber, |
| | | hospitalOutAddress: this.taskForm.hospitalOut.address, |
| | | // 转出医院GPS坐标(保存到扩展表) |
| | | hospitalOutLongitude: this.addressCoordinates.hospitalOutAddress ? this.addressCoordinates.hospitalOutAddress.lng : null, |
| | | hospitalOutLatitude: this.addressCoordinates.hospitalOutAddress ? this.addressCoordinates.hospitalOutAddress.lat : null, |
| | | hospitalInName: this.taskForm.hospitalIn.name, |
| | | hospitalInDepartment: this.taskForm.hospitalIn.department, |
| | | hospitalInBedNumber: this.taskForm.hospitalIn.bedNumber, |
| | | hospitalInAddress: this.taskForm.hospitalIn.address, |
| | | // 转入医院GPS坐标(保存到扩展表) |
| | | hospitalInLongitude: this.addressCoordinates.hospitalInAddress ? this.addressCoordinates.hospitalInAddress.lng : null, |
| | | hospitalInLatitude: this.addressCoordinates.hospitalInAddress ? this.addressCoordinates.hospitalInAddress.lat : null, |
| | | transferDistance: this.taskForm.transferDistance ? parseFloat(this.taskForm.transferDistance) : null, |
| | | transferPrice: this.taskForm.price ? parseFloat(this.taskForm.price) : null, |
| | | })) : [], |
| | | |
| | | // 患者信息(嵌套对象) |
| | | patient: { |
| | | contact: this.taskForm.patient.contact, |
| | | phone: this.taskForm.patient.phone, |
| | | name: this.taskForm.patient.name, |
| | | gender: this.taskForm.patient.gender, |
| | | idCard: this.taskForm.patient.idCard, |
| | | condition: conditionText, // 使用合并后的病情信息 |
| | | // 病情详细信息(过滤掉空的病情名称) |
| | | diseases: this.selectedDiseases |
| | | .filter(d => d.icdName && d.icdName.trim()) |
| | |
| | | icdCode: d.icdCode, |
| | | icdName: d.icdName |
| | | })) |
| | | }, |
| | | |
| | | // 转出医院信息(嵌套对象) |
| | | hospitalOut: { |
| | | id: this.taskForm.hospitalOut.id, |
| | | name: this.taskForm.hospitalOut.name, |
| | | address: this.taskForm.hospitalOut.address, |
| | | longitude: this.addressCoordinates.hospitalOutAddress ? this.addressCoordinates.hospitalOutAddress.lng : null, |
| | | latitude: this.addressCoordinates.hospitalOutAddress ? this.addressCoordinates.hospitalOutAddress.lat : null, |
| | | department: this.taskForm.hospitalOut.department, |
| | | departmentId: this.taskForm.hospitalOut.departmentId, |
| | | bedNumber: this.taskForm.hospitalOut.bedNumber |
| | | }, |
| | | |
| | | // 转入医院信息(嵌套对象) |
| | | hospitalIn: { |
| | | id: this.taskForm.hospitalIn.id, |
| | | name: this.taskForm.hospitalIn.name, |
| | | address: this.taskForm.hospitalIn.address, |
| | | longitude: this.addressCoordinates.hospitalInAddress ? this.addressCoordinates.hospitalInAddress.lng : null, |
| | | latitude: this.addressCoordinates.hospitalInAddress ? this.addressCoordinates.hospitalInAddress.lat : null, |
| | | department: this.taskForm.hospitalIn.department, |
| | | departmentId: this.taskForm.hospitalIn.departmentId, |
| | | bedNumber: this.taskForm.hospitalIn.bedNumber |
| | | } |
| | | } |
| | | |
| | | // 出发地GPS坐标 |
| | | if (this.departureLongitude && this.departureLatitude) { |
| | | submitData.departureLongitude = this.departureLongitude |
| | | submitData.departureLatitude = this.departureLatitude |
| | | } |
| | | |
| | | // 目标地GPS坐标由后端自动获取 |
| | | |
| | | return submitData |
| | | }, |
| | |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .staff-popup-container { |
| | | height: 80vh; |
| | | background-color: white; |
| | | border-top-left-radius: 20rpx; |
| | | border-top-right-radius: 20rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .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; |
| | | } |
| | | } |
| | | |
| | | .search-bar { |
| | | padding: 20rpx 30rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | |
| | | .search-input { |
| | | height: 70rpx; |
| | | padding: 0 20rpx; |
| | | background-color: #f5f5f5; |
| | | border-radius: 10rpx; |
| | | font-size: 28rpx; |
| | | } |
| | | } |
| | | |
| | | .filter-tabs { |
| | | display: flex; |
| | | padding: 20rpx 30rpx; |
| | | gap: 20rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | |
| | | .filter-tab { |
| | | flex: 1; |
| | | height: 60rpx; |
| | | line-height: 60rpx; |
| | | text-align: center; |
| | | background-color: #f5f5f5; |
| | | border-radius: 10rpx; |
| | | font-size: 28rpx; |
| | | color: #666; |
| | | |
| | | &.active { |
| | | background-color: #007AFF; |
| | | color: white; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .staff-list-scroll { |
| | | flex: 1; |
| | | padding: 20rpx 30rpx; |
| | | |
| | | .staff-list-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 20rpx; |
| | | margin-bottom: 15rpx; |
| | | background-color: #f8f8f8; |
| | | border-radius: 10rpx; |
| | | |
| | | .staff-item-info { |
| | | flex: 1; |
| | | |
| | | .staff-item-name { |
| | | display: block; |
| | | font-size: 30rpx; |
| | | color: #333; |
| | | margin-bottom: 8rpx; |
| | | } |
| | | |
| | | .staff-item-dept { |
| | | display: block; |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | } |
| | | } |
| | | |
| | | .staff-item-check { |
| | | width: 50rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | } |
| | | |
| | | .empty-tip { |
| | | text-align: center; |
| | | padding: 100rpx 0; |
| | | color: #999; |
| | | font-size: 28rpx; |
| | | } |
| | | } |
| | | |
| | | .popup-footer { |
| | | padding: 20rpx 30rpx; |
| | | border-top: 1rpx solid #f0f0f0; |
| | | |
| | | .confirm-btn { |
| | | width: 100%; |
| | | height: 80rpx; |
| | | background-color: #007AFF; |
| | | color: white; |
| | | border-radius: 10rpx; |
| | | font-size: 32rpx; |
| | | } |
| | | } |
| | | } |
| | |
| | | selectedVehicleId: null, |
| | | selectedOrganization: '', |
| | | mapSelectorType: '', |
| | | // 扩展 addressCoordinates 支持多种键名 |
| | | addressCoordinates: { |
| | | start: null, |
| | | end: null, |
| | | startAddress: null, |
| | | endAddress: null |
| | | }, |
| | | taskForm: { |
| | | serviceTime: '', |
| | | passenger: { |
| | |
| | | |
| | | // 设置地址坐标(使用mixin中的方法) |
| | | if (this.taskDetail.departureLongitude && this.taskDetail.departureLatitude) { |
| | | this.setStartLocation({ |
| | | const startLocation = { |
| | | lng: this.taskDetail.departureLongitude, |
| | | lat: this.taskDetail.departureLatitude |
| | | }) |
| | | } |
| | | this.setStartLocation(startLocation) |
| | | // 同时保存到 startAddress 键 |
| | | this.addressCoordinates.startAddress = startLocation |
| | | } |
| | | if (this.taskDetail.destinationLongitude && this.taskDetail.destinationLatitude) { |
| | | this.setEndLocation({ |
| | | const endLocation = { |
| | | lng: this.taskDetail.destinationLongitude, |
| | | lat: this.taskDetail.destinationLatitude |
| | | }) |
| | | } |
| | | this.setEndLocation(endLocation) |
| | | // 同时保存到 endAddress 键 |
| | | this.addressCoordinates.endAddress = endLocation |
| | | } |
| | | }).catch(error => { |
| | | console.error('加载任务详情失败:', error) |
| | |
| | | |
| | | if (this.mapSelectorType === 'startAddress') { |
| | | this.taskForm.startAddress = formattedAddress |
| | | this.setLocationByAddress('start', address) |
| | | const location = this.setLocationByAddress('start', address) |
| | | // 同时保存到 startAddress 键 |
| | | this.addressCoordinates.startAddress = location |
| | | } else if (this.mapSelectorType === 'endAddress') { |
| | | this.taskForm.endAddress = formattedAddress |
| | | this.setLocationByAddress('end', address) |
| | | const location = this.setLocationByAddress('end', address) |
| | | // 同时保存到 endAddress 键 |
| | | this.addressCoordinates.endAddress = location |
| | | } |
| | | |
| | | // 监听mixin中的距离计算完成事件 |
| | |
| | | }, |
| | | |
| | | buildSubmitData() { |
| | | // 构建提交数据 - 使用与 TaskCreateVO 一致的结构 |
| | | const submitData = { |
| | | taskId: this.taskId, |
| | | taskType: 'WELFARE', |
| | | vehicleIds: this.selectedVehicleId ? [this.selectedVehicleId] : [], |
| | | plannedStartTime: this.taskForm.serviceTime, |
| | | welfareInfo: { |
| | | passengerContact: this.taskForm.passenger.contact, |
| | | passengerPhone: this.taskForm.passenger.phone, |
| | | pickupAddress: this.taskForm.startAddress, |
| | | destinationAddress: this.taskForm.endAddress, |
| | | serviceDistance: this.taskForm.distance ? parseFloat(this.taskForm.distance) : null, |
| | | servicePrice: this.taskForm.price ? parseFloat(this.taskForm.price) : null |
| | | |
| | | // 出发地地址和坐标 |
| | | departureAddress: this.taskForm.startAddress, |
| | | departureLongitude: this.addressCoordinates.startAddress ? this.addressCoordinates.startAddress.lng : null, |
| | | departureLatitude: this.addressCoordinates.startAddress ? this.addressCoordinates.startAddress.lat : null, |
| | | |
| | | // 目标地地址和坐标 |
| | | destinationAddress: this.taskForm.endAddress, |
| | | destinationLongitude: this.addressCoordinates.endAddress ? this.addressCoordinates.endAddress.lng : null, |
| | | destinationLatitude: this.addressCoordinates.endAddress ? this.addressCoordinates.endAddress.lat : null, |
| | | |
| | | // 距离和价格(主任务字段) |
| | | distance: this.taskForm.distance ? parseFloat(this.taskForm.distance) : null, |
| | | price: this.taskForm.price ? parseFloat(this.taskForm.price) : null, |
| | | |
| | | // 乘客信息(嵌套对象) |
| | | passenger: { |
| | | contact: this.taskForm.passenger.contact, |
| | | phone: this.taskForm.passenger.phone |
| | | } |
| | | } |
| | | |
| | | 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 |
| | |
| | | taskId: null, |
| | | taskDetail: null, |
| | | mapSelectorType: '', |
| | | // 扩展 addressCoordinates 支持多种键名 |
| | | addressCoordinates: { |
| | | start: null, |
| | | end: null, |
| | | startLocation: null, |
| | | endLocation: null |
| | | }, |
| | | taskForm: { |
| | | taskDescription: '', |
| | | taskType: '', |
| | |
| | | |
| | | // 设置地址坐标(使用mixin中的方法) |
| | | if (this.taskDetail.departureLongitude && this.taskDetail.departureLatitude) { |
| | | this.setStartLocation({ |
| | | const startLocation = { |
| | | lng: this.taskDetail.departureLongitude, |
| | | lat: this.taskDetail.departureLatitude |
| | | }) |
| | | } |
| | | this.setStartLocation(startLocation) |
| | | // 同时保存到 startLocation 键 |
| | | this.addressCoordinates.startLocation = startLocation |
| | | } |
| | | if (this.taskDetail.destinationLongitude && this.taskDetail.destinationLatitude) { |
| | | this.setEndLocation({ |
| | | const endLocation = { |
| | | lng: this.taskDetail.destinationLongitude, |
| | | lat: this.taskDetail.destinationLatitude |
| | | }) |
| | | } |
| | | this.setEndLocation(endLocation) |
| | | // 同时保存到 endLocation 键 |
| | | this.addressCoordinates.endLocation = endLocation |
| | | } |
| | | }).catch(error => { |
| | | console.error('加载任务详情失败:', error) |
| | |
| | | |
| | | if (this.mapSelectorType === 'startLocation') { |
| | | this.taskForm.startLocation = formattedAddress |
| | | this.setLocationByAddress('start', address) |
| | | const location = this.setLocationByAddress('start', address) |
| | | // 同时保存到 startLocation 键 |
| | | this.addressCoordinates.startLocation = location |
| | | } else if (this.mapSelectorType === 'endLocation') { |
| | | this.taskForm.endLocation = formattedAddress |
| | | this.setLocationByAddress('end', address) |
| | | const location = this.setLocationByAddress('end', address) |
| | | // 同时保存到 endLocation 键 |
| | | this.addressCoordinates.endLocation = location |
| | | } |
| | | |
| | | // 监听mixin中的距离计算完成事件 |
| | |
| | | }, |
| | | |
| | | buildSubmitData() { |
| | | // 构建提交数据 - 使用与 TaskCreateVO 一致的结构 |
| | | const submitData = { |
| | | taskId: this.taskId, |
| | | taskDescription: this.taskForm.taskDescription, |
| | |
| | | vehicleIds: this.taskForm.vehicleId ? [this.taskForm.vehicleId] : [], |
| | | plannedStartTime: this.taskForm.plannedStartTime, |
| | | plannedEndTime: this.taskForm.plannedEndTime, |
| | | |
| | | // 出发地地址和坐标 |
| | | departureAddress: this.taskForm.startLocation, |
| | | departureLongitude: this.addressCoordinates.startLocation ? this.addressCoordinates.startLocation.lng : null, |
| | | departureLatitude: this.addressCoordinates.startLocation ? this.addressCoordinates.startLocation.lat : null, |
| | | |
| | | // 目标地地址和坐标 |
| | | destinationAddress: this.taskForm.endLocation, |
| | | estimatedDistance: this.taskForm.distance ? parseFloat(this.taskForm.distance) : null, |
| | | destinationLongitude: this.addressCoordinates.endLocation ? this.addressCoordinates.endLocation.lng : null, |
| | | destinationLatitude: this.addressCoordinates.endLocation ? this.addressCoordinates.endLocation.lat : null, |
| | | |
| | | // 距离(主任务字段) |
| | | distance: 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 |
| | |
| | | // 通信成功且业务成功 |
| | | if ("SUCCESS".equals(returnCode) && "SUCCESS".equals(resultCode)) { |
| | | // 查询订单和交易(使用业务订单号查询) |
| | | PaymentOrder order = paymentOrderMapper.selectByBizOrderIdAndChannel(outTradeNo, "WECHAT"); |
| | | PaymentOrder order = paymentOrderMapper.selectById(Long.parseLong(outTradeNo)); |
| | | if (order == null) { |
| | | log.error("订单不存在: {}", outTradeNo); |
| | | updateNotifyLog(notifyLog.getId(), false, "订单不存在"); |
| | |
| | | // 交易成功 |
| | | if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) { |
| | | // 查询订单和交易(使用业务订单号查询) |
| | | PaymentOrder order = paymentOrderMapper.selectByBizOrderIdAndChannel(outTradeNo, "ALIPAY"); |
| | | PaymentOrder order = paymentOrderMapper.selectById(Long.parseLong(outTradeNo)); |
| | | if (order == null) { |
| | | log.error("订单不存在: {}", outTradeNo); |
| | | updateNotifyLog(notifyLog.getId(), false, "订单不存在"); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 处理第三方支付宝回调 |
| | | * 第三方支付宝通过GET方式回调,参数通过URL传递 |
| | | * |
| | | * 参数格式: |
| | | * method: 方法名 |
| | | * APPID: 应用ID |
| | | * PaidMoneyType: 支付类型 |
| | | * PaidMoney: 支付金额(分) |
| | | * PaidRemarks: 支付备注(包含transaction_id) |
| | | * UnixTime: 时间戳 |
| | | * Sign: 签名 |
| | | */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void processAlipayThirdPartyNotify(Map<String, String> params) { |
| | | String method = params.get("method"); // 方法名 |
| | | String appId = params.get("APPID"); // 应用ID |
| | | String paidMoneyType = params.get("PaidMoneyType"); // 支付类型 |
| | | String paidMoney = params.get("PaidMoney"); // 支付金额(分) |
| | | String paidRemarks = params.get("PaidRemarks"); // 支付备注(可能包含订单号和交易号) |
| | | String unixTime = params.get("UnixTime"); // 时间戳 |
| | | String sign = params.get("Sign"); // 签名 |
| | | |
| | | log.info("处理第三方支付宝回调,method: {}, APPID: {}, PaidMoneyType: {}, PaidMoney: {}, PaidRemarks: {}, UnixTime: {}, Sign: {}", |
| | | method, appId, paidMoneyType, paidMoney, paidRemarks, unixTime, sign); |
| | | log.info("第三方支付宝回调完整参数: {}", params); |
| | | |
| | | // 从PaidRemarks中提取订单号(out_trade_no) |
| | | // PaidRemarks格式可能是: "订单号+交易号" 或其他格式,需要解析 |
| | | String outTradeNo = extractOutTradeNo(paidRemarks); |
| | | String transactionId = extractTransactionId(paidRemarks); |
| | | |
| | | if (outTradeNo == null || outTradeNo.isEmpty()) { |
| | | log.error("无法从PaidRemarks中提取订单号: {}", paidRemarks); |
| | | return; |
| | | } |
| | | |
| | | log.info("解析出订单号: {}, 交易号: {}", outTradeNo, transactionId); |
| | | |
| | | // 检查幂等性(使用订单号或签名) |
| | | String notifyId = transactionId != null ? transactionId : sign; |
| | | if (isNotifyProcessed("ALIPAY_THIRD_PARTY", notifyId)) { |
| | | log.info("第三方支付宝回调已处理过,标识: {}", notifyId); |
| | | return; |
| | | } |
| | | |
| | | // 记录回调日志 |
| | | NotifyLog notifyLog = saveNotifyLog("ALIPAY_THIRD_PARTY", notifyId, JSON.toJSONString(params), true); |
| | | |
| | | try { |
| | | // 第三方支付宝成功的判断条件(根据实际情况调整) |
| | | // 通常只要能收到回调就表示支付成功 |
| | | if (paidMoney != null && Integer.parseInt(paidMoney) > 0) { |
| | | // 查询订单(使用商户订单号,这里的outTradeNo实际是我们的订单ID) |
| | | PaymentOrder order = paymentOrderMapper.selectById(Long.valueOf(outTradeNo)); |
| | | if (order == null) { |
| | | log.error("订单不存在: {}", outTradeNo); |
| | | updateNotifyLog(notifyLog.getId(), false, "订单不存在"); |
| | | return; |
| | | } |
| | | |
| | | // 验证金额是否匹配 |
| | | if (!order.getAmount().equals(Integer.parseInt(paidMoney))) { |
| | | log.error("订单金额不匹配,订单金额: {}, 回调金额: {}", order.getAmount(), paidMoney); |
| | | updateNotifyLog(notifyLog.getId(), false, "金额不匹配"); |
| | | return; |
| | | } |
| | | |
| | | // 查询待支付交易 |
| | | PaymentTransaction transaction = paymentTransactionMapper.selectPendingByOrderId(order.getId()); |
| | | if (transaction == null) { |
| | | // 如果没有待支付交易,尝试查询最新交易 |
| | | transaction = paymentTransactionMapper.selectLatestByOrderId(order.getId()); |
| | | if (transaction == null || "SUCCEEDED".equals(transaction.getStatus())) { |
| | | log.warn("交易已完成或不存在,订单ID: {}", order.getId()); |
| | | updateNotifyLog(notifyLog.getId(), true, "交易已完成"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // 更新交易状态 |
| | | transaction.setStatus("SUCCEEDED"); |
| | | if (transactionId != null) { |
| | | transaction.setChannelTradeNo(transactionId); |
| | | } |
| | | transaction.setPaidAt(LocalDateTime.now()); |
| | | transaction.setResponseSnapshot(JSON.toJSONString(params)); |
| | | paymentTransactionMapper.update(transaction); |
| | | |
| | | // 更新订单状态 |
| | | order.setStatus("SUCCEEDED"); |
| | | if (transactionId != null) { |
| | | order.setChannelTradeNo(transactionId); |
| | | } |
| | | order.setPaidAt(LocalDateTime.now()); |
| | | order.setLatestTransactionId(transaction.getId()); |
| | | paymentOrderMapper.update(order); |
| | | |
| | | // 更新通知日志 |
| | | notifyLog.setOrderId(order.getId()); |
| | | notifyLog.setTransactionId(transaction.getId()); |
| | | updateNotifyLog(notifyLog.getId(), true, "处理成功"); |
| | | |
| | | // 触发业务回调 |
| | | bizCallbackService.triggerCallback(order, transaction); |
| | | |
| | | log.info("第三方支付宝回调处理成功,订单ID: {}, 交易ID: {}", order.getId(), transaction.getId()); |
| | | } else { |
| | | log.warn("第三方支付宝支付金额异常: {}", paidMoney); |
| | | updateNotifyLog(notifyLog.getId(), true, "金额异常: " + paidMoney); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("处理第三方支付宝回调异常", e); |
| | | updateNotifyLog(notifyLog.getId(), false, "处理异常: " + e.getMessage()); |
| | | throw e; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从PaidRemarks中提取订单号 |
| | | * PaidRemarks可能包含订单号和交易号,需要解析 |
| | | */ |
| | | private String extractOutTradeNo(String paidRemarks) { |
| | | if (paidRemarks == null || paidRemarks.isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | // 假设PaidRemarks格式: "订单号+交易号" 或 "订单号" |
| | | // 根据实际格式调整解析逻辑 |
| | | // 这里假设订单号在最前面,可能用某个分隔符分隔 |
| | | |
| | | // 尝试提取纯数字作为订单ID |
| | | String[] parts = paidRemarks.split("[^0-9]+"); |
| | | for (String part : parts) { |
| | | if (part.length() > 10) { // 订单ID通常是长整型,长度较长 |
| | | return part; |
| | | } |
| | | } |
| | | |
| | | // 如果没有找到,返回整个字符串的数字部分 |
| | | return paidRemarks.replaceAll("[^0-9]", ""); |
| | | } |
| | | |
| | | /** |
| | | * 从PaidRemarks中提取交易号 |
| | | */ |
| | | private String extractTransactionId(String paidRemarks) { |
| | | if (paidRemarks == null || paidRemarks.isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | // 如果PaidRemarks包含支付宝交易号(通常以2开头的28位数字) |
| | | // 这里简单返回null,可以根据实际格式调整 |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 处理第三方微信回调 |
| | | * 第三方微信通过GET方式回调,参数通过URL传递 |
| | | * |
| | | * 参数格式: |
| | | * method: 方法名 |
| | | * APPID: 应用ID |
| | | * PaidMoneyType: 支付类型 |
| | | * PaidMoney: 支付金额(分) |
| | | * PaidRemarks: 支付备注(包含transaction_id) |
| | | * UnixTime: 时间戳 |
| | | */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void processWechatThirdPartyNotify(Map<String, String> params) { |
| | | String method = params.get("method"); // 方法名 |
| | | String appId = params.get("APPID"); // 应用ID |
| | | String paidMoneyType = params.get("PaidMoneyType"); // 支付类型 |
| | | String paidMoney = params.get("PaidMoney"); // 支付金额(分) |
| | | String paidRemarks = params.get("PaidRemarks"); // 支付备注(可能包含订单号和交易号) |
| | | String unixTime = params.get("UnixTime"); // 时间戳 |
| | | |
| | | log.info("处理第三方微信回调,method: {}, APPID: {}, PaidMoneyType: {}, PaidMoney: {}, PaidRemarks: {}, UnixTime: {}", |
| | | method, appId, paidMoneyType, paidMoney, paidRemarks, unixTime); |
| | | log.info("第三方微信回调完整参数: {}", params); |
| | | |
| | | // 从PaidRemarks中提取订单号(out_trade_no) |
| | | String outTradeNo = extractOutTradeNo(paidRemarks); |
| | | String transactionId = extractTransactionId(paidRemarks); |
| | | |
| | | if (outTradeNo == null || outTradeNo.isEmpty()) { |
| | | log.error("无法从PaidRemarks中提取订单号: {}", paidRemarks); |
| | | return; |
| | | } |
| | | |
| | | log.info("解析出订单号: {}, 交易号: {}", outTradeNo, transactionId); |
| | | |
| | | // 检查幂等性(使用订单号或时间戳) |
| | | String notifyId = transactionId != null ? transactionId : (outTradeNo + "_" + unixTime); |
| | | if (isNotifyProcessed("WECHAT_THIRD_PARTY", notifyId)) { |
| | | log.info("第三方微信回调已处理过,标识: {}", notifyId); |
| | | return; |
| | | } |
| | | |
| | | // 记录回调日志 |
| | | NotifyLog notifyLog = saveNotifyLog("WECHAT_THIRD_PARTY", notifyId, JSON.toJSONString(params), true); |
| | | |
| | | try { |
| | | // 第三方微信成功的判断条件(通常只要能收到回调就表示支付成功) |
| | | if (paidMoney != null && Integer.parseInt(paidMoney) > 0) { |
| | | // 查询订单(使用商户订单号,这里的outTradeNo实际是我们的订单ID) |
| | | PaymentOrder order = paymentOrderMapper.selectById(Long.valueOf(outTradeNo)); |
| | | if (order == null) { |
| | | log.error("订单不存在: {}", outTradeNo); |
| | | updateNotifyLog(notifyLog.getId(), false, "订单不存在"); |
| | | return; |
| | | } |
| | | |
| | | // 验证金额是否匹配 |
| | | if (!order.getAmount().equals(Integer.parseInt(paidMoney))) { |
| | | log.error("订单金额不匹配,订单金额: {}, 回调金额: {}", order.getAmount(), paidMoney); |
| | | updateNotifyLog(notifyLog.getId(), false, "金额不匹配"); |
| | | return; |
| | | } |
| | | |
| | | // 查询待支付交易 |
| | | PaymentTransaction transaction = paymentTransactionMapper.selectPendingByOrderId(order.getId()); |
| | | if (transaction == null) { |
| | | // 如果没有待支付交易,尝试查询最新交易 |
| | | transaction = paymentTransactionMapper.selectLatestByOrderId(order.getId()); |
| | | if (transaction == null || "SUCCEEDED".equals(transaction.getStatus())) { |
| | | log.warn("交易已完成或不存在,订单ID: {}", order.getId()); |
| | | updateNotifyLog(notifyLog.getId(), true, "交易已完成"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // 更新交易状态 |
| | | transaction.setStatus("SUCCEEDED"); |
| | | if (transactionId != null) { |
| | | transaction.setChannelTradeNo(transactionId); |
| | | } |
| | | transaction.setPaidAt(LocalDateTime.now()); |
| | | transaction.setResponseSnapshot(JSON.toJSONString(params)); |
| | | paymentTransactionMapper.update(transaction); |
| | | |
| | | // 更新订单状态 |
| | | order.setStatus("SUCCEEDED"); |
| | | if (transactionId != null) { |
| | | order.setChannelTradeNo(transactionId); |
| | | } |
| | | order.setPaidAt(LocalDateTime.now()); |
| | | order.setLatestTransactionId(transaction.getId()); |
| | | paymentOrderMapper.update(order); |
| | | |
| | | // 更新通知日志 |
| | | notifyLog.setOrderId(order.getId()); |
| | | notifyLog.setTransactionId(transaction.getId()); |
| | | updateNotifyLog(notifyLog.getId(), true, "处理成功"); |
| | | |
| | | // 触发业务回调 |
| | | bizCallbackService.triggerCallback(order, transaction); |
| | | |
| | | log.info("第三方微信回调处理成功,订单ID: {}, 交易ID: {}", order.getId(), transaction.getId()); |
| | | } else { |
| | | log.warn("第三方微信支付金额异常: {}", paidMoney); |
| | | updateNotifyLog(notifyLog.getId(), true, "金额异常: " + paidMoney); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("处理第三方微信回调异常", e); |
| | | updateNotifyLog(notifyLog.getId(), false, "处理异常: " + e.getMessage()); |
| | | throw e; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 检查通知是否已处理(幂等性) |
| | | */ |
| | | private boolean isNotifyProcessed(String channel, String notifyId) { |
| | |
| | | import com.ruoyi.payment.infrastructure.channel.wechat.WxPayV2Client; |
| | | import com.ruoyi.payment.infrastructure.config.AlipayConfig; |
| | | import com.ruoyi.payment.infrastructure.config.QrCodeConfig; |
| | | import com.ruoyi.payment.infrastructure.config.WechatPayConfig; |
| | | import com.ruoyi.payment.infrastructure.persistence.mapper.PaymentOrderMapper; |
| | | import com.ruoyi.payment.infrastructure.persistence.mapper.PaymentTransactionMapper; |
| | | import com.ruoyi.payment.infrastructure.util.QrCodeUtil; |
| | |
| | | @Autowired |
| | | private AlipayConfig alipayConfig; |
| | | |
| | | @Autowired |
| | | private WechatPayConfig wechatPayConfig; |
| | | |
| | | /** |
| | | * 发起微信Native支付 |
| | | */ |
| | |
| | | requestParams.put("bizOrderId", request.getBizOrderId()); |
| | | requestParams.put("amount", request.getAmount()); |
| | | requestParams.put("subject", request.getSubject()); |
| | | requestParams.put("notifyUrl",wechatPayConfig.getNotifyUrl()); |
| | | transaction.setRequestParams(JSON.toJSONString(requestParams)); |
| | | |
| | | paymentTransactionMapper.insert(transaction); |
| | |
| | | requestParams.put("bizOrderId", request.getBizOrderId()); |
| | | requestParams.put("amount", request.getAmount()); |
| | | requestParams.put("subject", request.getSubject()); |
| | | requestParams.put("notifyUrl",alipayConfig.getNotifyUrl()); |
| | | transaction.setRequestParams(JSON.toJSONString(requestParams)); |
| | | |
| | | paymentTransactionMapper.insert(transaction); |
| | |
| | | requestParams.put("amount", request.getAmount()); |
| | | requestParams.put("subject", request.getSubject()); |
| | | requestParams.put("thirdParty", true); |
| | | requestParams.put("notifyUrl",alipayConfig.getThirdParty().getDefaultNotifyUrl()); |
| | | transaction.setRequestParams(JSON.toJSONString(requestParams)); |
| | | |
| | | paymentTransactionMapper.insert(transaction); |
| | |
| | | private String callAlipayThirdPartyPrecreate(PaymentOrder order, PaymentRequest request) { |
| | | try { |
| | | // 使用AlipayConfig中配置的回调地址 |
| | | String notifyUrl = alipayConfig.getNotifyUrl(); |
| | | String notifyUrl = alipayConfig.getThirdParty().getDefaultNotifyUrl(); |
| | | String outTradeNo = String.valueOf(order.getId()); |
| | | Integer totalFee = order.getAmount(); // 单位:分 |
| | | String serviceOrdId = request.getBizOrderId(); // 业务订单ID |
| | | |
| | | |
| | | return alipayThirdPartyClient.createQrCodeUrl(notifyUrl, outTradeNo, totalFee, serviceOrdId); |
| | | } catch (Exception e) { |
| | | log.error("调用第三方支付宝接口失败", e); |
| | |
| | | throw new RuntimeException("查询交易状态失败: " + e.getMessage(), e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 轮询查询支付状态(用于回调失败的补偿机制) |
| | | * 支持微信和支付宝 |
| | | * |
| | | * @param orderId 订单ID |
| | | * @return 轮询结果(包含订单状态和交易状态) |
| | | */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Map<String, Object> pollPaymentStatus(Long orderId) { |
| | | log.info("开始轮询查询支付状态,订单ID: {}", orderId); |
| | | |
| | | Map<String, Object> result = new HashMap<>(); |
| | | |
| | | // 1. 查询订单信息 |
| | | PaymentOrder order = paymentOrderMapper.selectById(orderId); |
| | | if (order == null) { |
| | | log.error("订单不存在,订单ID: {}", orderId); |
| | | throw new RuntimeException("订单不存在"); |
| | | } |
| | | |
| | | // 如果订单已经是成功状态,直接返回 |
| | | if (OrderStatus.SUCCEEDED.getCode().equals(order.getStatus())) { |
| | | log.info("订单已成功,无需轮询,订单ID: {}", orderId); |
| | | result.put("orderStatus", order.getStatus()); |
| | | result.put("needPoll", false); |
| | | result.put("message", "订单已成功"); |
| | | return result; |
| | | } |
| | | |
| | | // 2. 查询最新交易记录 |
| | | PaymentTransaction transaction = paymentTransactionMapper.selectLatestByOrderId(orderId); |
| | | if (transaction == null) { |
| | | log.error("未找到交易记录,订单ID: {}", orderId); |
| | | throw new RuntimeException("未找到交易记录"); |
| | | } |
| | | |
| | | // 如果交易已成功,但订单状态未更新,更新订单状态 |
| | | if (TransactionStatus.SUCCEEDED.getCode().equals(transaction.getStatus())) { |
| | | log.info("交易已成功,同步订单状态,订单ID: {}", orderId); |
| | | order.setStatus(OrderStatus.SUCCEEDED.getCode()); |
| | | order.setUpdatedAt(LocalDateTime.now()); |
| | | paymentOrderMapper.update(order); |
| | | |
| | | result.put("orderStatus", OrderStatus.SUCCEEDED.getCode()); |
| | | result.put("transactionStatus", transaction.getStatus()); |
| | | result.put("needPoll", false); |
| | | result.put("message", "支付成功"); |
| | | return result; |
| | | } |
| | | |
| | | // 3. 根据支付渠道调用相应的查询接口 |
| | | String channel = order.getChannel(); |
| | | boolean paymentSuccess = false; |
| | | String tradeStatus = null; |
| | | |
| | | try { |
| | | if (PayChannel.WECHAT.getCode().equals(channel)) { |
| | | // 查询微信支付状态 |
| | | paymentSuccess = queryWechatPaymentStatus(order, transaction); |
| | | tradeStatus = paymentSuccess ? "SUCCESS" : "NOTPAY"; |
| | | result.put("paymethod","WECHAT"); |
| | | } else if (PayChannel.ALIPAY.getCode().equals(channel)) { |
| | | // 判断是否为第三方支付宝 |
| | | boolean isThirdParty = isThirdPartyAlipay(transaction); |
| | | result.put("paymethod","ALIPAY"); |
| | | if (isThirdParty) { |
| | | // 查询第三方支付宝状态 |
| | | tradeStatus = queryAlipayThirdPartyTradeStatus(orderId); |
| | | paymentSuccess = "SUCCESS".equals(tradeStatus); |
| | | } else { |
| | | // 查询官方支付宝状态 |
| | | paymentSuccess = queryAlipayPaymentStatus(order, transaction); |
| | | tradeStatus = paymentSuccess ? "TRADE_SUCCESS" : "WAIT_BUYER_PAY"; |
| | | } |
| | | } else { |
| | | log.error("不支持的支付渠道: {}", channel); |
| | | throw new RuntimeException("不支持的支付渠道"); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("查询支付状态异常,订单ID: {}", orderId, e); |
| | | result.put("orderStatus", order.getStatus()); |
| | | result.put("transactionStatus", transaction.getStatus()); |
| | | result.put("needPoll", true); |
| | | result.put("error", e.getMessage()); |
| | | result.put("message", "查询异常,请稍后重试"); |
| | | return result; |
| | | } |
| | | |
| | | // 4. 如果支付成功,更新订单和交易状态 |
| | | if (paymentSuccess) { |
| | | log.info("轮询查询发现支付成功,订单ID: {}, 交易状态: {}", orderId, tradeStatus); |
| | | |
| | | // 更新交易状态 |
| | | transaction.setStatus(TransactionStatus.SUCCEEDED.getCode()); |
| | | transaction.setPaidAt(LocalDateTime.now()); |
| | | paymentTransactionMapper.update(transaction); |
| | | |
| | | // 更新订单状态 |
| | | order.setStatus(OrderStatus.SUCCEEDED.getCode()); |
| | | order.setUpdatedAt(LocalDateTime.now()); |
| | | paymentOrderMapper.update(order); |
| | | |
| | | result.put("orderStatus", OrderStatus.SUCCEEDED.getCode()); |
| | | result.put("transactionStatus", TransactionStatus.SUCCEEDED.getCode()); |
| | | result.put("tradeStatus", tradeStatus); |
| | | result.put("needPoll", false); |
| | | result.put("message", "支付成功"); |
| | | |
| | | log.info("轮询查询处理完成,订单和交易状态已更新,订单ID: {}", orderId); |
| | | } else { |
| | | log.info("轮询查询,支付尚未完成,订单ID: {}, 交易状态: {}", orderId, tradeStatus); |
| | | result.put("orderStatus", order.getStatus()); |
| | | result.put("transactionStatus", transaction.getStatus()); |
| | | result.put("tradeStatus", tradeStatus); |
| | | result.put("needPoll", true); |
| | | result.put("message", "支付尚未完成,请继续轮询"); |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 查询微信支付状态 |
| | | */ |
| | | private boolean queryWechatPaymentStatus(PaymentOrder order, PaymentTransaction transaction) throws Exception { |
| | | log.info("查询微信支付状态,订单ID: {}", order.getId()); |
| | | |
| | | String outTradeNo = String.valueOf(order.getId()); |
| | | Map<String, String> queryResult = wxPayV2Client.queryOrder(outTradeNo); |
| | | |
| | | // 检查业务结果 |
| | | if (!"SUCCESS".equals(queryResult.get("result_code"))) { |
| | | log.warn("微信订单查询业务失败: {}", queryResult.get("err_code_des")); |
| | | return false; |
| | | } |
| | | |
| | | // 检查交易状态 |
| | | String tradeState = queryResult.get("trade_state"); |
| | | log.info("微信订单状态: {}", tradeState); |
| | | |
| | | return "SUCCESS".equals(tradeState); |
| | | } |
| | | |
| | | /** |
| | | * 查询支付宝支付状态(官方接口) |
| | | */ |
| | | private boolean queryAlipayPaymentStatus(PaymentOrder order, PaymentTransaction transaction) throws Exception { |
| | | log.info("查询支付宝支付状态,订单ID: {}", order.getId()); |
| | | |
| | | String outTradeNo = String.valueOf(order.getId()); |
| | | com.alipay.api.response.AlipayTradeQueryResponse queryResult = alipayF2FClient.queryOrder(outTradeNo); |
| | | |
| | | // 检查交易状态 |
| | | String tradeStatus = queryResult.getTradeStatus(); |
| | | log.info("支付宝订单状态: {}", tradeStatus); |
| | | log.info("支付宝订单查询结果: {}", queryResult.getBody()); |
| | | // TRADE_SUCCESS 或 TRADE_FINISHED 表示支付成功 |
| | | return "TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus); |
| | | } |
| | | |
| | | /** |
| | | * 判断是否为第三方支付宝 |
| | | */ |
| | | private boolean isThirdPartyAlipay(PaymentTransaction transaction) { |
| | | if (transaction.getRequestParams() == null) { |
| | | return false; |
| | | } |
| | | try { |
| | | Map<String, Object> params = JSON.parseObject(transaction.getRequestParams(), Map.class); |
| | | return params.containsKey("thirdParty") && Boolean.TRUE.equals(params.get("thirdParty")); |
| | | } catch (Exception e) { |
| | | log.warn("解析交易请求参数失败", e); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据交易单号查询微信支付状态 |
| | | * |
| | | * @param outTradeNo 商户订单号(交易单号) |
| | | * @return 交易状态信息 |
| | | */ |
| | | public Map<String, Object> queryWechatTradeByOutTradeNo(String outTradeNo) { |
| | | log.info("根据交易单号查询微信支付状态,商户订单号: {}", outTradeNo); |
| | | |
| | | Map<String, Object> result = new HashMap<>(); |
| | | |
| | | try { |
| | | Map<String, String> queryResult = wxPayV2Client.queryOrder(outTradeNo); |
| | | |
| | | // 检查调用是否成功 |
| | | if (!"SUCCESS".equals(queryResult.get("return_code"))) { |
| | | result.put("success", false); |
| | | result.put("message", "微信查询接口调用失败: " + queryResult.get("return_msg")); |
| | | return result; |
| | | } |
| | | |
| | | // 检查业务结果 |
| | | if (!"SUCCESS".equals(queryResult.get("result_code"))) { |
| | | result.put("success", false); |
| | | result.put("message", "微信订单查询业务失败: " + queryResult.get("err_code_des")); |
| | | result.put("errorCode", queryResult.get("err_code")); |
| | | return result; |
| | | } |
| | | |
| | | // 返回交易信息 |
| | | result.put("success", true); |
| | | result.put("tradeState", queryResult.get("trade_state")); // 交易状态 |
| | | result.put("tradeStateDesc", queryResult.get("trade_state_desc")); // 交易状态描述 |
| | | result.put("outTradeNo", queryResult.get("out_trade_no")); // 商户订单号 |
| | | result.put("transactionId", queryResult.get("transaction_id")); // 微信支付订单号 |
| | | result.put("totalFee", queryResult.get("total_fee")); // 订单金额(分) |
| | | result.put("timeEnd", queryResult.get("time_end")); // 支付完成时间 |
| | | result.put("openid", queryResult.get("openid")); // 用户标识 |
| | | |
| | | // 判断是否支付成功 |
| | | boolean isPaid = "SUCCESS".equals(queryResult.get("trade_state")); |
| | | result.put("isPaid", isPaid); |
| | | result.put("message", isPaid ? "支付成功" : "支付未完成"); |
| | | |
| | | log.info("微信交易查询成功,交易状态: {}", queryResult.get("trade_state")); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("查询微信交易状态异常,商户订单号: {}", outTradeNo, e); |
| | | result.put("success", false); |
| | | result.put("message", "查询异常: " + e.getMessage()); |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 根据交易单号查询支付宝支付状态 |
| | | * |
| | | * @param outTradeNo 商户订单号(交易单号) |
| | | * @return 交易状态信息 |
| | | */ |
| | | public Map<String, Object> queryAlipayTradeByOutTradeNo(String outTradeNo) { |
| | | log.info("根据交易单号查询支付宝支付状态,商户订单号: {}", outTradeNo); |
| | | |
| | | Map<String, Object> result = new HashMap<>(); |
| | | |
| | | try { |
| | | com.alipay.api.response.AlipayTradeQueryResponse queryResult = alipayF2FClient.queryOrder(outTradeNo); |
| | | |
| | | // 检查调用是否成功 |
| | | if (!queryResult.isSuccess()) { |
| | | result.put("success", false); |
| | | result.put("message", "支付宝查询失败: " + queryResult.getSubMsg()); |
| | | result.put("errorCode", queryResult.getSubCode()); |
| | | return result; |
| | | } |
| | | |
| | | // 返回交易信息 |
| | | result.put("success", true); |
| | | result.put("tradeStatus", queryResult.getTradeStatus()); // 交易状态 |
| | | result.put("outTradeNo", queryResult.getOutTradeNo()); // 商户订单号 |
| | | result.put("tradeNo", queryResult.getTradeNo()); // 支付宝交易号 |
| | | result.put("totalAmount", queryResult.getTotalAmount()); // 订单金额(元) |
| | | result.put("buyerPayAmount", queryResult.getBuyerPayAmount()); // 买家实际支付金额 |
| | | result.put("sendPayDate", queryResult.getSendPayDate()); // 交易支付时间 |
| | | result.put("buyerLogonId", queryResult.getBuyerLogonId()); // 买家支付宝账号 |
| | | |
| | | // 判断是否支付成功 |
| | | String tradeStatus = queryResult.getTradeStatus(); |
| | | boolean isPaid = "TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus); |
| | | result.put("isPaid", isPaid); |
| | | |
| | | // 状态说明 |
| | | String message; |
| | | if ("TRADE_SUCCESS".equals(tradeStatus)) { |
| | | message = "交易支付成功"; |
| | | } else if ("TRADE_FINISHED".equals(tradeStatus)) { |
| | | message = "交易结束,不可退款"; |
| | | } else if ("WAIT_BUYER_PAY".equals(tradeStatus)) { |
| | | message = "交易创建,等待买家付款"; |
| | | } else if ("TRADE_CLOSED".equals(tradeStatus)) { |
| | | message = "未付款交易超时关闭,或支付完成后全额退款"; |
| | | } else { |
| | | message = "未知状态: " + tradeStatus; |
| | | } |
| | | result.put("message", message); |
| | | |
| | | log.info("支付宝交易查询成功,交易状态: {}", tradeStatus); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("查询支付宝交易状态异常,商户订单号: {}", outTradeNo, e); |
| | | result.put("success", false); |
| | | result.put("message", "查询异常: " + e.getMessage()); |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | } |
| | |
| | | /** 第三方接口URL */ |
| | | private String url = "https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php"; |
| | | /** 默认回调地址 */ |
| | | private String defaultNotifyUrl = "https://dsp.966120.com.cn/alipay/pay_notify"; |
| | | private String defaultNotifyUrl = "https://api.966120.com.cn/alipay/DspNotifyUrl.php"; |
| | | /** 超时时间(毫秒) */ |
| | | private int timeout = 30000; |
| | | } |
| New file |
| | |
| | | package com.ruoyi.payment.infrastructure.config; |
| | | |
| | | import lombok.Data; |
| | | |
| | | import java.io.Serializable; |
| | | |
| | | @Data |
| | | public class ThirdPartyConfig implements Serializable { |
| | | /** 是否启用 */ |
| | | private boolean enabled = false; |
| | | /** 第三方接口URL */ |
| | | private String url = "https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php"; |
| | | /** 默认回调地址 */ |
| | | private String defaultNotifyUrl = "https://api.966120.com.cn/alipay/DspNotifyUrl.php"; |
| | | /** 超时时间(毫秒) */ |
| | | private int timeout = 30000; |
| | | } |
| | |
| | | |
| | | /** 签名类型 */ |
| | | private String signType = "MD5"; |
| | | |
| | | /** |
| | | * 第三方支付配置 |
| | | */ |
| | | private ThirdPartyConfig thirdParty = new ThirdPartyConfig(); |
| | | |
| | | } |
| | |
| | | /** |
| | | * 发起微信Native支付 |
| | | */ |
| | | @Anonymous() |
| | | @PostMapping("/wechat/native") |
| | | public AjaxResult createWechatNativePayment(@Validated @RequestBody PaymentRequest request) { |
| | | try { |
| | |
| | | /** |
| | | * 发起支付宝当面付 |
| | | */ |
| | | @Anonymous() |
| | | @PostMapping("/alipay/precreate") |
| | | public AjaxResult createAlipayPrecreate(@Validated @RequestBody PaymentRequest request) { |
| | | try { |
| | |
| | | /** |
| | | * 发起支付宝当面付(第三方接口) |
| | | */ |
| | | @Anonymous() |
| | | @PostMapping("/alipay/thirdparty/precreate") |
| | | public AjaxResult createAlipayThirdPartyPrecreate(@Validated @RequestBody PaymentRequest request) { |
| | | try { |
| | |
| | | /** |
| | | * 查询订单 |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/orders/{orderId}") |
| | | public AjaxResult getOrder(@PathVariable Long orderId) { |
| | | try { |
| | |
| | | /** |
| | | * 查询最新交易 |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/orders/{orderId}/transactions/latest") |
| | | public AjaxResult getLatestTransaction(@PathVariable Long orderId) { |
| | | try { |
| | |
| | | /** |
| | | * 查询支付宝第三方交易状态 |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/alipay/thirdparty/query/{orderId}") |
| | | public AjaxResult queryAlipayThirdPartyTradeStatus(@PathVariable Long orderId) { |
| | | try { |
| | |
| | | return AjaxResult.error("查询失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 轮询查询支付状态(用于回调失败的补偿机制) |
| | | * <p> |
| | | * 这个接口用于在支付回调未成功时,主动轮询查询支付状态。 |
| | | * 支持微信和支付宝(包括第三方支付宝)。 |
| | | * <p> |
| | | * 返回结果包含: |
| | | * - orderStatus: 订单状态 |
| | | * - transactionStatus: 交易状态 |
| | | * - tradeStatus: 第三方交易状态 |
| | | * - needPoll: 是否需要继续轮询 |
| | | * - message: 提示信息 |
| | | * <p> |
| | | * 建议轮询策略: |
| | | * 1. 首次轮询:立即轮询 |
| | | * 2. 后续轮询:间隔3-5秒 |
| | | * 3. 最多轮询20次,约100秒后停止 |
| | | * 4. 如果 needPoll=false,表示支付已完成或确认失败,无需继续轮询 |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/poll/{orderId}") |
| | | public AjaxResult pollPaymentStatus(@PathVariable Long orderId) { |
| | | try { |
| | | java.util.Map<String, Object> result = paymentService.pollPaymentStatus(orderId); |
| | | return AjaxResult.success(result); |
| | | } catch (Exception e) { |
| | | log.error("轮询查询支付状态失败", e); |
| | | return AjaxResult.error("查询失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据交易单号查询微信支付状态 |
| | | * <p> |
| | | * 该接口用于通过商户订单号(交易单号)直接查询微信支付状态。 |
| | | * <p> |
| | | * 返回结果包含: |
| | | * - success: 查询是否成功 |
| | | * - isPaid: 是否已支付 |
| | | * - tradeState: 交易状态(SUCCESS-支付成功,NOTPAY-未支付,CLOSED-已关闭等) |
| | | * - transactionId: 微信支付订单号 |
| | | * - totalFee: 订单金额(分) |
| | | * - timeEnd: 支付完成时间 |
| | | * - message: 提示信息 |
| | | * |
| | | * @param outTradeNo 商户订单号(交易单号) |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/wechat/query/{outTradeNo}") |
| | | public AjaxResult queryWechatTrade(@PathVariable String outTradeNo) { |
| | | try { |
| | | java.util.Map<String, Object> result = paymentService.queryWechatTradeByOutTradeNo(outTradeNo); |
| | | return AjaxResult.success(result); |
| | | } catch (Exception e) { |
| | | log.error("查询微信交易状态失败,交易单号: {}", outTradeNo, e); |
| | | return AjaxResult.error("查询失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据交易单号查询支付宝支付状态 |
| | | * <p> |
| | | * 该接口用于通过商户订单号(交易单号)直接查询支付宝支付状态。 |
| | | * <p> |
| | | * 返回结果包含: |
| | | * - success: 查询是否成功 |
| | | * - isPaid: 是否已支付 |
| | | * - tradeStatus: 交易状态(TRADE_SUCCESS-支付成功,WAIT_BUYER_PAY-等待付款,TRADE_CLOSED-已关闭等) |
| | | * - tradeNo: 支付宝交易号 |
| | | * - totalAmount: 订单金额(元) |
| | | * - sendPayDate: 交易支付时间 |
| | | * - buyerLogonId: 买家支付宝账号 |
| | | * - message: 提示信息 |
| | | * |
| | | * @param outTradeNo 商户订单号(交易单号) |
| | | */ |
| | | @Anonymous() |
| | | @GetMapping("/alipay/query/{outTradeNo}") |
| | | public AjaxResult queryAlipayTrade(@PathVariable String outTradeNo) { |
| | | try { |
| | | java.util.Map<String, Object> result = paymentService.queryAlipayTradeByOutTradeNo(outTradeNo); |
| | | return AjaxResult.success(result); |
| | | } catch (Exception e) { |
| | | log.error("查询支付宝交易状态失败,交易单号: {}", outTradeNo, e); |
| | | return AjaxResult.error("查询失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | |
| | | * 微信支付回调 |
| | | */ |
| | | @Anonymous |
| | | @PostMapping("/wechat") |
| | | @PostMapping("/wechat/notify") |
| | | public String wechatNotify(HttpServletRequest request) { |
| | | try { |
| | | log.info("接收到微信支付回调"); |
| | |
| | | * 支付宝回调 |
| | | */ |
| | | @Anonymous |
| | | @PostMapping("/alipay") |
| | | @PostMapping("/alipay/notify") |
| | | public String alipayNotify(HttpServletRequest request) { |
| | | try { |
| | | log.info("接收到支付宝回调"); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 第三方支付宝回调(GET请求) |
| | | * <p> |
| | | * 第三方支付宝通过GET方式回调,参数通过URL传递 |
| | | * <p> |
| | | * 参数格式: |
| | | * method=xxx&APPID=xxx&PaidMoneyType=xxx&PaidMoney=xxx&PaidRemarks=xxx&UnixTime=xxx&Sign=xxx |
| | | * <p> |
| | | * 参数说明: |
| | | * - method: 方法名 |
| | | * - APPID: 应用ID |
| | | * - PaidMoneyType: 支付类型 |
| | | * - PaidMoney: 支付金额(单位:分) |
| | | * - PaidRemarks: 支付备注(包含订单号和交易号信息) |
| | | * - UnixTime: 时间戳 |
| | | * - Sign: 签名 |
| | | * <p> |
| | | * 示例: |
| | | * GET /api/pay/notify/alipay/thirdparty?method=alipay.pay&APPID=123456&PaidMoneyType=alipay&PaidMoney=100&PaidRemarks=1234567890&UnixTime=1638360000&Sign=abc123 |
| | | */ |
| | | @Anonymous |
| | | @GetMapping("/alipay/thirdparty") |
| | | public String alipayThirdPartyNotify(HttpServletRequest request) { |
| | | try { |
| | | log.info("接收到第三方支付宝回调(GET请求)"); |
| | | |
| | | // 1. 获取所有参数 |
| | | Map<String, String> params = new HashMap<>(); |
| | | Enumeration<String> parameterNames = request.getParameterNames(); |
| | | while (parameterNames.hasMoreElements()) { |
| | | String name = parameterNames.nextElement(); |
| | | params.put(name, request.getParameter(name)); |
| | | } |
| | | |
| | | log.info("第三方支付宝回调参数: {}", params); |
| | | |
| | | // 2. 第三方支付宝不需要验签,或者根据第三方平台的签名规则进行验证 |
| | | // 这里直接处理,如果需要验签可以在Service层添加 |
| | | |
| | | // 3. 处理回调(更新订单状态、触发业务回调) |
| | | paymentNotifyService.processAlipayThirdPartyNotify(params); |
| | | |
| | | // 4. 返回第三方支付宝要求的应答 |
| | | return "success"; |
| | | } catch (Exception e) { |
| | | log.error("处理第三方支付宝回调失败", e); |
| | | return "fail"; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 第三方微信回调(GET请求) |
| | | * <p> |
| | | * 第三方微信通过GET方式回调,参数通过URL传递 |
| | | * <p> |
| | | * 参数格式: |
| | | * method=xxx&APPID=xxx&PaidMoneyType=xxx&PaidMoney=xxx&PaidRemarks=xxx&UnixTime=xxx |
| | | * <p> |
| | | * 参数说明: |
| | | * - method: 方法名 |
| | | * - APPID: 应用ID |
| | | * - PaidMoneyType: 支付类型 |
| | | * - PaidMoney: 支付金额(单位:分) |
| | | * - PaidRemarks: 支付备注(包含订单号和交易号信息) |
| | | * - UnixTime: 时间戳 |
| | | * <p> |
| | | * 示例: |
| | | * GET /api/pay/notify/wechat/thirdparty?method=wechat.pay&APPID=123456&PaidMoneyType=wechat&PaidMoney=100&PaidRemarks=1234567890&UnixTime=1638360000 |
| | | */ |
| | | @Anonymous |
| | | @GetMapping("/wechat/thirdparty") |
| | | public String wechatThirdPartyNotify(HttpServletRequest request) { |
| | | try { |
| | | log.info("接收到第三方微信回调(GET请求)"); |
| | | |
| | | // 1. 获取所有参数 |
| | | Map<String, String> params = new HashMap<>(); |
| | | Enumeration<String> parameterNames = request.getParameterNames(); |
| | | while (parameterNames.hasMoreElements()) { |
| | | String name = parameterNames.nextElement(); |
| | | params.put(name, request.getParameter(name)); |
| | | } |
| | | |
| | | log.info("第三方微信回调参数: {}", params); |
| | | |
| | | // 2. 第三方微信不需要验签,或者根据第三方平台的签名规则进行验证 |
| | | // 这里直接处理,如枟需要验签可以在Service层添加 |
| | | |
| | | // 3. 处理回调(更新订单状态、触发业务回调) |
| | | paymentNotifyService.processWechatThirdPartyNotify(params); |
| | | |
| | | // 4. 返回第三方微信要求的应答 |
| | | return "success"; |
| | | } catch (Exception e) { |
| | | log.error("处理第三方微信回调失败", e); |
| | | return "fail"; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 读取请求体 |
| | | */ |
| | | private String readRequestBody(HttpServletRequest request) throws Exception { |
| | |
| | | @Log(title = "任务管理", businessType = BusinessType.UPDATE) |
| | | @PutMapping("/admin") |
| | | public AjaxResult adminEdit(@RequestBody TaskUpdateVO updateVO) { |
| | | return toAjax(sysTaskService.updateSysTask(updateVO)); |
| | | return toAjax(sysTaskService.updateSysTask(updateVO,false)); |
| | | } |
| | | |
| | | /** |
| | |
| | | @Log(title = "任务修改", businessType = BusinessType.UPDATE) |
| | | @PutMapping |
| | | public AjaxResult appEdit(@RequestBody TaskUpdateVO updateVO) { |
| | | return toAjax(sysTaskService.updateSysTask(updateVO)); |
| | | return toAjax(sysTaskService.updateSysTask(updateVO,false)); |
| | | } |
| | | |
| | | /** |
| | |
| | | package com.ruoyi.web.controller.task; |
| | | |
| | | import java.util.List; |
| | | |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | |
| | | @PostMapping("/assign/{taskId}") |
| | | public AjaxResult assignVehicle(@PathVariable("taskId") Long taskId, @RequestBody AssignVehicleRequest request) { |
| | | try { |
| | | int result = sysTaskService.assignVehicleToTask(taskId, request.getVehicleId(), request.getRemark()); |
| | | Long userId= SecurityUtils.getUserId(); |
| | | String username = SecurityUtils.getUsername(); |
| | | int result = sysTaskService.assignVehicleToTask(taskId, request.getVehicleId(), request.getRemark(), userId, username); |
| | | if (result > 0) { |
| | | return success("分配成功"); |
| | | } else { |
| | |
| | | try { |
| | | // 设置请求对象中的taskId,确保参数一致性 |
| | | request.setTaskId(taskId); |
| | | int result = sysTaskService.assignMultipleVehiclesToTask(request.getTaskId(), request.getVehicleIds(), request.getRemark()); |
| | | Long userId= SecurityUtils.getUserId(); |
| | | String username = SecurityUtils.getUsername(); |
| | | int result = sysTaskService.assignMultipleVehiclesToTask(request.getTaskId(), request.getVehicleIds(), request.getRemark(), userId, username); |
| | | if (result > 0) { |
| | | return success("批量分配成功,共分配 " + result + " 辆车"); |
| | | } else { |
| | |
| | | mchId: 1573728151 |
| | | appName: 民航医疗快线 |
| | | mchKey: Xz0ClPK3f5sCeT6SGTaha1vpVmyUFcbp |
| | | notifyUrl: https://dsp.966120.com/api/pay/notify/wechat |
| | | notifyUrl: https://dsp.966120.com.cn/api/pay/notify/wechat/notify |
| | | signType: MD5 |
| | | checkSign: false |
| | | thirdParty: |
| | | enabled: false |
| | | url: https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php |
| | | defaultNotifyUrl: https://api.966120.com.cn/weixin/DspNotifyUrl.asp |
| | | timeout: 30000 |
| | | |
| | | # 支付宝配置 |
| | | alipay: |
| | |
| | | privateKey: MIIEpgIBAAKCAQEArt273bWTEPXPjCsUYwFx7CNjhcQlm1NtbNjfeIsZ2g9sbFCQP9qpyufp6zkBv6eq+6WEztkC1KwSsuDjP5LvgY/1pmGFlr8r7cjeZI4bTeIe9jG5UaHolnzbdXUlSoInzgWRvbYXxuQZciwVpokwviW27YK9wPIzz9OTiRquL8b3YWPZLO7xK0gBMa2KfFfUXxCB8gHJtidQ+FjjYXb2WpnScKLJdKfWcGWFnyGiZOknyFR9kI8cm0cYPNHtecQId0bQ1ee7YDLD8dBPd2Pd/JBC4Wn6HuOvZOLqZvIpIj+8q0zGXjUUns6MsjNL3MUKuhKy6hQGwP5sGrPcVcwqiwIDAQABAoIBAQCTeW9iSSsRx61VUlOsN+DDPQlHHCh3OcH0ZWb6e52+2Pkg1EUDhT9jX3lZJsfBwf8iofJCnKSVhdVzRNSCnkIdq7KJsn9+phW/QYPFnE+MvKJOEZtwLDNDD2PqSHS9xM0bJHlIXNTqqR6IuoM740HXa2k+H+A2ZE2r/YzUuUqkASwAYPKYzWa1wivg4CZrvoPZ5bXvYOHoV0jZEtyUQB9dHuCz+bghbR+28vGkYwEGInLsOCe6Gl5D61F0l2qAXRQky3P0jXxIPXPFmhBYutAAUufLpryruQgL3MDDm5dhBJpwp89qwFDjc8fWVS/FFYJ0KDQOpAxAI800YHgaJ4VBAoGBAOdCcoYS49Q9Iula7gFGVXeto7QSUsP0CnQZM/tsAU6TiX3XG5pxBYoVmYSIQPylgagRTBZHD5t/LGa+I+KYMHSVAH/kndPdgTO7EiwfUCzi1DmGZVWs7XJ2zRfTmYRVdsElPy3Z8Pm802jd7mhffw/5p6y9pzNJqOjmGOUbYGrrAoGBAMGS1CGHkK4nD2BsIWJoKW2Lxph3Bq5hN1iaE8ZjOAvFT9drNqfwRa6BVGyfYEXPZTvUT0FUNMdukcTCM6O3FRU8EABJIcVue2QA+BvkfwPEU3JxMA8DrWHO7IcgtG9wjbxretDsf+SZmkQgK0ZUPod5ZZSycFxM3/GiXDQjGhbhAoGBAL/Y/+j6AscvcKbmKEwmbQC7q/LWwJKPAZ0Oy3DoSK1G9+jNarjUyiOjh5fK8R6mrskekGBq0yfMeKlDU8HHP2t3sNJodgYs2+JubsTrtTeHdUfDlo1cyB8NL1d00wZVYA8bNy5yftavLzLv6bfsgRxfoBpNu0dw9A9B06U88N/BAoGBAKCN/nEJFlG8iB570Xzj1GjOJJzVLK96ZwOQWJKWPShWMhEFFkJZIhLJppKp5ppAmUD0qgAPre80oKdIRLin5E7GkKcMAXzWVHXv79qCvW8MagJkK25oqGiVzs2NrNs5yfXcV/PuFW4wkSmsXPhqa6rGYCDjmBqWkLDE8CE2dC9BAoGBAJ2+QfAAvB27mr48+vY4HxZyPRrCBA1YkokraWQ0IlfD0MKsFw0Af4Iu3oy7V6NlE2GwL/AcObyHeGZt7DLbViAQOgmb9BpUrjUZ4bXSBuGPfRe11HCu6j6W67qa76TAoy3A0Dfm0OE/m9r4H99NaLzeBm1KluySKkfYXoqyueQw |
| | | alipayPublicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl8BjXYaudmGT+sEos0AXEUrKl6+wyw6++hBoJDdY7P+P7poa34GN4YSkkavTA/HplmRM1wTLcY+NqhxhLpNrcgy08AbC/GGcLM2wxQGFa+L+DQLz34uBAShXDK8yN6O49UdbbJ2RRaJSAb+nW4ZVCPMGtMu4S3lXTymQgizM1IYo9L92U5QPRzZRSP8+AmQPzwofRqEgvkO02s66xU2G5AAdkVg5BQm34eM0Io2CmcWF9jSoWQTJdyd7tw3oec9NqD7x3CfcsN3NAJOQLz4+bWWqDWelyviyAr2reeH6AuBVjaWwAvAJx3yuLevKMXTzPC95Ja7w4XYSB9Fg2+aKmwIDAQAB |
| | | serverUrl: https://openapi.alipay.com/gateway.do |
| | | notifyUrl: https://dsp.966120.com/api/pay/notify/alipay |
| | | notifyUrl: https://dsp.966120.com.cn/api/pay/notify/alipay/notify |
| | | signType: RSA2 |
| | | checkSign: false |
| | | # 支付方式: OFFICIAL(官方支付宝) 或 THIRD_PARTY(第三方支付宝) |
| | |
| | | thirdParty: |
| | | enabled: true |
| | | url: https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php |
| | | defaultNotifyUrl: https://dsp.966120.com.cn/alipay/pay_notify |
| | | # defaultNotifyUrl: https://api.966120.com.cn/alipay/DspNotifyUrl.php |
| | | defaultNotifyUrl: https://dsp.966120.com.cn/api/pay/notify/alipay/notify |
| | | timeout: 30000 |
| | | |
| | | # 业务回调配置 |
| | |
| | | * cron表达式: 0 0/10 * * * ? (每10分钟执行一次) |
| | | */ |
| | | public void syncAdditionalFeeToLegacy() { |
| | | log.info("开始执行附加费用同步定时任务(新系统 -> 旧系统)"); |
| | | log.info("开始执行新系统附加费用同步定时任务(新系统 -> 旧系统)"); |
| | | try { |
| | | int successCount = additionalFeeSyncService.batchSyncAdditionalFeeToLegacy(); |
| | | log.info("附加费用同步完成,成功同步: {} 条记录", successCount); |
| | |
| | | * cron表达式: 0 0/15 * * * ? (每15分钟执行一次) |
| | | */ |
| | | public void syncAdditionalFeeFromLegacy() { |
| | | log.info("开始执行附加费用同步定时任务(旧系统 -> 新系统)"); |
| | | log.info("开始执行旧系统附加费用同步定时任务(旧系统 -> 新系统)"); |
| | | try { |
| | | // 同步最近24小时内的记录 |
| | | int successCount = additionalFeeSyncService.batchSyncAdditionalFeeFromLegacy(24); |
| | | log.info("附加费用同步完成,成功同步: {} 条记录", successCount); |
| | | int successCount = additionalFeeSyncService.batchSyncAdditionalFeeFromLegacy(72); |
| | | log.info("旧系统附加费用同步完成,成功同步: {} 条记录", successCount); |
| | | } catch (Exception e) { |
| | | log.error("附加费用同步异常", e); |
| | | } |
| | |
| | | package com.ruoyi.system.domain.vo; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | /** |
| | | * 任务更新对象 |
| | | * 任务更新对象(继承自TaskCreateVO) |
| | | * |
| | | * @author ruoyi |
| | | * @date 2024-01-15 |
| | | */ |
| | | public class TaskUpdateVO { |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | public class TaskUpdateVO extends TaskCreateVO { |
| | | |
| | | /** 任务ID */ |
| | | /** 任务ID(必填,用于更新指定任务) */ |
| | | private Long taskId; |
| | | |
| | | /** 任务描述 */ |
| | | private String taskDescription; |
| | | |
| | | /** 出发地址 */ |
| | | private String departureAddress; |
| | | |
| | | /** 目的地址 */ |
| | | private String destinationAddress; |
| | | |
| | | /** 计划开始时间 */ |
| | | |
| | | /** 更新时间 */ |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | private Date plannedStartTime; |
| | | |
| | | /** 计划结束时间 */ |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | private Date plannedEndTime; |
| | | |
| | | /** 执行人ID */ |
| | | private Long assigneeId; |
| | | |
| | | /** 备注 */ |
| | | private String remark; |
| | | |
| | | /** 出发地经度 */ |
| | | private BigDecimal departureLongitude; |
| | | |
| | | /** 出发地纬度 */ |
| | | private BigDecimal departureLatitude; |
| | | |
| | | /** 目的地经度 */ |
| | | private BigDecimal destinationLongitude; |
| | | |
| | | /** 目的地纬度 */ |
| | | private BigDecimal destinationLatitude; |
| | | |
| | | /** 任务类型 */ |
| | | private String taskType; |
| | | |
| | | /** 部门ID */ |
| | | private Long deptId; |
| | | |
| | | /** 车辆ID列表 */ |
| | | private List<Long> vehicleIds; |
| | | |
| | | /** 病情ID列表(用于同步调度单的OrdICD_ID参数) */ |
| | | private List<Long> diseaseIds; |
| | | |
| | | /** 执行人员列表(包含角色类型) */ |
| | | private List<AssigneeInfo> assignees; |
| | | |
| | | /** 急救转运任务扩展信息 */ |
| | | private EmergencyInfoVO emergencyInfo; |
| | | |
| | | public Long getTaskId() { |
| | | return taskId; |
| | | } |
| | | |
| | | public void setTaskId(Long taskId) { |
| | | this.taskId = taskId; |
| | | } |
| | | |
| | | public String getTaskDescription() { |
| | | return taskDescription; |
| | | } |
| | | |
| | | public void setTaskDescription(String taskDescription) { |
| | | this.taskDescription = taskDescription; |
| | | } |
| | | |
| | | public String getDepartureAddress() { |
| | | return departureAddress; |
| | | } |
| | | |
| | | public void setDepartureAddress(String departureAddress) { |
| | | this.departureAddress = departureAddress; |
| | | } |
| | | |
| | | public String getDestinationAddress() { |
| | | return destinationAddress; |
| | | } |
| | | |
| | | public void setDestinationAddress(String destinationAddress) { |
| | | this.destinationAddress = destinationAddress; |
| | | } |
| | | |
| | | public Date getPlannedStartTime() { |
| | | return plannedStartTime; |
| | | } |
| | | |
| | | public void setPlannedStartTime(Date plannedStartTime) { |
| | | this.plannedStartTime = plannedStartTime; |
| | | } |
| | | |
| | | public Date getPlannedEndTime() { |
| | | return plannedEndTime; |
| | | } |
| | | |
| | | public void setPlannedEndTime(Date plannedEndTime) { |
| | | this.plannedEndTime = plannedEndTime; |
| | | } |
| | | |
| | | public Long getAssigneeId() { |
| | | return assigneeId; |
| | | } |
| | | |
| | | public void setAssigneeId(Long assigneeId) { |
| | | this.assigneeId = assigneeId; |
| | | } |
| | | |
| | | public String getRemark() { |
| | | return remark; |
| | | } |
| | | |
| | | public void setRemark(String remark) { |
| | | this.remark = remark; |
| | | } |
| | | |
| | | public BigDecimal getDepartureLongitude() { |
| | | return departureLongitude; |
| | | } |
| | | |
| | | public void setDepartureLongitude(BigDecimal departureLongitude) { |
| | | this.departureLongitude = departureLongitude; |
| | | } |
| | | |
| | | public BigDecimal getDepartureLatitude() { |
| | | return departureLatitude; |
| | | } |
| | | |
| | | public void setDepartureLatitude(BigDecimal departureLatitude) { |
| | | this.departureLatitude = departureLatitude; |
| | | } |
| | | |
| | | public BigDecimal getDestinationLongitude() { |
| | | return destinationLongitude; |
| | | } |
| | | |
| | | public void setDestinationLongitude(BigDecimal destinationLongitude) { |
| | | this.destinationLongitude = destinationLongitude; |
| | | } |
| | | |
| | | public BigDecimal getDestinationLatitude() { |
| | | return destinationLatitude; |
| | | } |
| | | |
| | | public void setDestinationLatitude(BigDecimal destinationLatitude) { |
| | | this.destinationLatitude = destinationLatitude; |
| | | } |
| | | |
| | | public String getTaskType() { |
| | | return taskType; |
| | | } |
| | | |
| | | public void setTaskType(String taskType) { |
| | | this.taskType = taskType; |
| | | } |
| | | |
| | | public Long getDeptId() { |
| | | return deptId; |
| | | } |
| | | |
| | | public void setDeptId(Long deptId) { |
| | | this.deptId = deptId; |
| | | } |
| | | |
| | | public List<Long> getVehicleIds() { |
| | | return vehicleIds; |
| | | } |
| | | |
| | | public void setVehicleIds(List<Long> vehicleIds) { |
| | | this.vehicleIds = vehicleIds; |
| | | } |
| | | |
| | | public List<Long> getDiseaseIds() { |
| | | return diseaseIds; |
| | | } |
| | | |
| | | public void setDiseaseIds(List<Long> diseaseIds) { |
| | | this.diseaseIds = diseaseIds; |
| | | } |
| | | |
| | | public EmergencyInfoVO getEmergencyInfo() { |
| | | return emergencyInfo; |
| | | } |
| | | |
| | | public void setEmergencyInfo(EmergencyInfoVO emergencyInfo) { |
| | | this.emergencyInfo = emergencyInfo; |
| | | } |
| | | |
| | | public List<AssigneeInfo> getAssignees() { |
| | | return assignees; |
| | | } |
| | | |
| | | public void setAssignees(List<AssigneeInfo> assignees) { |
| | | this.assignees = assignees; |
| | | } |
| | | |
| | | /** |
| | | * 急救转运任务扩展信息内部类 |
| | | */ |
| | | public static class EmergencyInfoVO { |
| | | /** 患者联系人 */ |
| | | private String patientContact; |
| | | |
| | | /** 患者联系电话 */ |
| | | private String patientPhone; |
| | | |
| | | /** 患者姓名 */ |
| | | private String patientName; |
| | | |
| | | /** 患者性别 */ |
| | | private String patientGender; |
| | | |
| | | /** 患者身份证号 */ |
| | | private String patientIdCard; |
| | | |
| | | /** 患者病情描述 */ |
| | | private String patientCondition; |
| | | |
| | | /** 转出医院ID */ |
| | | private Long hospitalOutId; |
| | | |
| | | /** 转出医院名称 */ |
| | | private String hospitalOutName; |
| | | |
| | | /** 转出医院科室 */ |
| | | private String hospitalOutDepartment; |
| | | |
| | | /** 转出医院科室ID */ |
| | | private String hospitalOutDepartmentId; |
| | | |
| | | /** 转出医院床号 */ |
| | | private String hospitalOutBedNumber; |
| | | |
| | | /** 转出医院地址 */ |
| | | private String hospitalOutAddress; |
| | | |
| | | /** 转出医院经度 */ |
| | | private BigDecimal hospitalOutLongitude; |
| | | |
| | | /** 转出医院纬度 */ |
| | | private BigDecimal hospitalOutLatitude; |
| | | |
| | | /** 转入医院ID */ |
| | | private Long hospitalInId; |
| | | |
| | | /** 转入医院名称 */ |
| | | private String hospitalInName; |
| | | |
| | | /** 转入医院科室 */ |
| | | private String hospitalInDepartment; |
| | | |
| | | /** 转入医院科室ID */ |
| | | private String hospitalInDepartmentId; |
| | | |
| | | /** 转入医院床号 */ |
| | | private String hospitalInBedNumber; |
| | | |
| | | /** 转入医院地址 */ |
| | | private String hospitalInAddress; |
| | | |
| | | /** 转入医院经度 */ |
| | | private BigDecimal hospitalInLongitude; |
| | | |
| | | /** 转入医院纬度 */ |
| | | private BigDecimal hospitalInLatitude; |
| | | |
| | | /** 转运公里数 */ |
| | | private BigDecimal transferDistance; |
| | | |
| | | /** 转运费用 */ |
| | | private BigDecimal transferPrice; |
| | | |
| | | // Getters and Setters |
| | | public String getPatientContact() { |
| | | return patientContact; |
| | | } |
| | | |
| | | public void setPatientContact(String patientContact) { |
| | | this.patientContact = patientContact; |
| | | } |
| | | |
| | | public String getPatientPhone() { |
| | | return patientPhone; |
| | | } |
| | | |
| | | public void setPatientPhone(String patientPhone) { |
| | | this.patientPhone = patientPhone; |
| | | } |
| | | |
| | | public String getPatientName() { |
| | | return patientName; |
| | | } |
| | | |
| | | public void setPatientName(String patientName) { |
| | | this.patientName = patientName; |
| | | } |
| | | |
| | | public String getPatientGender() { |
| | | return patientGender; |
| | | } |
| | | |
| | | public void setPatientGender(String patientGender) { |
| | | this.patientGender = patientGender; |
| | | } |
| | | |
| | | public String getPatientIdCard() { |
| | | return patientIdCard; |
| | | } |
| | | |
| | | public void setPatientIdCard(String patientIdCard) { |
| | | this.patientIdCard = patientIdCard; |
| | | } |
| | | |
| | | public String getPatientCondition() { |
| | | return patientCondition; |
| | | } |
| | | |
| | | public void setPatientCondition(String patientCondition) { |
| | | this.patientCondition = patientCondition; |
| | | } |
| | | |
| | | public Long getHospitalOutId() { |
| | | return hospitalOutId; |
| | | } |
| | | |
| | | public void setHospitalOutId(Long hospitalOutId) { |
| | | this.hospitalOutId = hospitalOutId; |
| | | } |
| | | |
| | | public String getHospitalOutName() { |
| | | return hospitalOutName; |
| | | } |
| | | |
| | | public void setHospitalOutName(String hospitalOutName) { |
| | | this.hospitalOutName = hospitalOutName; |
| | | } |
| | | |
| | | public String getHospitalOutDepartment() { |
| | | return hospitalOutDepartment; |
| | | } |
| | | |
| | | public void setHospitalOutDepartment(String hospitalOutDepartment) { |
| | | this.hospitalOutDepartment = hospitalOutDepartment; |
| | | } |
| | | |
| | | public String getHospitalOutDepartmentId() { |
| | | return hospitalOutDepartmentId; |
| | | } |
| | | |
| | | public void setHospitalOutDepartmentId(String hospitalOutDepartmentId) { |
| | | this.hospitalOutDepartmentId = hospitalOutDepartmentId; |
| | | } |
| | | |
| | | public String getHospitalOutBedNumber() { |
| | | return hospitalOutBedNumber; |
| | | } |
| | | |
| | | public void setHospitalOutBedNumber(String hospitalOutBedNumber) { |
| | | this.hospitalOutBedNumber = hospitalOutBedNumber; |
| | | } |
| | | |
| | | public String getHospitalOutAddress() { |
| | | return hospitalOutAddress; |
| | | } |
| | | |
| | | public void setHospitalOutAddress(String hospitalOutAddress) { |
| | | this.hospitalOutAddress = hospitalOutAddress; |
| | | } |
| | | |
| | | public BigDecimal getHospitalOutLongitude() { |
| | | return hospitalOutLongitude; |
| | | } |
| | | |
| | | public void setHospitalOutLongitude(BigDecimal hospitalOutLongitude) { |
| | | this.hospitalOutLongitude = hospitalOutLongitude; |
| | | } |
| | | |
| | | public BigDecimal getHospitalOutLatitude() { |
| | | return hospitalOutLatitude; |
| | | } |
| | | |
| | | public void setHospitalOutLatitude(BigDecimal hospitalOutLatitude) { |
| | | this.hospitalOutLatitude = hospitalOutLatitude; |
| | | } |
| | | |
| | | public Long getHospitalInId() { |
| | | return hospitalInId; |
| | | } |
| | | |
| | | public void setHospitalInId(Long hospitalInId) { |
| | | this.hospitalInId = hospitalInId; |
| | | } |
| | | |
| | | public String getHospitalInName() { |
| | | return hospitalInName; |
| | | } |
| | | |
| | | public void setHospitalInName(String hospitalInName) { |
| | | this.hospitalInName = hospitalInName; |
| | | } |
| | | |
| | | public String getHospitalInDepartment() { |
| | | return hospitalInDepartment; |
| | | } |
| | | |
| | | public void setHospitalInDepartment(String hospitalInDepartment) { |
| | | this.hospitalInDepartment = hospitalInDepartment; |
| | | } |
| | | |
| | | public String getHospitalInDepartmentId() { |
| | | return hospitalInDepartmentId; |
| | | } |
| | | |
| | | public void setHospitalInDepartmentId(String hospitalInDepartmentId) { |
| | | this.hospitalInDepartmentId = hospitalInDepartmentId; |
| | | } |
| | | |
| | | public String getHospitalInBedNumber() { |
| | | return hospitalInBedNumber; |
| | | } |
| | | |
| | | public void setHospitalInBedNumber(String hospitalInBedNumber) { |
| | | this.hospitalInBedNumber = hospitalInBedNumber; |
| | | } |
| | | |
| | | public String getHospitalInAddress() { |
| | | return hospitalInAddress; |
| | | } |
| | | |
| | | public void setHospitalInAddress(String hospitalInAddress) { |
| | | this.hospitalInAddress = hospitalInAddress; |
| | | } |
| | | |
| | | public BigDecimal getHospitalInLongitude() { |
| | | return hospitalInLongitude; |
| | | } |
| | | |
| | | public void setHospitalInLongitude(BigDecimal hospitalInLongitude) { |
| | | this.hospitalInLongitude = hospitalInLongitude; |
| | | } |
| | | |
| | | public BigDecimal getHospitalInLatitude() { |
| | | return hospitalInLatitude; |
| | | } |
| | | |
| | | public void setHospitalInLatitude(BigDecimal hospitalInLatitude) { |
| | | this.hospitalInLatitude = hospitalInLatitude; |
| | | } |
| | | |
| | | public BigDecimal getTransferDistance() { |
| | | return transferDistance; |
| | | } |
| | | |
| | | public void setTransferDistance(BigDecimal transferDistance) { |
| | | this.transferDistance = transferDistance; |
| | | } |
| | | |
| | | public BigDecimal getTransferPrice() { |
| | | return transferPrice; |
| | | } |
| | | |
| | | public void setTransferPrice(BigDecimal transferPrice) { |
| | | this.transferPrice = transferPrice; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 执行人员信息内部类 |
| | | */ |
| | | public static class AssigneeInfo { |
| | | /** 用户ID */ |
| | | private Long userId; |
| | | |
| | | /** 用户姓名 */ |
| | | private String userName; |
| | | |
| | | /** 用户类型(角色):driver-司机, doctor-医生, nurse-护士 */ |
| | | private String userType; |
| | | |
| | | public Long getUserId() { |
| | | return userId; |
| | | } |
| | | |
| | | public void setUserId(Long userId) { |
| | | this.userId = userId; |
| | | } |
| | | |
| | | public String getUserName() { |
| | | return userName; |
| | | } |
| | | |
| | | public void setUserName(String userName) { |
| | | this.userName = userName; |
| | | } |
| | | |
| | | public String getUserType() { |
| | | return userType; |
| | | } |
| | | |
| | | public void setUserType(String userType) { |
| | | this.userType = userType; |
| | | } |
| | | } |
| | | private Date updateTime; |
| | | } |
| | |
| | | package com.ruoyi.system.service; |
| | | |
| | | import com.ruoyi.system.domain.PaidMoneyAdd; |
| | | import com.ruoyi.system.domain.SysTaskAdditionalFee; |
| | | |
| | | /** |
| | | * 附加费用同步Service接口 |
| | | * |
| | |
| | | * @param feeId 附加费用ID |
| | | * @return 是否同步成功 |
| | | */ |
| | | boolean syncAdditionalFeeToLegacy(Long feeId); |
| | | boolean syncAdditionalFeeToLegacy(SysTaskAdditionalFee fee); |
| | | |
| | | /** |
| | | * 将旧系统PaidMoney_Add记录同步到新系统 |
| | | * |
| | | * @param paidMoneyAddId 旧系统附加费用记录ID |
| | | * @param paidMoneyAdd 旧系统附加费用记录ID |
| | | * @return 是否同步成功 |
| | | */ |
| | | boolean syncAdditionalFeeFromLegacy(Long paidMoneyAddId); |
| | | |
| | | boolean syncAdditionalFeeFromLegacy(PaidMoneyAdd paidMoneyAdd); |
| | | |
| | | /** |
| | | * 批量同步新系统未同步的附加费用到旧系统 |
| | | * |
| | |
| | | |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | |
| | | import com.ruoyi.system.domain.vo.*; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | import com.ruoyi.system.domain.SysTask; |
| | | import com.ruoyi.system.domain.SysTaskLog; |
| | | import com.ruoyi.system.domain.SysTaskVehicle; |
| | | import com.ruoyi.system.domain.SysTaskAttachment; |
| | | import com.ruoyi.system.domain.vo.TaskQueryVO; |
| | | import com.ruoyi.system.domain.vo.TaskCreateVO; |
| | | import com.ruoyi.system.domain.vo.TaskUpdateVO; |
| | | import com.ruoyi.system.domain.vo.TaskStatisticsVO; |
| | | import com.ruoyi.system.domain.enums.TaskStatus; |
| | | |
| | | /** |
| | |
| | | * @param updateVO 任务更新对象 |
| | | * @return 结果 |
| | | */ |
| | | public int updateSysTask(TaskUpdateVO updateVO); |
| | | public int updateSysTask(TaskUpdateVO updateVO,Boolean updateFromLegacy); |
| | | |
| | | /** |
| | | * 修改任务管理(允许从外部传入用户信息、部门信息和时间信息) 用于从旧系统中同步过来 |
| | | * @param updateVO |
| | | * @param serviceOrderId |
| | | * @param dispatchOrderId |
| | | * @param serviceOrdNo |
| | | * @param userId |
| | | * @param userName |
| | | * @param deptId |
| | | * @param createTime |
| | | * @param updateTime |
| | | * @return |
| | | */ |
| | | public int updateTask(TaskUpdateVO updateVO, String serviceOrderId, String dispatchOrderId, String serviceOrdNo, Long userId, String userName, Long deptId, Date createTime, Date updateTime); |
| | | |
| | | /** |
| | | * 批量删除任务管理 |
| | |
| | | * @param remark 备注 |
| | | * @return 结果 |
| | | */ |
| | | public int assignVehicleToTask(Long taskId, Long vehicleId, String remark); |
| | | public int assignVehicleToTask(Long taskId, Long vehicleId, String remark,Long userId,String userName); |
| | | |
| | | /** |
| | | * 取消任务车辆分配 |
| | |
| | | * @param remark 备注 |
| | | * @return 结果 |
| | | */ |
| | | public int assignMultipleVehiclesToTask(Long taskId, List<Long> vehicleIds, String remark); |
| | | public int assignMultipleVehiclesToTask(Long taskId, List<Long> vehicleIds, String remark,Long userId,String userName); |
| | | |
| | | /** |
| | | * 查询任务关联的车辆 |
| | |
| | | package com.ruoyi.system.service.impl; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | |
| | |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import com.ruoyi.common.annotation.DataSource; |
| | | import com.ruoyi.common.enums.DataSourceType; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.system.domain.PaidMoneyAdd; |
| | | import com.ruoyi.system.domain.SysTask; |
| | | import com.ruoyi.system.domain.SysTaskAdditionalFee; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public boolean syncAdditionalFeeToLegacy(Long feeId) { |
| | | public boolean syncAdditionalFeeToLegacy(SysTaskAdditionalFee fee) { |
| | | Long feeId= fee.getId(); |
| | | try { |
| | | // 1. 查询新系统附加费用记录 |
| | | SysTaskAdditionalFee fee = additionalFeeMapper.selectByTaskId(null).stream() |
| | | .filter(f -> f.getId().equals(feeId)) |
| | | .findFirst() |
| | | .orElse(null); |
| | | |
| | | |
| | | if (fee == null) { |
| | | log.info("新系统附加费用记录不存在,feeId: {}", feeId); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | // 1. 检查是否已同步 |
| | | // 2. 如果已同步过,跳过 |
| | | if (fee.getPid() != null && fee.getPid() > 0) { |
| | | log.info("附加费用已同步,feeId: {}, pid: {}", feeId, fee.getPid()); |
| | |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 从旧系统同步附加费用 |
| | | * @param paidMoneyAdd 旧系统附加费用记录ID |
| | | * @return |
| | | */ |
| | | @Override |
| | | public boolean syncAdditionalFeeFromLegacy(Long paidMoneyAddId) { |
| | | public boolean syncAdditionalFeeFromLegacy(PaidMoneyAdd paidMoneyAdd) { |
| | | try { |
| | | // 1. 查询旧系统PaidMoney_Add记录 |
| | | PaidMoneyAdd paidMoneyAdd = paidMoneyAddMapper.selectById(paidMoneyAddId); |
| | | if (paidMoneyAdd == null) { |
| | | log.error("旧系统附加费用记录不存在,paidMoneyAddId: {}", paidMoneyAddId); |
| | | return false; |
| | | } |
| | | |
| | | Long paidMoneyAddId = paidMoneyAdd.getId(); |
| | | log.info("1.开始同步旧系统附加费用,paidMoneyAddId: {}", paidMoneyAddId); |
| | | // 2. 检查是否已同步过 |
| | | SysTaskAdditionalFee existFee = additionalFeeMapper.selectByPid(paidMoneyAddId); |
| | | if (existFee != null) { |
| | | log.info("旧系统附加费用记录已同步,paidMoneyAddId: {}, feeId: {}", paidMoneyAddId, existFee.getId()); |
| | | return true; |
| | | } |
| | | log.info("2.开始同步旧系统附加费用,paidMoneyAddId: {}", paidMoneyAddId); |
| | | |
| | | // 3. 根据ServiceOrdID查询新系统任务 |
| | | if (paidMoneyAdd.getToServiceOrdID() == null) { |
| | | log.warn("旧系统附加费用记录缺少ServiceOrdID,paidMoneyAddId: {},跳过同步", paidMoneyAddId); |
| | | return false; |
| | | } |
| | | log.info("3.开始同步旧系统附加费用,paidMoneyAddId: {}, ServiceOrdID: {}", paidMoneyAddId, paidMoneyAdd.getToServiceOrdID()); |
| | | |
| | | SysTaskEmergency emergency = sysTaskEmergencyMapper.selectByLegacyServiceOrdId(paidMoneyAdd.getToServiceOrdID()); |
| | | if (emergency == null) { |
| | | log.warn("未找到对应的转运任务,ServiceOrdID: {},跳过同步", paidMoneyAdd.getToServiceOrdID()); |
| | | log.warn("4.开始同步旧系统附加费用,未找到对应的转运任务,ServiceOrdID: {},跳过同步", paidMoneyAdd.getToServiceOrdID()); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | log.info("4.开始同步旧系统附加费用,paidMoneyAddId: {}, ServiceOrdID: {}, DispatchOrdID: {}", paidMoneyAddId, paidMoneyAdd.getToServiceOrdID(), paidMoneyAdd.getToDispatchOrdID()); |
| | | // 4. 验证DispatchOrdID是否匹配 |
| | | if (paidMoneyAdd.getToDispatchOrdID() == null) { |
| | | log.warn("旧系统附加费用记录缺少DispatchOrdID,paidMoneyAddId: {},跳过同步", paidMoneyAddId); |
| | | return false; |
| | | } |
| | | |
| | | log.info("5.开始同步旧系统附加费用,paidMoneyAddId: {}, ServiceOrdID: {}, DispatchOrdID: {}", paidMoneyAddId, paidMoneyAdd.getToServiceOrdID(), paidMoneyAdd.getToDispatchOrdID()); |
| | | if (!paidMoneyAdd.getToDispatchOrdID().equals(emergency.getLegacyDispatchOrdId())) { |
| | | log.warn("转运任务DispatchOrdID不匹配,ServiceOrdID: {}, 附加费用DispatchOrdID: {} vs 任务DispatchOrdID: {},跳过同步", |
| | | log.warn("5.转运任务DispatchOrdID不匹配,ServiceOrdID: {}, 附加费用DispatchOrdID: {} vs 任务DispatchOrdID: {},跳过同步", |
| | | paidMoneyAdd.getToServiceOrdID(), paidMoneyAdd.getToDispatchOrdID(), emergency.getLegacyDispatchOrdId()); |
| | | return false; |
| | | } |
| | |
| | | fee.setPid(paidMoneyAddId); |
| | | fee.setSyncStatus(2); // 同步成功 |
| | | fee.setSyncTime(new Date()); |
| | | |
| | | log.info("6.开始同步旧系统附加费用,paidMoneyAddId: {}, ServiceOrdID: {}, DispatchOrdID: {}, feeId: {}", paidMoneyAddId, paidMoneyAdd.getToServiceOrdID(), paidMoneyAdd.getToDispatchOrdID(), fee.getId()); |
| | | // 6. 插入新系统附加费用记录 |
| | | int result = additionalFeeMapper.insert(fee); |
| | | if (result > 0) { |
| | | log.info("旧系统附加费用记录同步到新系统成功,paidMoneyAddId: {}, feeId: {}", paidMoneyAddId, fee.getId()); |
| | | log.info("6.旧系统附加费用记录同步到新系统成功,paidMoneyAddId: {}, feeId: {}", paidMoneyAddId, fee.getId()); |
| | | return true; |
| | | } else { |
| | | log.error("插入新系统附加费用记录失败,paidMoneyAddId: {}", paidMoneyAddId); |
| | | log.error("6.插入新系统附加费用记录失败,paidMoneyAddId: {}", paidMoneyAddId); |
| | | return false; |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | log.error("同步旧系统附加费用记录到新系统异常,paidMoneyAddId: {}", paidMoneyAddId, e); |
| | | log.error("8.同步旧系统附加费用记录到新系统异常,paidMoneyAddId: {}", paidMoneyAdd.getId(), e); |
| | | return false; |
| | | } |
| | | } |
| | |
| | | |
| | | for (SysTaskAdditionalFee fee : unsyncedFees) { |
| | | try { |
| | | if (syncAdditionalFeeToLegacy(fee.getId())) { |
| | | if (syncAdditionalFeeToLegacy(fee)) { |
| | | successCount++; |
| | | } |
| | | // 每条记录间隔1秒,避免过于频繁 |
| | |
| | | |
| | | for (PaidMoneyAdd paidMoneyAdd : recentRecords) { |
| | | try { |
| | | if (syncAdditionalFeeFromLegacy(paidMoneyAdd.getId())) { |
| | | if (syncAdditionalFeeFromLegacy(paidMoneyAdd)) { |
| | | successCount++; |
| | | } |
| | | // 每条记录间隔1秒,避免过于频繁 |
| | |
| | | if (parts.length != 2) |
| | | { |
| | | log.warn("部门名称格式不正确,跳过: {}", fullName); |
| | | continue; |
| | | parts= fullName.split("-"); |
| | | if(parts.length != 2) { |
| | | continue; |
| | | } |
| | | } |
| | | String part=""; |
| | | //只要发现有(新)或(新)就在branchName中加个新 字 |
| | | if(fullName.contains("(新)") || fullName.contains("(新)")) { |
| | | part="(新)"; |
| | | } |
| | | |
| | | String branchName = parts[0].trim() + "分公司"; // 湛江 -> 湛江分公司 |
| | | String deptName = parts[1].trim(); // 护士 |
| | | String cityName=parts[0].trim(); |
| | | String namePart=parts[0].trim(); |
| | | if(namePart.contains("(新)") || namePart.contains("(新)")) { |
| | | branchName=namePart+"分公司"; |
| | | cityName=namePart; |
| | | deptName=parts[1].trim(); |
| | | } |
| | | else{ |
| | | branchName=namePart+part+"分公司"; |
| | | cityName=namePart+ part; |
| | | deptName=parts[1].trim(); |
| | | } |
| | | |
| | | |
| | | // 获取或创建分公司 |
| | | Long branchDeptId = branchMap.get(branchName); |
| | |
| | | branchMap.put(branchName, branchDeptId); |
| | | |
| | | // 检查并更新编码 |
| | | syncOrderClassCodes(existingBranch, parts[0].trim(), serviceOrderList, dispatchOrderList,addressList); |
| | | syncOrderClassCodes(existingBranch, cityName, serviceOrderList, dispatchOrderList,addressList); |
| | | // existingBranch.setDepartmentId(dto.getDepartmentId()); |
| | | sysDeptMapper.updateDept(existingBranch); |
| | | log.info("更新分公司编码: {}, 服务单编码: {}, 调度单编码: {}", |
| | |
| | | // newBranch.setDepartmentId(dto.getDepartmentId()); |
| | | |
| | | // 自动匹配并设置服务单和调度单编码 |
| | | syncOrderClassCodes(newBranch, parts[0].trim(), serviceOrderList, dispatchOrderList,addressList); |
| | | syncOrderClassCodes(newBranch, cityName, serviceOrderList, dispatchOrderList,addressList); |
| | | |
| | | sysDeptMapper.insertDept(newBranch); |
| | | branchDeptId = newBranch.getDeptId(); |
| | |
| | | // 遍历编码列表,查找包含城市名称的项 |
| | | for (OrderClassDTO dto : orderClassList) |
| | | { |
| | | //有些加了新 TODO |
| | | if (dto.getVtext() != null && dto.getVtext().contains(cityName)) |
| | | { |
| | | log.debug("城市名称匹配成功 - 城市: {}, vtext: {}, vOrder2: {}", |
| | |
| | | params.put("ServiceOrdCoName", StringUtils.nvl(emergency.getPatientContact(), "")); |
| | | params.put("ServiceOrdCoPhone", StringUtils.nvl(emergency.getPatientPhone(), "")); |
| | | params.put("ServiceOrdPtName", StringUtils.nvl(emergency.getPatientName(), "")); |
| | | |
| | | params.put("ServiceOrdTraStreet",StringUtils.nvl(task.getDepartureAddress(), StringUtils.nvl(emergency.getHospitalOutAddress(), ""))); |
| | | // 地址信息 |
| | | params.put("DispatchOrdTraStreet", StringUtils.nvl(task.getDepartureAddress(), StringUtils.nvl(emergency.getHospitalOutAddress(), ""))); |
| | | params.put("DispatchOrdTraEnd", StringUtils.nvl(task.getDestinationAddress(), StringUtils.nvl(emergency.getHospitalInAddress(), ""))); |
| | |
| | | params.put("DispatchOrdID", emergency.getLegacyDispatchOrdId().toString()); |
| | | params.put("ServiceOrdID", emergency.getLegacyServiceOrdId().toString()); |
| | | params.put("DispatchOrdState", "3"); |
| | | log.info("重新同步调度单到旧系统请求参数: {}", params); |
| | | // 发送HTTP请求到旧系统(使用admin_save_25.asp接口) |
| | | String response = sendHttpPost(legacyConfig.getDispatchUpdateUrl(), params); |
| | | |
| | | log.info("重新同步调度单到旧系统响应: ServiceOrdID:{},DispatchOrdId:{},Result: {}",emergency.getLegacyServiceOrdId(),emergency.getLegacyDispatchOrdId(), response); |
| | | // 解析响应 |
| | | Long dispatchOrdId = parseResponse(response); |
| | | // Long dispatchOrdId = parseResponse(response); |
| | | |
| | | if (dispatchOrdId != null && dispatchOrdId > 0) { |
| | | if (response != null && response.equals("OK")) { |
| | | // 重新同步成功,清除重新同步标记 |
| | | emergency.setNeedResync(0); |
| | | emergency.setDispatchSyncTime(new Date()); |
| | | emergency.setDispatchSyncErrorMsg(null); |
| | | sysTaskEmergencyService.updateSysTaskEmergency(emergency); |
| | | |
| | | log.info("调度单重新同步成功,任务ID: {}, DispatchOrdID: {}", taskId, dispatchOrdId); |
| | | log.info("调度单重新同步成功,任务ID: {}, DispatchOrdID: {}", taskId, emergency.getLegacyDispatchOrdId()); |
| | | return true; |
| | | } else { |
| | | // 重新同步失败 |
| | |
| | | package com.ruoyi.system.service.impl; |
| | | |
| | | import com.ruoyi.common.annotation.DataSource; |
| | | import com.ruoyi.common.core.domain.entity.SysDept; |
| | | import com.ruoyi.common.core.domain.entity.SysUser; |
| | | import com.ruoyi.common.enums.DataSourceType; |
| | | import com.ruoyi.common.utils.DateUtils; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.system.domain.SysTask; |
| | | import com.ruoyi.system.domain.SysTaskEmergency; |
| | | import com.ruoyi.system.domain.VehicleInfo; |
| | | import com.ruoyi.system.domain.vo.TaskCreateVO; |
| | | import com.ruoyi.system.domain.vo.TaskUpdateVO; |
| | | import com.ruoyi.system.mapper.SysTaskEmergencyMapper; |
| | | import com.ruoyi.system.mapper.SysTaskMapper; |
| | | import com.ruoyi.system.service.ILegacyTransferSyncService; |
| | |
| | | import com.ruoyi.system.mapper.VehicleInfoMapper; |
| | | import com.ruoyi.system.service.ISysUserService; |
| | | import com.ruoyi.system.service.IWechatTaskNotifyService; |
| | | import org.apache.poi.ss.usermodel.DateUtil; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.beans.BeanUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.HashMap; |
| | | |
| | | /** |
| | | * 旧系统转运单同步Service业务层处理 |
| | |
| | | |
| | | @Autowired |
| | | private IWechatTaskNotifyService wechatTaskNotifyService; |
| | | |
| | | |
| | | |
| | | |
| | | /** |
| | | * 同步指定日期范围的旧系统转运单到新系统 |
| | | * |
| | |
| | | // 检查是否已同步 |
| | | if (isTransferOrderSynced(serviceOrdID, dispatchOrdID)) { |
| | | log.debug("转运单已同步,跳过: ServiceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID); |
| | | //进行更新操作 |
| | | updateTransferOrder(serviceOrdID, dispatchOrdID, order); |
| | | continue; |
| | | } |
| | | |
| | |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | |
| | | private boolean updateTransferOrder(String serviceOrdID, String dispatchOrdID, Map<String, Object> order){ |
| | | log.info("开始同步单个转运单: ServiceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID); |
| | | String sysTaskCode=""; |
| | | try { |
| | | SysTaskEmergency emergency=sysTaskEmergencyMapper.selectByLegacyServiceOrdId(Long.parseLong(serviceOrdID)); |
| | | if(emergency.getNeedResync().equals(1)){ |
| | | log.info("新系统需要同步到旧系统那里,所以不要同步旧数据到新系统,serviceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID); |
| | | return false; |
| | | } |
| | | // 构造TaskCreateVO对象 |
| | | TaskCreateVO createTaskVo = buildCreateTaskVo(serviceOrdID, dispatchOrdID, order); |
| | | |
| | | sysTaskCode = createTaskVo.getTaskCode(); |
| | | if (createTaskVo == null) { |
| | | log.error("构造TaskCreateVO失败: ServiceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID); |
| | | return false; |
| | | } |
| | | |
| | | // 记录创建的任务信息 |
| | | log.debug("准备创建任务: ServiceOrdID={}, DispatchOrdID={}, 患者姓名={}, 转出医院={}, 转入医院={}", |
| | | serviceOrdID, dispatchOrdID, |
| | | createTaskVo.getPatient() != null ? createTaskVo.getPatient().getName() : "未知", |
| | | createTaskVo.getHospitalOut() != null ? createTaskVo.getHospitalOut().getName() : "未知", |
| | | createTaskVo.getHospitalIn() != null ? createTaskVo.getHospitalIn().getName() : "未知"); |
| | | /** |
| | | * 开单时间 |
| | | */ |
| | | Date ServiceOrd_CC_Time= getDateValue(order, "ServiceOrd_CC_Time"); |
| | | // 调用sysTaskService创建任务 |
| | | String serviceOrdClass = getStringValue(order,"ServiceOrdClass"); |
| | | String serviceOrdNo = getStringValue(order,"ServiceOrdNo"); |
| | | |
| | | Integer oauserId=getIntegerValue(order,"ServiceOrd_NS_ID"); |
| | | SysUser sysUser=sysUserService.selectUserByOaUserId(oauserId); |
| | | Long taskCreatorId=sysUser==null?null:sysUser.getUserId(); |
| | | String createUserName=sysUser==null?"system":sysUser.getUserName(); |
| | | SysDept dept=sysDeptService.selectDeptByServiceClass(serviceOrdClass); |
| | | Long deptId=dept==null?null:dept.getDeptId(); |
| | | TaskUpdateVO updateTaskVo = new TaskUpdateVO(); |
| | | BeanUtils.copyProperties(createTaskVo, updateTaskVo); |
| | | |
| | | |
| | | int result = sysTaskService.updateTask(updateTaskVo,serviceOrdID,dispatchOrdID, serviceOrdNo, taskCreatorId,createUserName, deptId, ServiceOrd_CC_Time, ServiceOrd_CC_Time); |
| | | |
| | | if (result > 0) { |
| | | log.info("转运单同步成功: ServiceOrdID={}, DispatchOrdID={}, 创建的任务ID={}", serviceOrdID, dispatchOrdID, result); |
| | | |
| | | try { |
| | | notifyTransferOrderByWechat((long) result, serviceOrdID, dispatchOrdID, serviceOrdNo, ServiceOrd_CC_Time, dept, order); |
| | | } catch (Exception e) { |
| | | log.error("转运单同步成功后发送微信通知失败: ServiceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID, e); |
| | | } |
| | | |
| | | return true; |
| | | } else { |
| | | log.error("转运单同步失败: ServiceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID); |
| | | return false; |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | log.error("同步单个转运单异常: ServiceOrdID={}, DispatchOrdID={},sysTaskCode:{}", serviceOrdID, dispatchOrdID,sysTaskCode, e); |
| | | return false; |
| | | } |
| | | } |
| | | /** |
| | | * 同步单个旧系统转运单到新系统(带详细信息) |
| | | * |
| | |
| | | |
| | | // 设置区域类型 |
| | | String serviceOrdAreaType = getStringValue(order, "ServiceOrdAreaType"); |
| | | if (StringUtils.isNotEmpty(serviceOrdAreaType)) { |
| | | // 可以根据需要将区域类型映射到TaskCreateVO的其他字段 |
| | | log.debug("区域类型: {}", serviceOrdAreaType); |
| | | } |
| | | |
| | | |
| | | // 设置用户ID |
| | | Long serviceOrdUserID = getLongValue(order, "ServiceOrdUserID"); |
| | | if (serviceOrdUserID != null) { |
| | | // 可以根据需要将用户ID映射到TaskCreateVO的其他字段 |
| | | log.debug("用户ID: {}", serviceOrdUserID); |
| | | } |
| | | |
| | | |
| | | // 设置患者信息 |
| | | TaskCreateVO.PatientInfo patientInfo = new TaskCreateVO.PatientInfo(); |
| | | patientInfo.setName(getStringValue(order, "ServiceOrdPtName")); |
| | |
| | | |
| | | |
| | | String hospitalOutDeptId = getStringValue(order, "ServiceOrdPtServicesID"); |
| | | |
| | | //转出床位 |
| | | String serviceOrdPtServices=getStringValue(order, "ServiceOrdPtServices"); |
| | | hospitalOutInfo.setDepartmentId(hospitalOutDeptId); |
| | | if (StringUtils.isNotEmpty(hospitalOutDeptId)) { |
| | | String hospitalOutDeptName = legacyTransferSyncMapper.selectDepartmentNameByDeptID(hospitalOutDeptId); |
| | |
| | | hospitalOutInfo.setDepartment(hospitalOutDeptName); |
| | | } |
| | | } |
| | | //转出床位 |
| | | String serviceOrdPtServices=getStringValue(order, "ServiceOrdPtServices"); |
| | | if(serviceOrdPtServices!= null){ |
| | | hospitalOutInfo.setBedNumber(serviceOrdPtServices); |
| | | } |
| | |
| | | createTaskVo.setHospitalIn(hospitalInInfo); |
| | | |
| | | // 设置地址信息 |
| | | createTaskVo.setDepartureAddress(getStringValue(order, "ServiceOrdTraVia")); |
| | | createTaskVo.setDepartureAddress(getStringValue(order, "ServiceOrdTraStreet")); |
| | | createTaskVo.setDestinationAddress(getStringValue(order, "ServiceOrdTraEnd")); |
| | | |
| | | // 设置价格和距离信息 |
| | |
| | | // 设置执行人信息 |
| | | List<TaskCreateVO.AssigneeInfo> assignees = queryAssignees(dispatchOrdID); |
| | | createTaskVo.setAssignees(assignees); |
| | | if(!assignees.isEmpty()){ |
| | | createTaskVo.setAssigneeId(assignees.get(0).getUserId()); |
| | | } |
| | | |
| | | // 设置车辆信息 |
| | | // 车辆ID需要根据DispatchOrdCarID查询获取 |
| | |
| | | if (assigneeList != null && !assigneeList.isEmpty()) { |
| | | for (Map<String, Object> assigneeMap : assigneeList) { |
| | | String entourageOAId = getStringValue(assigneeMap, "EntourageOAId"); |
| | | String entourageState = getStringValue(assigneeMap, "EntourageState"); |
| | | String entourageState = getStringValue(assigneeMap, "EntourageID"); |
| | | |
| | | if (StringUtils.isNotEmpty(entourageOAId)) { |
| | | try { |
| | |
| | | |
| | | // 异步同步到旧系统 |
| | | try { |
| | | additionalFeeSyncService.syncAdditionalFeeToLegacy(fee.getId()); |
| | | additionalFeeSyncService.syncAdditionalFeeToLegacy(fee); |
| | | } catch (Exception e) { |
| | | log.error("同步附加费用到旧系统失败", e); |
| | | } |
| | |
| | | |
| | | // 生成回调地址 |
| | | String callbackUrl = callbackBaseUrl + "/payment/callback/" + provider.toLowerCase(); |
| | | |
| | | payment.setCallbackUrl(callbackUrl); |
| | | |
| | | try { |
| | |
| | | import java.net.URL; |
| | | |
| | | import com.ruoyi.common.core.domain.entity.SysUser; |
| | | import com.ruoyi.system.domain.vo.*; |
| | | import com.ruoyi.system.mapper.*; |
| | | import com.ruoyi.system.utils.TaskCodeGenerator; |
| | | import com.ruoyi.common.config.ImageUrlConfig; |
| | |
| | | import com.ruoyi.system.domain.SysTaskEmergency; |
| | | import com.ruoyi.system.domain.SysTaskWelfare; |
| | | import com.ruoyi.system.domain.SysTaskAssignee; |
| | | import com.ruoyi.system.domain.vo.TaskQueryVO; |
| | | import com.ruoyi.system.domain.vo.TaskCreateVO; |
| | | import com.ruoyi.system.domain.vo.TaskUpdateVO; |
| | | import com.ruoyi.system.domain.vo.TaskStatisticsVO; |
| | | import com.ruoyi.system.domain.enums.TaskStatus; |
| | | import com.ruoyi.system.domain.VehicleInfo; |
| | | import com.ruoyi.system.service.ISysTaskService; |
| | |
| | | taskVehicle.setTaskId(task.getTaskId()); |
| | | taskVehicle.setVehicleId(vehicleId); |
| | | taskVehicle.setAssignTime(DateUtils.getNowDate()); |
| | | taskVehicle.setAssignBy(SecurityUtils.getUsername()); |
| | | taskVehicle.setAssignBy(username); |
| | | taskVehicle.setStatus("ASSIGNED"); |
| | | taskVehicle.setCreateBy(SecurityUtils.getUsername()); |
| | | taskVehicle.setCreateBy(username); |
| | | taskVehicle.setCreateTime(DateUtils.getNowDate()); |
| | | taskVehicle.setUpdateBy(SecurityUtils.getUsername()); |
| | | taskVehicle.setUpdateBy(username); |
| | | taskVehicle.setUpdateTime(DateUtils.getNowDate()); |
| | | |
| | | sysTaskVehicleMapper.insertSysTaskVehicle(taskVehicle); |
| | |
| | | */ |
| | | @Override |
| | | @Transactional |
| | | public int updateSysTask(TaskUpdateVO updateVO) { |
| | | public int updateSysTask(TaskUpdateVO updateVO, Boolean updateFromLegacy) { |
| | | SysTask oldTask = sysTaskMapper.selectSysTaskByTaskId(updateVO.getTaskId()); |
| | | if (oldTask == null) { |
| | | throw new RuntimeException("任务不存在"); |
| | | } |
| | | String userName = SecurityUtils.getUsername(); |
| | | |
| | | SysTask task = new SysTask(); |
| | | task.setTaskId(updateVO.getTaskId()); |
| | | task.setTaskDescription(updateVO.getTaskDescription()); |
| | | task.setPlannedStartTime(updateVO.getPlannedStartTime()); |
| | | task.setPlannedEndTime(updateVO.getPlannedEndTime()); |
| | | task.setAssigneeId(updateVO.getAssigneeId()); |
| | | task.setUpdateBy(userName); |
| | | task.setUpdateTime(updateVO.getUpdateTime() != null ? updateVO.getUpdateTime() : DateUtils.getNowDate()); |
| | | task.setRemark(updateVO.getRemark()); |
| | | |
| | | // 设置通用地址和坐标信息 |
| | | task.setDepartureAddress(updateVO.getDepartureAddress()); |
| | | task.setDestinationAddress(updateVO.getDestinationAddress()); |
| | | task.setDepartureLongitude(updateVO.getDepartureLongitude()); |
| | | task.setDepartureLatitude(updateVO.getDepartureLatitude()); |
| | | task.setDestinationLongitude(updateVO.getDestinationLongitude()); |
| | | task.setDestinationLatitude(updateVO.getDestinationLatitude()); |
| | | task.setPlannedStartTime(updateVO.getPlannedStartTime()); |
| | | task.setPlannedEndTime(updateVO.getPlannedEndTime()); |
| | | task.setAssigneeId(updateVO.getAssigneeId()); |
| | | task.setUpdateBy(SecurityUtils.getUsername()); |
| | | task.setUpdateTime(DateUtils.getNowDate()); |
| | | task.setRemark(updateVO.getRemark()); |
| | | |
| | | // 设置预计距离 |
| | | if (updateVO.getEstimatedDistance() != null) { |
| | | task.setEstimatedDistance(updateVO.getEstimatedDistance()); |
| | | } else if (updateVO.getTransferDistance() != null) { |
| | | // 兼容急救转运字段 |
| | | task.setEstimatedDistance(updateVO.getTransferDistance()); |
| | | } else if (updateVO.getDistance() != null) { |
| | | // 兼容福祉车字段 |
| | | task.setEstimatedDistance(updateVO.getDistance()); |
| | | } |
| | | |
| | | // 如果更新了部门ID |
| | | if (updateVO.getDeptId() != null) { |
| | | task.setDeptId(updateVO.getDeptId()); |
| | | } |
| | | |
| | | // 如果更新了任务编号 |
| | | if (updateVO.getTaskCode() != null) { |
| | | task.setTaskCode(updateVO.getTaskCode()); |
| | | } |
| | | |
| | | // 自动获取出发地GPS坐标(如果更新了地址但缺失坐标) |
| | | if (updateVO.getDepartureAddress() != null && |
| | | (updateVO.getDepartureLongitude() == null || updateVO.getDepartureLatitude() == null) && |
| | | mapService != null) { |
| | | try { |
| | | Map<String, Double> coords = mapService.geocoding( |
| | | updateVO.getDepartureAddress(), |
| | | extractCityFromAddress(updateVO.getDepartureAddress()) |
| | | ); |
| | | if (coords != null) { |
| | | task.setDepartureLongitude(BigDecimal.valueOf(coords.get("lng"))); |
| | | task.setDepartureLatitude(BigDecimal.valueOf(coords.get("lat"))); |
| | | log.info("出发地GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat")); |
| | | if (!updateVO.getDepartureAddress().equals(oldTask.getDepartureAddress())) { |
| | | try { |
| | | Map<String, Double> coords = mapService.geocoding( |
| | | updateVO.getDepartureAddress(), |
| | | extractCityFromAddress(updateVO.getDepartureAddress()) |
| | | ); |
| | | if (coords != null) { |
| | | task.setDepartureLongitude(BigDecimal.valueOf(coords.get("lng"))); |
| | | task.setDepartureLatitude(BigDecimal.valueOf(coords.get("lat"))); |
| | | log.info("出发地GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat")); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("自动获取出发地GPS坐标失败", e); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("自动获取出发地GPS坐标失败", e); |
| | | } |
| | | } |
| | | |
| | |
| | | if (updateVO.getDestinationAddress() != null && |
| | | (updateVO.getDestinationLongitude() == null || updateVO.getDestinationLatitude() == null) && |
| | | mapService != null) { |
| | | try { |
| | | Map<String, Double> coords = mapService.geocoding( |
| | | updateVO.getDestinationAddress(), |
| | | extractCityFromAddress(updateVO.getDestinationAddress()) |
| | | ); |
| | | if (coords != null) { |
| | | task.setDestinationLongitude(BigDecimal.valueOf(coords.get("lng"))); |
| | | task.setDestinationLatitude(BigDecimal.valueOf(coords.get("lat"))); |
| | | log.info("目的地GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat")); |
| | | if (!updateVO.getDestinationAddress().equals(oldTask.getDestinationAddress())) { |
| | | try { |
| | | Map<String, Double> coords = mapService.geocoding( |
| | | updateVO.getDestinationAddress(), |
| | | extractCityFromAddress(updateVO.getDestinationAddress()) |
| | | ); |
| | | if (coords != null) { |
| | | task.setDestinationLongitude(BigDecimal.valueOf(coords.get("lng"))); |
| | | task.setDestinationLatitude(BigDecimal.valueOf(coords.get("lat"))); |
| | | log.info("目的地GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat")); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("自动获取目的地GPS坐标失败", e); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("自动获取目的地GPS坐标失败", e); |
| | | } |
| | | } |
| | | |
| | | // 重新计算预计公里数 |
| | | calculateEstimatedDistance(task); |
| | | |
| | | // 用于跟踪是否需要重新同步(车辆、人员、地址、成交价变更) |
| | | boolean needResync = true; |
| | | int result = sysTaskMapper.updateSysTask(task); |
| | | |
| | | // 用于跟踪是否需要重新同步(车辆、人员、地址、成交价变更) |
| | | boolean needResync = false; |
| | | |
| | | |
| | | // 更新车辆关联 |
| | | if (result > 0 && updateVO.getVehicleIds() != null && !updateVO.getVehicleIds().isEmpty()) { |
| | |
| | | |
| | | // 添加新的车辆关联 |
| | | Date now = DateUtils.getNowDate(); |
| | | String currentUser = SecurityUtils.getUsername(); |
| | | String currentUser = userName; |
| | | for (Long vehicleId : updateVO.getVehicleIds()) { |
| | | SysTaskVehicle taskVehicle = new SysTaskVehicle(); |
| | | taskVehicle.setTaskId(updateVO.getTaskId()); |
| | |
| | | .collect(Collectors.toList()); |
| | | |
| | | List<Long> newAssigneeIds = updateVO.getAssignees().stream() |
| | | .map(TaskUpdateVO.AssigneeInfo::getUserId) |
| | | .map(TaskCreateVO.AssigneeInfo::getUserId) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 比较新旧执行人员ID列表,判断是否有变化 |
| | |
| | | |
| | | // 添加新的执行人员关联 |
| | | if (!updateVO.getAssignees().isEmpty()) { |
| | | // 将 TaskUpdateVO.AssigneeInfo 转换为 TaskCreateVO.AssigneeInfo |
| | | List<TaskCreateVO.AssigneeInfo> createAssignees = updateVO.getAssignees().stream() |
| | | .map(assignee -> { |
| | | TaskCreateVO.AssigneeInfo createAssignee = new TaskCreateVO.AssigneeInfo(); |
| | | createAssignee.setUserId(assignee.getUserId()); |
| | | createAssignee.setUserName(assignee.getUserName()); |
| | | createAssignee.setUserType(assignee.getUserType()); |
| | | return createAssignee; |
| | | }) |
| | | .collect(Collectors.toList()); |
| | | saveTaskAssignees(updateVO.getTaskId(), createAssignees, SecurityUtils.getUsername()); |
| | | saveTaskAssignees(updateVO.getTaskId(), updateVO.getAssignees(), userName); |
| | | } |
| | | |
| | | // 标记需要重新同步(人员变更) |
| | |
| | | } |
| | | |
| | | // 更新急救转运扩展信息(检测地址和成交价变更) |
| | | if (result > 0 && "EMERGENCY_TRANSFER".equals(oldTask.getTaskType()) && updateVO.getEmergencyInfo() != null) { |
| | | if (result > 0 && "EMERGENCY_TRANSFER".equals(oldTask.getTaskType())) { |
| | | // 获取旧的急救转运信息 |
| | | SysTaskEmergency oldEmergency = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(updateVO.getTaskId()); |
| | | |
| | | // 检测转出医院地址变更 |
| | | boolean hospitalOutAddressChanged = false; |
| | | if (updateVO.getEmergencyInfo().getHospitalOutAddress() != null |
| | | && oldEmergency != null |
| | | && !updateVO.getEmergencyInfo().getHospitalOutAddress().equals(oldEmergency.getHospitalOutAddress())) { |
| | | if (updateVO.getHospitalOut() != null && updateVO.getHospitalOut().getAddress() != null |
| | | && oldEmergency != null |
| | | && !updateVO.getHospitalOut().getAddress().equals(oldEmergency.getHospitalOutAddress())) { |
| | | hospitalOutAddressChanged = true; |
| | | } |
| | | |
| | | // 检测转入医院地址变更 |
| | | boolean hospitalInAddressChanged = false; |
| | | if (updateVO.getEmergencyInfo().getHospitalInAddress() != null |
| | | && oldEmergency != null |
| | | && !updateVO.getEmergencyInfo().getHospitalInAddress().equals(oldEmergency.getHospitalInAddress())) { |
| | | if (updateVO.getHospitalIn() != null && updateVO.getHospitalIn().getAddress() != null |
| | | && oldEmergency != null |
| | | && !updateVO.getHospitalIn().getAddress().equals(oldEmergency.getHospitalInAddress())) { |
| | | hospitalInAddressChanged = true; |
| | | } |
| | | |
| | | // 检测成交价变更 |
| | | boolean transferPriceChanged = false; |
| | | if (updateVO.getEmergencyInfo().getTransferPrice() != null |
| | | && oldEmergency != null |
| | | if (updateVO.getPrice() != null |
| | | && oldEmergency != null |
| | | && oldEmergency.getTransferPrice() != null |
| | | && updateVO.getEmergencyInfo().getTransferPrice().compareTo(oldEmergency.getTransferPrice()) != 0) { |
| | | && updateVO.getPrice().compareTo(oldEmergency.getTransferPrice()) != 0) { |
| | | transferPriceChanged = true; |
| | | } |
| | | |
| | | ; |
| | | |
| | | // 更新急救转运信息 |
| | | updateEmergencyInfo(updateVO.getTaskId(), updateVO); |
| | | if (updateVO.getHospitalOut() != null || updateVO.getHospitalIn() != null || updateVO.getPatient() != null) { |
| | | updateEmergencyInfoFromCreateVO(updateVO.getTaskId(), updateVO, userName); |
| | | } |
| | | |
| | | // 如果地址或成交价发生变更,标记需要重新同步 |
| | | if (hospitalOutAddressChanged || hospitalInAddressChanged || transferPriceChanged) { |
| | |
| | | } |
| | | } |
| | | |
| | | // 更新福祉车扩展信息 |
| | | if (result > 0 && "WELFARE".equals(oldTask.getTaskType())) { |
| | | if (updateVO.getPassenger() != null || updateVO.getStartAddress() != null || updateVO.getEndAddress() != null) { |
| | | updateWelfareInfoFromCreateVO(updateVO.getTaskId(), updateVO, userName); |
| | | } |
| | | } |
| | | |
| | | // 如果是急救转运任务且有变更,标记需要重新同步 |
| | | if (result > 0 && "EMERGENCY_TRANSFER".equals(oldTask.getTaskType()) && needResync) { |
| | | if (result > 0 && "EMERGENCY_TRANSFER".equals(oldTask.getTaskType()) && needResync && !updateFromLegacy) { |
| | | try { |
| | | sysTaskEmergencyService.markNeedResync(updateVO.getTaskId()); |
| | | } catch (Exception e) { |
| | |
| | | |
| | | // 记录操作日志 |
| | | if (result > 0) { |
| | | recordTaskLog(updateVO.getTaskId(), "UPDATE", "更新任务", |
| | | buildTaskDescription(oldTask), buildTaskDescription(task), |
| | | SecurityUtils.getUserId(), SecurityUtils.getUsername()); |
| | | recordTaskLog(updateVO.getTaskId(), "UPDATE", "更新任务", |
| | | buildTaskDescription(oldTask), buildTaskDescription(task), |
| | | SecurityUtils.getUserId(), userName); |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 更新任务(用于旧系统同步) |
| | | * |
| | | * @param updateVO 任务更新对象 |
| | | * @param serviceOrderId 旧系统服务单ID |
| | | * @param dispatchOrderId 旧系统调度单ID |
| | | * @param serviceOrdNo 旧系统服务单编号 |
| | | * @param userId 用户ID |
| | | * @param userName 用户名 |
| | | * @param deptId 部门ID |
| | | * @param createTime 创建时间 |
| | | * @param updateTime 更新时间 |
| | | * @return 结果 |
| | | */ |
| | | @Override |
| | | public int updateTask(TaskUpdateVO updateVO, String serviceOrderId, String dispatchOrderId, String serviceOrdNo, |
| | | Long userId, String userName, Long deptId, Date createTime, Date updateTime) { |
| | | // 通过旧系统服务单ID查找任务 |
| | | SysTaskEmergency taskEmergency = sysTaskEmergencyMapper.selectByLegacyServiceOrdId(Long.parseLong(serviceOrderId)); |
| | | Long taskId = taskEmergency.getTaskId(); |
| | | updateVO.setTaskId(taskId); |
| | | SysTask task = new SysTask(); |
| | | task.setTaskId(taskId); |
| | | task.setTaskDescription(updateVO.getTaskDescription()); |
| | | task.setPlannedStartTime(updateVO.getPlannedStartTime()); |
| | | task.setPlannedEndTime(updateVO.getPlannedEndTime()); |
| | | task.setAssigneeId(updateVO.getAssigneeId()); |
| | | task.setUpdateBy(userName); |
| | | task.setUpdateTime(DateUtils.getNowDate()); |
| | | task.setRemark(updateVO.getRemark()); |
| | | |
| | | |
| | | // 设置地址和坐标信息 |
| | | task.setDepartureAddress(updateVO.getDepartureAddress()); |
| | | task.setDestinationAddress(updateVO.getDestinationAddress()); |
| | | task.setDepartureLongitude(updateVO.getDepartureLongitude()); |
| | | task.setDepartureLatitude(updateVO.getDepartureLatitude()); |
| | | task.setDestinationLongitude(updateVO.getDestinationLongitude()); |
| | | task.setDestinationLatitude(updateVO.getDestinationLatitude()); |
| | | |
| | | // 如果更新了部门ID |
| | | if (updateVO.getDeptId() != null) { |
| | | task.setDeptId(updateVO.getDeptId()); |
| | | } |
| | | |
| | | // 如果更新了任务编号 |
| | | if (updateVO.getTaskCode() != null) { |
| | | task.setTaskCode(updateVO.getTaskCode()); |
| | | } |
| | | |
| | | // 获取旧任务信息,用于判断地址是否变更 |
| | | SysTask oldTask = sysTaskMapper.selectSysTaskByTaskId(taskId); |
| | | |
| | | // 自动获取出发地GPS坐标(如果地址变更且缺失坐标) |
| | | if (oldTask != null && updateVO.getDepartureAddress() != null |
| | | && !updateVO.getDepartureAddress().equals(oldTask.getDepartureAddress()) |
| | | && (updateVO.getDepartureLongitude() == null || updateVO.getDepartureLatitude() == null) |
| | | && mapService != null) { |
| | | try { |
| | | Map<String, Double> coords = mapService.geocoding( |
| | | updateVO.getDepartureAddress(), |
| | | extractCityFromAddress(updateVO.getDepartureAddress()) |
| | | ); |
| | | if (coords != null) { |
| | | task.setDepartureLongitude(BigDecimal.valueOf(coords.get("lng"))); |
| | | task.setDepartureLatitude(BigDecimal.valueOf(coords.get("lat"))); |
| | | log.info("出发地GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat")); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("自动获取出发地GPS坐标失败", e); |
| | | } |
| | | } |
| | | |
| | | // 自动获取目的地GPS坐标(如果地址变更且缺失坐标) |
| | | if (oldTask != null && updateVO.getDestinationAddress() != null |
| | | && !updateVO.getDestinationAddress().equals(oldTask.getDestinationAddress()) |
| | | && (updateVO.getDestinationLongitude() == null || updateVO.getDestinationLatitude() == null) |
| | | && mapService != null) { |
| | | try { |
| | | Map<String, Double> coords = mapService.geocoding( |
| | | updateVO.getDestinationAddress(), |
| | | extractCityFromAddress(updateVO.getDestinationAddress()) |
| | | ); |
| | | if (coords != null) { |
| | | task.setDestinationLongitude(BigDecimal.valueOf(coords.get("lng"))); |
| | | task.setDestinationLatitude(BigDecimal.valueOf(coords.get("lat"))); |
| | | log.info("目的地GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat")); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("自动获取目的地GPS坐标失败", e); |
| | | } |
| | | } |
| | | |
| | | int result = sysTaskMapper.updateSysTask(task); |
| | | |
| | | // 更新车辆关联 |
| | | if (result > 0 && updateVO.getVehicleIds() != null && !updateVO.getVehicleIds().isEmpty()) { |
| | | // 查询现有的车辆关联 |
| | | List<SysTaskVehicle> existingVehicles = sysTaskVehicleMapper.selectSysTaskVehicleByTaskId(taskId); |
| | | List<Long> existingVehicleIds = existingVehicles.stream() |
| | | .map(SysTaskVehicle::getVehicleId) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 比较新旧车辆ID列表,判断是否有变化 |
| | | boolean vehiclesChanged = !new HashSet<>(existingVehicleIds).equals(new HashSet<>(updateVO.getVehicleIds())); |
| | | |
| | | // 只有车辆发生变化时才更新 |
| | | if (vehiclesChanged) { |
| | | // 删除旧的车辆关联 |
| | | sysTaskVehicleMapper.deleteSysTaskVehicleByTaskId(taskId); |
| | | |
| | | // 添加新的车辆关联 |
| | | Date now = DateUtils.getNowDate(); |
| | | for (Long vehicleId : updateVO.getVehicleIds()) { |
| | | SysTaskVehicle taskVehicle = new SysTaskVehicle(); |
| | | taskVehicle.setTaskId(taskId); |
| | | taskVehicle.setVehicleId(vehicleId); |
| | | taskVehicle.setAssignTime(now); |
| | | taskVehicle.setAssignBy(userName); |
| | | taskVehicle.setCreateTime(now); |
| | | sysTaskVehicleMapper.insertSysTaskVehicle(taskVehicle); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 更新执行人员(检测人员变更) |
| | | if (result > 0 && updateVO.getAssignees() != null) { |
| | | // 查询现有的执行人员 |
| | | List<SysTaskAssignee> existingAssignees = sysTaskAssigneeMapper.selectSysTaskAssigneeByTaskId(taskId); |
| | | List<Long> existingAssigneeIds = existingAssignees.stream() |
| | | .map(SysTaskAssignee::getUserId) |
| | | .collect(Collectors.toList()); |
| | | |
| | | List<Long> newAssigneeIds = updateVO.getAssignees().stream() |
| | | .map(TaskCreateVO.AssigneeInfo::getUserId) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 比较新旧执行人员ID列表,判断是否有变化 |
| | | boolean assigneesChanged = !new HashSet<>(existingAssigneeIds).equals(new HashSet<>(newAssigneeIds)); |
| | | |
| | | // 只有执行人员发生变化时才更新 |
| | | if (assigneesChanged) { |
| | | // 删除旧的执行人员关联 |
| | | sysTaskAssigneeMapper.deleteSysTaskAssigneeByTaskId(taskId); |
| | | |
| | | // 添加新的执行人员关联 |
| | | if (!updateVO.getAssignees().isEmpty()) { |
| | | saveTaskAssignees(taskId, updateVO.getAssignees(), userName); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 更新急救转运扩展信息 |
| | | if (result > 0) { |
| | | // 更新旧系统ID |
| | | if (serviceOrderId != null) { |
| | | taskEmergency.setLegacyServiceOrdId(Long.parseLong(serviceOrderId)); |
| | | } |
| | | if (dispatchOrderId != null) { |
| | | taskEmergency.setLegacyDispatchOrdId(Long.parseLong(dispatchOrderId)); |
| | | } |
| | | if (serviceOrdNo != null) { |
| | | taskEmergency.setLegacyServiceOrdNo(serviceOrdNo); |
| | | } |
| | | |
| | | // 使用TaskCreateVO的字段来更新急救转运信息 |
| | | if (updateVO.getHospitalOut() != null || updateVO.getHospitalIn() != null || updateVO.getPatient() != null) { |
| | | updateEmergencyInfoFromCreateVO(taskId, updateVO, userName); |
| | | } |
| | | } |
| | | |
| | | return result; |
| | |
| | | */ |
| | | @Override |
| | | @Transactional |
| | | public int assignVehicleToTask(Long taskId, Long vehicleId, String remark) { |
| | | public int assignVehicleToTask(Long taskId, Long vehicleId, String remark,Long userId,String userName) { |
| | | // 检查是否已经分配 |
| | | int exists = sysTaskVehicleMapper.checkTaskVehicleExists(taskId, vehicleId); |
| | | if (exists > 0) { |
| | |
| | | taskVehicle.setTaskId(taskId); |
| | | taskVehicle.setVehicleId(vehicleId); |
| | | taskVehicle.setAssignTime(DateUtils.getNowDate()); |
| | | taskVehicle.setAssignBy(SecurityUtils.getUsername()); |
| | | taskVehicle.setAssignBy(userName); |
| | | taskVehicle.setStatus("ASSIGNED"); |
| | | taskVehicle.setRemark(remark); |
| | | |
| | |
| | | if (result > 0) { |
| | | recordTaskLog(taskId, "ASSIGN", "分配车辆", null, |
| | | "分配车辆ID:" + vehicleId + ",备注:" + remark, |
| | | SecurityUtils.getUserId(), SecurityUtils.getUsername()); |
| | | userId, userName); |
| | | } |
| | | |
| | | return result; |
| | |
| | | */ |
| | | @Override |
| | | @Transactional |
| | | public int assignMultipleVehiclesToTask(Long taskId, List<Long> vehicleIds, String remark) { |
| | | public int assignMultipleVehiclesToTask(Long taskId, List<Long> vehicleIds, String remark,Long userId,String userName) { |
| | | List<SysTaskVehicle> taskVehicles = new ArrayList<>(); |
| | | Date now = DateUtils.getNowDate(); |
| | | String assignBy = SecurityUtils.getUsername(); |
| | | String assignBy = userName; |
| | | |
| | | for (Long vehicleId : vehicleIds) { |
| | | // 检查是否已经分配 |
| | |
| | | if (result > 0) { |
| | | recordTaskLog(taskId, "ASSIGN", "批量分配车辆", null, |
| | | "分配车辆数量:" + result + ",备注:" + remark, |
| | | SecurityUtils.getUserId(), SecurityUtils.getUsername()); |
| | | userId,userName); |
| | | } |
| | | |
| | | return result; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 更新急救转运任务扩展信息 |
| | | * 从 TaskCreateVO 更新急救转运任务扩展信息 |
| | | * |
| | | * @param taskId 任务ID |
| | | * @param updateVO 任务更新对象 |
| | | * @param createVO 任务创建/更新对象 |
| | | * @param userName 操作人名 |
| | | */ |
| | | private void updateEmergencyInfo(Long taskId, TaskUpdateVO updateVO) { |
| | | private void updateEmergencyInfoFromCreateVO(Long taskId, TaskCreateVO createVO, String userName) { |
| | | // 查询现有的扩展信息 |
| | | SysTaskEmergency existingInfo = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(taskId); |
| | | if (existingInfo == null) { |
| | |
| | | existingInfo = new SysTaskEmergency(); |
| | | existingInfo.setTaskId(taskId); |
| | | existingInfo.setCreateTime(DateUtils.getNowDate()); |
| | | existingInfo.setCreateBy(SecurityUtils.getUsername()); |
| | | existingInfo.setCreateBy(userName); |
| | | } |
| | | |
| | | TaskUpdateVO.EmergencyInfoVO emergencyInfo = updateVO.getEmergencyInfo(); |
| | | |
| | | // 更新患者信息 |
| | | if (emergencyInfo.getPatientContact() != null) { |
| | | existingInfo.setPatientContact(emergencyInfo.getPatientContact()); |
| | | } |
| | | if (emergencyInfo.getPatientPhone() != null) { |
| | | existingInfo.setPatientPhone(emergencyInfo.getPatientPhone()); |
| | | } |
| | | if (emergencyInfo.getPatientName() != null) { |
| | | existingInfo.setPatientName(emergencyInfo.getPatientName()); |
| | | } |
| | | if (emergencyInfo.getPatientGender() != null) { |
| | | existingInfo.setPatientGender(emergencyInfo.getPatientGender()); |
| | | } |
| | | if (emergencyInfo.getPatientIdCard() != null) { |
| | | existingInfo.setPatientIdCard(emergencyInfo.getPatientIdCard()); |
| | | } |
| | | if (emergencyInfo.getPatientCondition() != null) { |
| | | existingInfo.setPatientCondition(emergencyInfo.getPatientCondition()); |
| | | if (createVO.getPatient() != null) { |
| | | if (createVO.getPatient().getContact() != null) { |
| | | existingInfo.setPatientContact(createVO.getPatient().getContact()); |
| | | } |
| | | if (createVO.getPatient().getPhone() != null) { |
| | | existingInfo.setPatientPhone(createVO.getPatient().getPhone()); |
| | | } |
| | | if (createVO.getPatient().getName() != null) { |
| | | existingInfo.setPatientName(createVO.getPatient().getName()); |
| | | } |
| | | if (createVO.getPatient().getGender() != null) { |
| | | existingInfo.setPatientGender(createVO.getPatient().getGender()); |
| | | } |
| | | if (createVO.getPatient().getIdCard() != null) { |
| | | existingInfo.setPatientIdCard(createVO.getPatient().getIdCard()); |
| | | } |
| | | if (createVO.getPatient().getCondition() != null) { |
| | | existingInfo.setPatientCondition(createVO.getPatient().getCondition()); |
| | | } |
| | | } |
| | | |
| | | // 更新转出医院信息 |
| | | if (emergencyInfo.getHospitalOutId() != null) { |
| | | existingInfo.setHospitalOutId(emergencyInfo.getHospitalOutId()); |
| | | } |
| | | if (emergencyInfo.getHospitalOutName() != null) { |
| | | existingInfo.setHospitalOutName(emergencyInfo.getHospitalOutName()); |
| | | } |
| | | if (emergencyInfo.getHospitalOutDepartment() != null) { |
| | | existingInfo.setHospitalOutDepartment(emergencyInfo.getHospitalOutDepartment()); |
| | | } |
| | | if (emergencyInfo.getHospitalOutDepartmentId() != null) { |
| | | existingInfo.setHospitalOutDepartmentId(emergencyInfo.getHospitalOutDepartmentId()); |
| | | } |
| | | if (emergencyInfo.getHospitalOutBedNumber() != null) { |
| | | existingInfo.setHospitalOutBedNumber(emergencyInfo.getHospitalOutBedNumber()); |
| | | } |
| | | if (emergencyInfo.getHospitalOutAddress() != null) { |
| | | existingInfo.setHospitalOutAddress(emergencyInfo.getHospitalOutAddress()); |
| | | |
| | | // 如果更新了地址但没有GPS坐标,后端自动获取 |
| | | if (emergencyInfo.getHospitalOutLongitude() == null && emergencyInfo.getHospitalOutLatitude() == null && mapService != null) { |
| | | try { |
| | | Map<String, Double> coords = mapService.geocoding( |
| | | emergencyInfo.getHospitalOutAddress(), |
| | | extractCityFromAddress(emergencyInfo.getHospitalOutAddress()) |
| | | ); |
| | | if (coords != null) { |
| | | existingInfo.setHospitalOutLongitude(BigDecimal.valueOf(coords.get("lng"))); |
| | | existingInfo.setHospitalOutLatitude(BigDecimal.valueOf(coords.get("lat"))); |
| | | log.info("转出医院GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat")); |
| | | if (createVO.getHospitalOut() != null) { |
| | | if (createVO.getHospitalOut().getId() != null) { |
| | | existingInfo.setHospitalOutId(createVO.getHospitalOut().getId()); |
| | | } |
| | | if (createVO.getHospitalOut().getName() != null) { |
| | | existingInfo.setHospitalOutName(createVO.getHospitalOut().getName()); |
| | | } |
| | | if (createVO.getHospitalOut().getDepartment() != null) { |
| | | existingInfo.setHospitalOutDepartment(createVO.getHospitalOut().getDepartment()); |
| | | } |
| | | if (createVO.getHospitalOut().getDepartmentId() != null) { |
| | | existingInfo.setHospitalOutDepartmentId(createVO.getHospitalOut().getDepartmentId()); |
| | | } |
| | | if (createVO.getHospitalOut().getBedNumber() != null) { |
| | | existingInfo.setHospitalOutBedNumber(createVO.getHospitalOut().getBedNumber()); |
| | | } |
| | | if (createVO.getHospitalOut().getAddress() != null && !createVO.getHospitalOut().getAddress().equals(existingInfo.getHospitalOutAddress())) { |
| | | existingInfo.setHospitalOutAddress(createVO.getHospitalOut().getAddress()); |
| | | |
| | | // 如枟更新了地址但没有GPS坐标,后端自动获取 |
| | | if (createVO.getHospitalOut().getLongitude() == null && createVO.getHospitalOut().getLatitude() == null && mapService != null) { |
| | | try { |
| | | |
| | | Map<String, Double> coords = mapService.geocoding( |
| | | createVO.getHospitalOut().getAddress(), |
| | | extractCityFromAddress(createVO.getHospitalOut().getAddress()) |
| | | ); |
| | | if (coords != null) { |
| | | existingInfo.setHospitalOutLongitude(BigDecimal.valueOf(coords.get("lng"))); |
| | | existingInfo.setHospitalOutLatitude(BigDecimal.valueOf(coords.get("lat"))); |
| | | log.info("转出医院GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat")); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("自动获取转出医院GPS坐标失败", e); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("自动获取转出医院GPS坐标失败", e); |
| | | } |
| | | } |
| | | } |
| | | if (emergencyInfo.getHospitalOutLongitude() != null) { |
| | | existingInfo.setHospitalOutLongitude(emergencyInfo.getHospitalOutLongitude()); |
| | | } |
| | | if (emergencyInfo.getHospitalOutLatitude() != null) { |
| | | existingInfo.setHospitalOutLatitude(emergencyInfo.getHospitalOutLatitude()); |
| | | if (createVO.getHospitalOut().getLongitude() != null) { |
| | | existingInfo.setHospitalOutLongitude(createVO.getHospitalOut().getLongitude()); |
| | | } |
| | | if (createVO.getHospitalOut().getLatitude() != null) { |
| | | existingInfo.setHospitalOutLatitude(createVO.getHospitalOut().getLatitude()); |
| | | } |
| | | } |
| | | |
| | | // 更新转入医院信息 |
| | | if (emergencyInfo.getHospitalInId() != null) { |
| | | existingInfo.setHospitalInId(emergencyInfo.getHospitalInId()); |
| | | } |
| | | if (emergencyInfo.getHospitalInName() != null) { |
| | | existingInfo.setHospitalInName(emergencyInfo.getHospitalInName()); |
| | | } |
| | | if (emergencyInfo.getHospitalInDepartment() != null) { |
| | | existingInfo.setHospitalInDepartment(emergencyInfo.getHospitalInDepartment()); |
| | | } |
| | | if (emergencyInfo.getHospitalInDepartmentId() != null) { |
| | | existingInfo.setHospitalInDepartmentId(emergencyInfo.getHospitalInDepartmentId()); |
| | | } |
| | | if (emergencyInfo.getHospitalInBedNumber() != null) { |
| | | existingInfo.setHospitalInBedNumber(emergencyInfo.getHospitalInBedNumber()); |
| | | } |
| | | if (emergencyInfo.getHospitalInAddress() != null) { |
| | | existingInfo.setHospitalInAddress(emergencyInfo.getHospitalInAddress()); |
| | | |
| | | // 如果更新了地址但没有GPS坐标,后端自动获取 |
| | | if (emergencyInfo.getHospitalInLongitude() == null && emergencyInfo.getHospitalInLatitude() == null && mapService != null) { |
| | | try { |
| | | Map<String, Double> coords = mapService.geocoding( |
| | | emergencyInfo.getHospitalInAddress(), |
| | | extractCityFromAddress(emergencyInfo.getHospitalInAddress()) |
| | | ); |
| | | if (coords != null) { |
| | | existingInfo.setHospitalInLongitude(BigDecimal.valueOf(coords.get("lng"))); |
| | | existingInfo.setHospitalInLatitude(BigDecimal.valueOf(coords.get("lat"))); |
| | | log.info("转入医院GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat")); |
| | | if (createVO.getHospitalIn() != null) { |
| | | if (createVO.getHospitalIn().getId() != null) { |
| | | existingInfo.setHospitalInId(createVO.getHospitalIn().getId()); |
| | | } |
| | | if (createVO.getHospitalIn().getName() != null) { |
| | | existingInfo.setHospitalInName(createVO.getHospitalIn().getName()); |
| | | } |
| | | if (createVO.getHospitalIn().getDepartment() != null) { |
| | | existingInfo.setHospitalInDepartment(createVO.getHospitalIn().getDepartment()); |
| | | } |
| | | if (createVO.getHospitalIn().getDepartmentId() != null) { |
| | | existingInfo.setHospitalInDepartmentId(createVO.getHospitalIn().getDepartmentId()); |
| | | } |
| | | if (createVO.getHospitalIn().getBedNumber() != null) { |
| | | existingInfo.setHospitalInBedNumber(createVO.getHospitalIn().getBedNumber()); |
| | | } |
| | | if (createVO.getHospitalIn().getAddress() != null && !createVO.getHospitalIn().getAddress().equals(existingInfo.getHospitalInAddress())) { |
| | | existingInfo.setHospitalInAddress(createVO.getHospitalIn().getAddress()); |
| | | |
| | | // 如果更新了地址但没有GPS坐标,后端自动获取 |
| | | if (createVO.getHospitalIn().getLongitude() == null && createVO.getHospitalIn().getLatitude() == null && mapService != null) { |
| | | try { |
| | | Map<String, Double> coords = mapService.geocoding( |
| | | createVO.getHospitalIn().getAddress(), |
| | | extractCityFromAddress(createVO.getHospitalIn().getAddress()) |
| | | ); |
| | | if (coords != null) { |
| | | existingInfo.setHospitalInLongitude(BigDecimal.valueOf(coords.get("lng"))); |
| | | existingInfo.setHospitalInLatitude(BigDecimal.valueOf(coords.get("lat"))); |
| | | log.info("转入医院GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat")); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("自动获取转入医院GPS坐标失败", e); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("自动获取转入医院GPS坐标失败", e); |
| | | } |
| | | } |
| | | } |
| | | if (emergencyInfo.getHospitalInLongitude() != null) { |
| | | existingInfo.setHospitalInLongitude(emergencyInfo.getHospitalInLongitude()); |
| | | } |
| | | if (emergencyInfo.getHospitalInLatitude() != null) { |
| | | existingInfo.setHospitalInLatitude(emergencyInfo.getHospitalInLatitude()); |
| | | if (createVO.getHospitalIn().getLongitude() != null) { |
| | | existingInfo.setHospitalInLongitude(createVO.getHospitalIn().getLongitude()); |
| | | } |
| | | if (createVO.getHospitalIn().getLatitude() != null) { |
| | | existingInfo.setHospitalInLatitude(createVO.getHospitalIn().getLatitude()); |
| | | } |
| | | } |
| | | |
| | | // 更新费用信息 |
| | | if (emergencyInfo.getTransferDistance() != null) { |
| | | existingInfo.setTransferDistance(emergencyInfo.getTransferDistance()); |
| | | if (createVO.getTransferDistance() != null) { |
| | | existingInfo.setTransferDistance(createVO.getTransferDistance()); |
| | | } |
| | | if (emergencyInfo.getTransferPrice() != null) { |
| | | existingInfo.setTransferPrice(emergencyInfo.getTransferPrice()); |
| | | if (createVO.getPrice() != null) { |
| | | existingInfo.setTransferPrice(createVO.getPrice()); |
| | | } |
| | | |
| | | // 更新单据类型ID |
| | | if (createVO.getDocumentTypeId() != null) { |
| | | existingInfo.setDocumentTypeId(createVO.getDocumentTypeId()); |
| | | } |
| | | |
| | | // 更新任务类型ID |
| | | if (createVO.getTaskTypeId() != null) { |
| | | existingInfo.setTaskTypeId(createVO.getTaskTypeId()); |
| | | } |
| | | |
| | | // 更新病情ID列表 |
| | | if (updateVO.getDiseaseIds() != null && !updateVO.getDiseaseIds().isEmpty()) { |
| | | String diseaseIdsStr = updateVO.getDiseaseIds().stream() |
| | | if (createVO.getDiseaseIds() != null && !createVO.getDiseaseIds().isEmpty()) { |
| | | String diseaseIdsStr = createVO.getDiseaseIds().stream() |
| | | .map(String::valueOf) |
| | | .collect(Collectors.joining(",")); |
| | | existingInfo.setDiseaseIds(diseaseIdsStr); |
| | | } else { |
| | | // 如果病情ID列表为空,清空该字段 |
| | | existingInfo.setDiseaseIds(null); |
| | | } |
| | | |
| | | // 系统字段 |
| | | existingInfo.setUpdateTime(DateUtils.getNowDate()); |
| | | existingInfo.setUpdateBy(SecurityUtils.getUsername()); |
| | | existingInfo.setUpdateBy(userName); |
| | | |
| | | // 执行更新 |
| | | sysTaskEmergencyMapper.updateSysTaskEmergency(existingInfo); |
| | | } |
| | | |
| | | /** |
| | | * 从 TaskCreateVO 更新福祉车任务扩展信息 |
| | | * |
| | | * @param taskId 任务ID |
| | | * @param createVO 任务创建/更新对象 |
| | | * @param userName 操作人名 |
| | | */ |
| | | private void updateWelfareInfoFromCreateVO(Long taskId, TaskCreateVO createVO, String userName) { |
| | | // 查询现有的扩展信息 |
| | | SysTaskWelfare existingInfo = sysTaskWelfareMapper.selectSysTaskWelfareByTaskId(taskId); |
| | | if (existingInfo == null) { |
| | | // 如果不存在,则创建新的 |
| | | existingInfo = new SysTaskWelfare(); |
| | | existingInfo.setTaskId(taskId); |
| | | existingInfo.setCreateTime(DateUtils.getNowDate()); |
| | | existingInfo.setCreateBy(userName); |
| | | } |
| | | |
| | | // 更新乘客信息 |
| | | if (createVO.getPassenger() != null) { |
| | | if (createVO.getPassenger().getContact() != null) { |
| | | existingInfo.setPassengerContact(createVO.getPassenger().getContact()); |
| | | } |
| | | if (createVO.getPassenger().getPhone() != null) { |
| | | existingInfo.setPassengerPhone(createVO.getPassenger().getPhone()); |
| | | } |
| | | } |
| | | |
| | | // 更新地址信息 |
| | | if (createVO.getStartAddress() != null) { |
| | | existingInfo.setPickupAddress(createVO.getStartAddress()); |
| | | } |
| | | if (createVO.getEndAddress() != null) { |
| | | existingInfo.setDestinationAddress(createVO.getEndAddress()); |
| | | } |
| | | |
| | | // 更新GPS坐标 |
| | | if (createVO.getDepartureLongitude() != null) { |
| | | existingInfo.setPickupLongitude(createVO.getDepartureLongitude()); |
| | | } |
| | | if (createVO.getDepartureLatitude() != null) { |
| | | existingInfo.setPickupLatitude(createVO.getDepartureLatitude()); |
| | | } |
| | | if (createVO.getDestinationLongitude() != null) { |
| | | existingInfo.setDestinationLongitude(createVO.getDestinationLongitude()); |
| | | } |
| | | if (createVO.getDestinationLatitude() != null) { |
| | | existingInfo.setDestinationLatitude(createVO.getDestinationLatitude()); |
| | | } |
| | | |
| | | // 更新距离和费用 |
| | | if (createVO.getDistance() != null) { |
| | | existingInfo.setServiceDistance(createVO.getDistance()); |
| | | } else if (createVO.getEstimatedDistance() != null) { |
| | | existingInfo.setServiceDistance(createVO.getEstimatedDistance()); |
| | | } |
| | | if (createVO.getPrice() != null) { |
| | | existingInfo.setServicePrice(createVO.getPrice()); |
| | | } |
| | | |
| | | // 系统字段 |
| | | existingInfo.setUpdateTime(DateUtils.getNowDate()); |
| | | existingInfo.setUpdateBy(userName); |
| | | |
| | | // 执行更新 |
| | | sysTaskWelfareMapper.updateSysTaskWelfare(existingInfo); |
| | | } |
| | | |
| | | /** |
| | | * 保存福祉车任务扩展信息 |
| | |
| | | <!-- 执行人结果映射 --> |
| | | <resultMap type="java.util.HashMap" id="AssigneeResult"> |
| | | <result property="EntourageOAId" column="EntourageOAId" /> |
| | | <result property="EntourageID" column="EntourageID" /> |
| | | <result property="EntourageState" column="EntourageState" /> |
| | | </resultMap> |
| | | |
| | |
| | | <select id="selectAssigneesByDispatchOrdID" resultMap="AssigneeResult"> |
| | | SELECT |
| | | EntourageOAId, |
| | | EntourageID, |
| | | EntourageState |
| | | FROM DispatchOrd_Entourage |
| | | WHERE DispatchOrdIDDt = #{dispatchOrdID} |
| | |
| | | <if test="quantity != null">quantity,</if> |
| | | <if test="totalAmount != null">total_amount,</if> |
| | | <if test="remark != null">remark,</if> |
| | | <if test="pid != null">pid,</if> |
| | | <if test="syncStatus != null">sync_status,</if> |
| | | <if test="syncTime != null">sync_time,</if> |
| | | <if test="createdBy != null and createdBy != ''">created_by,</if> |
| | | </trim> |
| | | <trim prefix="values (" suffix=")" suffixOverrides=","> |
| | |
| | | <if test="quantity != null">#{quantity},</if> |
| | | <if test="totalAmount != null">#{totalAmount},</if> |
| | | <if test="remark != null">#{remark},</if> |
| | | <if test="pid != null">#{pid},</if> |
| | | <if test="syncStatus != null">#{syncStatus},</if> |
| | | <if test="syncTime != null">#{syncTime},</if> |
| | | <if test="createdBy != null and createdBy != ''">#{createdBy},</if> |
| | | </trim> |
| | | </insert> |
| | |
| | | <if test="actualEndTime != null">actual_end_time = #{actualEndTime},</if> |
| | | <if test="creatorId != null">creator_id = #{creatorId},</if> |
| | | <if test="assigneeId != null">assignee_id = #{assigneeId},</if> |
| | | |
| | | <if test="deptId != null">dept_id = #{deptId},</if> |
| | | <if test="updateTime != null">update_time = #{updateTime},</if> |
| | | <if test="updateBy != null">update_by = #{updateBy},</if> |
| | |
| | | <!-- 急救转运任务扩展信息 --> |
| | | <el-descriptions v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && taskDetail.emergencyInfo" title="急救转运信息" :column="2" border style="margin-top: 20px;"> |
| | | <el-descriptions-item label="联系人"> |
| | | <span v-if="taskDetail.emergencyInfo.contactPerson">{{ taskDetail.emergencyInfo.contactPerson }}</span> |
| | | <span v-if="taskDetail.emergencyInfo.patientContact">{{ taskDetail.emergencyInfo.patientContact }}</span> |
| | | <span v-else style="color: #C0C4CC;">--</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="联系电话"> |
| | | <span v-if="taskDetail.emergencyInfo.contactPhone">{{ taskDetail.emergencyInfo.contactPhone }}</span> |
| | | <span v-if="taskDetail.emergencyInfo.patientPhone">{{ taskDetail.emergencyInfo.patientPhone }}</span> |
| | | <span v-else style="color: #C0C4CC;">--</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="患者姓名"> |
| | |
| | | <span v-else style="color: #C0C4CC;">--</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="病情描述" :span="2"> |
| | | <span v-if="taskDetail.emergencyInfo.illnessDescription">{{ taskDetail.emergencyInfo.illnessDescription }}</span> |
| | | <span v-if="taskDetail.emergencyInfo.patientCondition">{{ taskDetail.emergencyInfo.patientCondition }}</span> |
| | | <span v-else style="color: #C0C4CC;">--</span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <!-- 支付信息(仅急救转运任务显示) --> |
| | | <el-card v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && paymentInfo" class="box-card" style="margin-top: 20px;"> |
| | | <div slot="header" class="clearfix"> |
| | | <span>支付信息</span> |
| | | </div> |
| | | |
| | | <!-- 支付概览 --> |
| | | <el-descriptions :column="3" border> |
| | | <el-descriptions-item label="基本价格"> |
| | | <span style="color: #409EFF; font-weight: bold;">¥{{ paymentInfo.transferPrice || '0.00' }}</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="附加费用总额"> |
| | | <span style="color: #E6A23C; font-weight: bold;">¥{{ paymentInfo.additionalAmount || '0.00' }}</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="总金额"> |
| | | <span style="color: #67C23A; font-weight: bold;">¥{{ paymentInfo.totalAmount || '0.00' }}</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="已支付金额"> |
| | | <span style="color: #67C23A; font-weight: bold;">¥{{ paymentInfo.paidAmount || '0.00' }}</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="剩余未付金额"> |
| | | <span style="color: #F56C6C; font-weight: bold;">¥{{ getRemainingAmount() }}</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="支付状态"> |
| | | <el-tag v-if="getRemainingAmount() <= 0" type="success" size="small"> |
| | | <i class="el-icon-success"></i> 已结清 |
| | | </el-tag> |
| | | <el-tag v-else-if="paymentInfo.paidAmount > 0" type="warning" size="small"> |
| | | <i class="el-icon-warning"></i> 部分支付 |
| | | </el-tag> |
| | | <el-tag v-else type="info" size="small"> |
| | | <i class="el-icon-warning"></i> 未支付 |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <!-- 附加费用明细 --> |
| | | <div style="margin-top: 20px;"> |
| | | <h4 style="margin-bottom: 10px;">附加费用明细</h4> |
| | | <el-table :data="paymentInfo.additionalFees" border> |
| | | <el-table-column label="费用类型" align="center" prop="feeType" width="120"> |
| | | <template slot-scope="scope"> |
| | | <dict-tag :options="dict.type.task_additional_fee_type" :value="scope.row.feeType"/> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="费用名称" align="center" prop="feeName" /> |
| | | <el-table-column label="单价" align="center" prop="unitAmount" width="120"> |
| | | <template slot-scope="scope"> |
| | | <span>¥{{ scope.row.unitAmount }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="数量" align="center" prop="quantity" width="80" /> |
| | | <el-table-column label="小计" align="center" prop="totalAmount" width="120"> |
| | | <template slot-scope="scope"> |
| | | <span style="color: #E6A23C; font-weight: bold;">¥{{ scope.row.totalAmount }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="同步状态" align="center" width="120"> |
| | | <template slot-scope="scope"> |
| | | <el-tag v-if="scope.row.syncStatus === 0" type="info" size="small"> |
| | | <i class="el-icon-warning"></i> 未同步 |
| | | </el-tag> |
| | | <el-tag v-else-if="scope.row.syncStatus === 1" type="warning" size="small"> |
| | | <i class="el-icon-loading"></i> 同步中 |
| | | </el-tag> |
| | | <el-tag v-else-if="scope.row.syncStatus === 2" type="success" size="small"> |
| | | <i class="el-icon-success"></i> 同步成功 |
| | | </el-tag> |
| | | <el-tag v-else-if="scope.row.syncStatus === 3" type="danger" size="small"> |
| | | <i class="el-icon-error"></i> 同步失败 |
| | | </el-tag> |
| | | <span v-else style="color: #C0C4CC;">--</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip> |
| | | <template slot-scope="scope"> |
| | | <span v-if="scope.row.remark">{{ scope.row.remark }}</span> |
| | | <span v-else style="color: #C0C4CC;">--</span> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- 空状态提示 --> |
| | | <div v-if="!paymentInfo.additionalFees || paymentInfo.additionalFees.length === 0" style="text-align: center; padding: 40px 0; color: #909399;"> |
| | | <i class="el-icon-document" style="font-size: 48px; display: block; margin-bottom: 12px;"></i> |
| | | <span>暂无附加费用</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 已支付记录 --> |
| | | <div style="margin-top: 20px;"> |
| | | <h4 style="margin-bottom: 10px;">已支付记录</h4> |
| | | <el-table :data="paymentInfo.paidPayments" border> |
| | | <el-table-column label="商户订单号" align="center" prop="outTradeNo" width="200" show-overflow-tooltip /> |
| | | <el-table-column label="支付金额" align="center" prop="settlementAmount" width="120"> |
| | | <template slot-scope="scope"> |
| | | <span style="color: #67C23A; font-weight: bold;">¥{{ scope.row.settlementAmount }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="支付方式" align="center" prop="paymentMethod" width="120"> |
| | | <template slot-scope="scope"> |
| | | <dict-tag :options="dict.type.task_payment_method" :value="scope.row.paymentMethod"/> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="支付状态" align="center" prop="payStatus" width="120"> |
| | | <template slot-scope="scope"> |
| | | <el-tag v-if="scope.row.payStatus === 'PAID'" type="success" size="small"> |
| | | <i class="el-icon-success"></i> 已支付 |
| | | </el-tag> |
| | | <el-tag v-else-if="scope.row.payStatus === 'PENDING'" type="warning" size="small"> |
| | | <i class="el-icon-time"></i> 待支付 |
| | | </el-tag> |
| | | <el-tag v-else-if="scope.row.payStatus === 'FAILED'" type="danger" size="small"> |
| | | <i class="el-icon-error"></i> 失败 |
| | | </el-tag> |
| | | <el-tag v-else type="info" size="small"> |
| | | {{ scope.row.payStatus }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="交易号" align="center" prop="tradeNo" width="150" show-overflow-tooltip /> |
| | | <el-table-column label="支付时间" align="center" prop="payTime" width="180"> |
| | | <template slot-scope="scope"> |
| | | <span v-if="scope.row.payTime">{{ parseTime(scope.row.payTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span> |
| | | <span v-else style="color: #C0C4CC;">--</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="同步状态" align="center" width="120"> |
| | | <template slot-scope="scope"> |
| | | <el-tag v-if="scope.row.syncStatus === 0" type="info" size="small"> |
| | | <i class="el-icon-warning"></i> 未同步 |
| | | </el-tag> |
| | | <el-tag v-else-if="scope.row.syncStatus === 1" type="warning" size="small"> |
| | | <i class="el-icon-loading"></i> 同步中 |
| | | </el-tag> |
| | | <el-tag v-else-if="scope.row.syncStatus === 2" type="success" size="small"> |
| | | <i class="el-icon-success"></i> 同步成功 |
| | | </el-tag> |
| | | <el-tag v-else-if="scope.row.syncStatus === 3" type="danger" size="small"> |
| | | <i class="el-icon-error"></i> 同步失败 |
| | | </el-tag> |
| | | <span v-else style="color: #C0C4CC;">--</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip> |
| | | <template slot-scope="scope"> |
| | | <span v-if="scope.row.remark">{{ scope.row.remark }}</span> |
| | | <span v-else style="color: #C0C4CC;">--</span> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- 空状态提示 --> |
| | | <div v-if="!paymentInfo.paidPayments || paymentInfo.paidPayments.length === 0" style="text-align: center; padding: 40px 0; color: #909399;"> |
| | | <i class="el-icon-document" style="font-size: 48px; display: block; margin-bottom: 12px;"></i> |
| | | <span>暂无支付记录</span> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- 福祉车任务扩展信息 --> |
| | | <el-descriptions v-if="taskDetail.taskType === 'WELFARE' && taskDetail.welfareInfo" title="福祉车服务信息" :column="2" border style="margin-top: 20px;"> |
| | | <el-descriptions-item label="乘客姓名">{{ taskDetail.welfareInfo.passengerName }}</el-descriptions-item> |
| | |
| | | <el-button type="success" @click="handleAssign" v-hasPermi="['task:general:assign']">分配任务</el-button> |
| | | <el-button type="warning" @click="handleStatusChange" v-hasPermi="['task:general:status']">状态变更</el-button> |
| | | <el-button type="info" @click="handleVehicleAssign" v-hasPermi="['task:general:assign']">分配车辆</el-button> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- 执行人员列表 --> |
| | | <el-card class="box-card" style="margin-top: 20px;"> |
| | | <div slot="header" class="clearfix"> |
| | | <span>执行人员</span> |
| | | </div> |
| | | |
| | | <el-table :data="taskDetail.assignees" v-loading="assigneeLoading"> |
| | | <el-table-column label="序号" align="center" width="60"> |
| | | <template slot-scope="scope"> |
| | | <span>{{ scope.$index + 1 }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="姓名" align="center" prop="userName"> |
| | | <template slot-scope="scope"> |
| | | <span>{{ scope.row.userName }}</span> |
| | | <el-tag v-if="scope.row.isPrimary === '1'" type="warning" size="mini" style="margin-left: 8px;"> |
| | | <i class="el-icon-star-on"></i> 负责人 |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="角色类型" align="center" prop="userType" width="120"> |
| | | <template slot-scope="scope"> |
| | | <el-tag v-if="scope.row.userType === 'driver'" type="primary" size="small">司机</el-tag> |
| | | <el-tag v-else-if="scope.row.userType === 'doctor'" type="success" size="small">医生</el-tag> |
| | | <el-tag v-else-if="scope.row.userType === 'nurse'" type="info" size="small">护士</el-tag> |
| | | <el-tag v-else type="" size="small">{{ scope.row.userType }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="创建时间" align="center" prop="createTime" width="180"> |
| | | <template slot-scope="scope"> |
| | | <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- 空状态提示 --> |
| | | <div v-if="!taskDetail.assignees || taskDetail.assignees.length === 0" style="text-align: center; padding: 40px 0; color: #909399;"> |
| | | <i class="el-icon-user" style="font-size: 48px; display: block; margin-bottom: 12px;"></i> |
| | | <span>暂无执行人员</span> |
| | | </div> |
| | | </el-card> |
| | | |
| | |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <!-- 附加费用列表 --> |
| | | <el-card class="box-card" style="margin-top: 20px;"> |
| | | <div slot="header" class="clearfix"> |
| | | <span>附加费用列表</span> |
| | | </div> |
| | | |
| | | <el-table :data="additionalFeeList" v-loading="feeLoading"> |
| | | <el-table-column label="费用类型" align="center" prop="feeType" width="120"> |
| | | <template slot-scope="scope"> |
| | | <dict-tag :options="dict.type.task_additional_fee_type" :value="scope.row.feeType"/> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="费用名称" align="center" prop="feeName" /> |
| | | <el-table-column label="单价" align="center" prop="unitAmount" width="120"> |
| | | <template slot-scope="scope"> |
| | | <span>¥{{ scope.row.unitAmount }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="数量" align="center" prop="quantity" width="80" /> |
| | | <el-table-column label="总金额" align="center" prop="totalAmount" width="120"> |
| | | <template slot-scope="scope"> |
| | | <span style="color: #E6A23C; font-weight: bold;">¥{{ scope.row.totalAmount }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="同步状态" align="center" width="120"> |
| | | <template slot-scope="scope"> |
| | | <el-tag v-if="scope.row.syncStatus === 0" type="info" size="small"> |
| | | <i class="el-icon-warning"></i> 未同步 |
| | | </el-tag> |
| | | <el-tag v-else-if="scope.row.syncStatus === 1" type="warning" size="small"> |
| | | <i class="el-icon-loading"></i> 同步中 |
| | | </el-tag> |
| | | <el-tag v-else-if="scope.row.syncStatus === 2" type="success" size="small"> |
| | | <i class="el-icon-success"></i> 同步成功 |
| | | </el-tag> |
| | | <el-tag v-else-if="scope.row.syncStatus === 3" type="danger" size="small"> |
| | | <i class="el-icon-error"></i> 同步失败 |
| | | </el-tag> |
| | | <span v-else style="color: #C0C4CC;">--</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="同步时间" align="center" prop="syncTime" width="180"> |
| | | <template slot-scope="scope"> |
| | | <span v-if="scope.row.syncTime">{{ parseTime(scope.row.syncTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span> |
| | | <span v-else style="color: #C0C4CC;">--</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="创建时间" align="center" prop="createdTime" width="180"> |
| | | <template slot-scope="scope"> |
| | | <span>{{ parseTime(scope.row.createdTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="创建者" align="center" prop="createdBy" width="100" /> |
| | | <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip> |
| | | <template slot-scope="scope"> |
| | | <span v-if="scope.row.remark">{{ scope.row.remark }}</span> |
| | | <span v-else style="color: #C0C4CC;">--</span> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- 空状态提示 --> |
| | | <div v-if="!additionalFeeList || additionalFeeList.length === 0" style="text-align: center; padding: 40px 0; color: #909399;"> |
| | | <i class="el-icon-document" style="font-size: 48px; display: block; margin-bottom: 12px;"></i> |
| | | <span>暂无附加费用</span> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- 操作日志 --> |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import { getTask, updateTask, assignTask, changeTaskStatus, uploadAttachment, deleteAttachment, getTaskVehicles, getAvailableVehicles, assignVehiclesToTask, unassignVehicleFromTask } from "@/api/task"; |
| | | import { getTask, updateTask, assignTask, changeTaskStatus, uploadAttachment, deleteAttachment, getTaskVehicles, getAvailableVehicles, assignVehiclesToTask, unassignVehicleFromTask, getPaymentInfo } from "@/api/task"; |
| | | import { listUser } from "@/api/system/user"; |
| | | import { getToken } from "@/utils/auth"; |
| | | |
| | | export default { |
| | | name: "TaskDetail", |
| | | dicts: ['sys_task_type', 'sys_task_status', 'sys_vehicle_type', 'sys_task_vehicle_status', 'sys_user_sex', 'hospital_department', 'sys_attachment_category'], |
| | | dicts: ['sys_task_type', 'sys_task_status', 'sys_vehicle_type', 'sys_task_vehicle_status', 'sys_user_sex', 'hospital_department', 'sys_attachment_category', 'task_additional_fee_type', 'task_payment_method'], |
| | | data() { |
| | | return { |
| | | // 任务详情 |
| | |
| | | // 加载状态 |
| | | vehicleLoading: false, |
| | | attachmentLoading: false, |
| | | feeLoading: false, |
| | | assigneeLoading: false, |
| | | // 附加费用列表 |
| | | additionalFeeList: [], |
| | | // 支付信息 |
| | | paymentInfo: null, |
| | | // 上传相关 |
| | | uploadUrl: process.env.VUE_APP_BASE_API + "/task/attachment/upload/" + (new URLSearchParams(window.location.search).get('taskId') || ''), |
| | | uploadHeaders: { |
| | |
| | | created() { |
| | | this.getTaskDetail(); |
| | | this.getUserList(); |
| | | this.getAdditionalFeeList(); |
| | | // 初始化上传URL |
| | | this.uploadUrl = process.env.VUE_APP_BASE_API + "/task/attachment/upload/" + this.$route.params.taskId; |
| | | }, |
| | |
| | | getTaskDetail() { |
| | | getTask(this.$route.params.taskId).then(response => { |
| | | this.taskDetail = response.data; |
| | | // 任务详情加载完成后,加载支付信息 |
| | | // console.log("TaskDetail", this.taskDetail); |
| | | this.loadPaymentInfo(); |
| | | }); |
| | | }, |
| | | /** 获取附加费用列表 */ |
| | | getAdditionalFeeList() { |
| | | this.feeLoading = true; |
| | | getPaymentInfo(this.$route.params.taskId).then(response => { |
| | | this.additionalFeeList = response.data.additionalFees || []; |
| | | this.feeLoading = false; |
| | | }).catch(() => { |
| | | this.feeLoading = false; |
| | | }); |
| | | }, |
| | | /** 加载支付信息 */ |
| | | loadPaymentInfo() { |
| | | //EMERGENCY_TRANSFER |
| | | if (this.taskDetail.taskType === 'EMERGENCY_TRANSFER') { |
| | | getPaymentInfo(this.$route.params.taskId).then(response => { |
| | | // console.log("PaymentInfo", response.data); |
| | | this.paymentInfo = response.data; |
| | | }).catch(() => { |
| | | this.paymentInfo = null; |
| | | }); |
| | | } |
| | | }, |
| | | /** 计算剩余未付金额 */ |
| | | getRemainingAmount() { |
| | | if (!this.paymentInfo) return '0.00'; |
| | | const total = parseFloat(this.paymentInfo.totalAmount || 0); |
| | | const paid = parseFloat(this.paymentInfo.paidAmount || 0); |
| | | const remaining = total - paid; |
| | | return remaining > 0 ? remaining.toFixed(2) : '0.00'; |
| | | }, |
| | | /** 获取用户列表 */ |
| | | getUserList() { |
| | | listUser().then(response => { |