| | |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // æ ¹æ®åå
¬å¸IDå表æ¥è¯¢ç¨æ·ï¼å°ç¨åºç«¯ä¸ç¨ï¼ |
| | | // æ ¹æ®åå
¬å¸IDæ°ç»æ¥è¯¢ç¨æ·(POSTæ¹å¼) |
| | | export function listUsersByBranchDepts(branchDeptIds) { |
| | | return request({ |
| | | url: '/system/user/branch/users', |
| | | method: 'post', |
| | | data: { |
| | | branchDeptIds |
| | | } |
| | | }) |
| | | } |
| | |
| | | <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-name">{{ getStaffDisplayName(staff) }}</text> |
| | | </view> |
| | | <uni-icons |
| | | v-if="canRemove(index)" |
| | |
| | | <script> |
| | | import { mapState } from 'vuex' |
| | | import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue' |
| | | import { listBranchUsers } from "@/api/system/user" |
| | | import { listUsersByBranchDepts } from "@/api/system/user" |
| | | |
| | | export default { |
| | | name: 'StaffSelector', |
| | |
| | | currentUserRemovable: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | // åå
¬å¸IDå表ï¼å¤é¨ä¼ å
¥ï¼ç¨äºæå®å è½½åªäºåå
¬å¸çç¨æ·ï¼ |
| | | branchDeptIds: { |
| | | type: Array, |
| | | default: null |
| | | }, |
| | | // å个åå
¬å¸IDï¼ä»
ä¼ ä¸ä¸ªæ¶æ´ä¾¿æ·ï¼ |
| | | branchDeptId: { |
| | | type: [Number, String], |
| | | default: null |
| | | } |
| | | }, |
| | | data() { |
| | |
| | | allStaffList: [], |
| | | filteredStaffList: [], |
| | | staffSearchKeyword: '', |
| | | staffFilterType: 'driver' // é»è®¤éä¸å¸æº |
| | | staffFilterType: 'driver', // é»è®¤éä¸å¸æº |
| | | staffListCache: {}, // ç¼å: { key: { data: [], timestamp: 0 } } |
| | | cacheExpireTime: 5 * 60 * 1000 // ç¼åè¿ææ¶é´ï¼5åé |
| | | } |
| | | }, |
| | | computed: { |
| | |
| | | }, |
| | | immediate: true, |
| | | deep: true |
| | | }, |
| | | // çå¬åå
¬å¸IDæ°ç»ååï¼éæ°å è½½ç¨æ·å表 |
| | | branchDeptIds: { |
| | | handler(newVal, oldVal) { |
| | | if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) { |
| | | console.log('åå
¬å¸IDååï¼éæ°å è½½ç¨æ·:', newVal) |
| | | this.loadStaffList() |
| | | } |
| | | }, |
| | | deep: true |
| | | }, |
| | | // çå¬å个åå
¬å¸IDåå |
| | | branchDeptId(newVal, oldVal) { |
| | | if (newVal !== oldVal) { |
| | | console.log('åå
¬å¸IDååï¼éæ°å è½½ç¨æ·:', newVal) |
| | | this.loadStaffList() |
| | | } |
| | | } |
| | | }, |
| | | mounted() { |
| | |
| | | |
| | | // å 载人åå表 |
| | | loadStaffList() { |
| | | listBranchUsers().then(response => { |
| | | // è·åææé¨é¨ID |
| | | let deptIds = [] |
| | | if (this.branchDeptIds && this.branchDeptIds.length > 0) { |
| | | deptIds = this.branchDeptIds |
| | | } else if (this.branchDeptId) { |
| | | deptIds = [this.branchDeptId] |
| | | } |
| | | |
| | | if (deptIds.length > 0) { |
| | | console.log('æ ¹æ®åå
¬å¸IDå è½½ç¨æ·:', deptIds) |
| | | this.loadStaffByBranchDepts(deptIds) |
| | | } else { |
| | | console.log('æªä¼ å
¥åå
¬å¸ID,ç»ä»¶ä¸å 载人åå表') |
| | | this.$modal && this.$modal.showToast && this.$modal.showToast('è¯·ä¼ å
¥åå
¬å¸ID') |
| | | } |
| | | }, |
| | | |
| | | // æ ¹æ®åå
¬å¸IDæ°ç»å è½½ç¨æ·ï¼æ¯æç¼åï¼ |
| | | loadStaffByBranchDepts(deptIds) { |
| | | // çæç¼åkeyï¼ææåºåidæ¼æ¥ï¼ |
| | | const cacheKey = [...deptIds].sort((a, b) => a - b).join(',') |
| | | const cached = this.staffListCache[cacheKey] |
| | | const now = Date.now() |
| | | |
| | | // æ£æ¥ç¼åæ¯å¦ææ |
| | | if (cached && (now - cached.timestamp) < this.cacheExpireTime) { |
| | | console.log('使ç¨ç¼åç人åå表:', cacheKey) |
| | | this.processUserList(cached.data) |
| | | return |
| | | } |
| | | |
| | | // ç¼å失ææä¸åå¨,è°ç¨æ¥å£ |
| | | console.log('å 载人åå表:', deptIds) |
| | | listUsersByBranchDepts(deptIds).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() |
| | | // æ´æ°ç¼å |
| | | this.staffListCache[cacheKey] = { |
| | | data: userList, |
| | | timestamp: now |
| | | } |
| | | this.processUserList(userList) |
| | | }).catch(error => { |
| | | console.error('å 载人åå表失败:', error) |
| | | this.$modal.showToast('å 载人åå表失败') |
| | | }) |
| | | }, |
| | | |
| | | |
| | | |
| | | // å¤çç¨æ·åè¡¨æ°æ® |
| | | processUserList(userList) { |
| | | 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() |
| | | }, |
| | | |
| | | // æ ¹æ®ç¨æ·çå²ä½æè§è²å¤æææç±»åï¼æ¯æå¤ç§èº«ä»½ï¼ |
| | |
| | | emitChange() { |
| | | this.$emit('input', this.selectedStaff) |
| | | this.$emit('change', this.selectedStaff) |
| | | }, |
| | | |
| | | // è·åäººåæ¾ç¤ºåç§°ï¼ä¼å
æ¾ç¤ºå§åï¼å¦æå§åä¸ºç©ºåæ¾ç¤ºææºå·ï¼ |
| | | getStaffDisplayName(staff) { |
| | | if (!staff) { |
| | | return 'æªç¥äººå' |
| | | } |
| | | // ä¼å
æ¾ç¤º nickNameï¼å¦æä¸ºç©ºåæ¾ç¤ºææºå·ï¼é½ä¸ºç©ºåæ¾ç¤º userId |
| | | if (staff.nickName && staff.nickName.trim()) { |
| | | return staff.nickName |
| | | } |
| | | if (staff.phonenumber && staff.phonenumber.trim()) { |
| | | return staff.phonenumber |
| | | } |
| | | return `ç¨æ·${staff.userId || ''}` |
| | | } |
| | | } |
| | | } |
| | |
| | | </view> |
| | | <view class="form-item"> |
| | | <OrganizationSelector |
| | | ref="organizationSelector" |
| | | v-model="selectedOrganizationId" |
| | | :required="true" |
| | | :auto-select-user-dept="true" |
| | |
| | | :required="false" |
| | | :auto-add-current-user="true" |
| | | :current-user-removable="false" |
| | | :branch-dept-ids="allOrganizationIds" |
| | | @change="onStaffChange" |
| | | /> |
| | | |
| | |
| | | return { |
| | | selectedVehicle: '', |
| | | selectedVehicleId: null, |
| | | selectedOrganizationId: null, // å½å±æºæIDï¼é¨é¨IDï¼ |
| | | selectedOrganizationId: null, // å½åéä¸çå½å±æºæID |
| | | allOrganizationIds: [], // ææå¯éæºæIDæ°ç» |
| | | selectedOrganizationServiceOrderClass: '', // å½å±æºæçæå¡åç¼ç |
| | | selectedRegion: '', // ä»å½å±æºæä¸æåçå°åä¿¡æ¯ï¼å¦ï¼å¹¿å·ãæ·±å³çï¼ |
| | | departureAddress: '', // åºåå°å°å |
| | |
| | | this.loadEmergencyTaskTypes() |
| | | // å è½½åæ®ç±»åæ°æ® |
| | | this.loadDocumentTypes() |
| | | // å è½½æææºæID |
| | | this.loadAllOrganizationIds() |
| | | }, |
| | | methods: { |
| | | // è·åç¨æ·ç»å®ç车è¾ä¿¡æ¯ |
| | |
| | | return region.replace(/(åå
¬å¸|æ»å
¬å¸|æ»é¨)$/g, '').trim(); |
| | | }, |
| | | |
| | | // å è½½æææºæID |
| | | loadAllOrganizationIds() { |
| | | // éè¿ OrganizationSelector ç»ä»¶è·åæææºæ |
| | | const orgSelector = this.$refs.organizationSelector |
| | | if (orgSelector) { |
| | | orgSelector.reload().then(organizations => { |
| | | this.allOrganizationIds = organizations.map(org => org.deptId) |
| | | console.log('æææºæID:', this.allOrganizationIds) |
| | | }) |
| | | } else { |
| | | // 妿ç»ä»¶è¿æªæè½½,ç¨åéè¯ |
| | | setTimeout(() => { |
| | | this.loadAllOrganizationIds() |
| | | }, 100) |
| | | } |
| | | }, |
| | | |
| | | // å è½½ç§å®¤æ°æ®ï¼ä» SQL Server 卿å è½½ï¼ |
| | | loadDepartments() { |
| | | getHospitalDepartments().then(response => { |
| | |
| | | |
| | | // æ£æ¥è½¦è¾ç¶æå¹¶åºå |
| | | checkVehicleAndDepart() { |
| | | // æ£æ¥åºåæ¶é´æ¯å¦ä¸ºç©ºæ1900å¹´ï¼ä¿®å¤ï¼é²æ¢æ ææ¶é´ï¼ |
| | | if (!this.taskDetail.plannedStartTime || this.taskDetail.plannedStartTime.startsWith('1900')) { |
| | | this.$modal.confirm('ä»»å¡çè½¬è¿æ¶é´æªè®¾ç½®ææ æï¼éè¦å
ä¿®æ¹ä»»å¡è¡¥å
è½¬è¿æ¶é´åæè½åºåãæ¯å¦ç°å¨å»ä¿®æ¹ï¼').then(() => { |
| | | this.handleEdit() |
| | | }).catch(() => {}) |
| | | return |
| | | } |
| | | |
| | | // è·åä»»å¡è½¦è¾ID |
| | | const vehicleId = this.getVehicleId(); |
| | | if (!vehicleId) { |
| | |
| | | /> |
| | | |
| | | <view class="form-item"> |
| | | <OrganizationSelector |
| | | <OrganizationSelector |
| | | ref="organizationSelector" |
| | | v-model="selectedOrganizationId" |
| | | :required="true" |
| | | :auto-select-user-dept="false" |
| | |
| | | :required="false" |
| | | :auto-add-current-user="false" |
| | | :current-user-removable="true" |
| | | :branch-dept-ids="allOrganizationIds" |
| | | @change="onStaffChange" |
| | | /> |
| | | |
| | |
| | | taskDetail: null, |
| | | selectedVehicleId: null, |
| | | selectedOrganizationId: null, |
| | | allOrganizationIds: [], // ææå¯éæºæIDæ°ç» |
| | | selectedRegion: '', |
| | | mapSelectorType: '', |
| | | // æ©å± addressCoordinates æ¯æå¤ç§é®å |
| | |
| | | }, 1500) |
| | | } |
| | | }, |
| | | |
| | | mounted() { |
| | | // é¡µé¢æè½½åå è½½æææºæID |
| | | this.loadAllOrganizationIds() |
| | | }, |
| | | methods: { |
| | | // å 载任å¡è¯¦æ
|
| | | loadTaskDetail() { |
| | |
| | | const info = this.taskDetail.emergencyInfo |
| | | console.log('转è¿ä»»å¡ä¿¡æ¯:', info) |
| | | |
| | | // è½¬è¿æ¶é´ |
| | | this.taskForm.transferTime = this.taskDetail.plannedStartTime || '' |
| | | // è½¬è¿æ¶é´ï¼ä¿®å¤ï¼1900å¹´çæ¥ææ¾ç¤ºä¸ºç©ºï¼ |
| | | const transferTime = this.taskDetail.plannedStartTime || '' |
| | | this.taskForm.transferTime = transferTime && transferTime.startsWith('1900') ? '' : transferTime |
| | | |
| | | // æ£è
ä¿¡æ¯ |
| | | this.taskForm.patient.contact = info.patientContact || '' |
| | |
| | | this.taskForm.hospitalOut.id = info.hospitalOutId || null |
| | | this.taskForm.hospitalOut.name = info.hospitalOutName || '' |
| | | this.taskForm.hospitalOut.department = info.hospitalOutDepartment || '' |
| | | this.taskForm.hospitalOut.departmentId = info.hospitalOutDepartmentId || null |
| | | this.taskForm.hospitalOut.bedNumber = info.hospitalOutBedNumber || '' |
| | | this.taskForm.hospitalOut.address = info.hospitalOutAddress || '' |
| | | console.log('转åºå»é¢ç§å®¤ID:', info.hospitalOutDepartmentId) |
| | | |
| | | // å 载转åºå»é¢GPSåæ ï¼ä¸æ¾ç¤ºï¼ä½ä¿å卿°æ®ä¸ï¼ |
| | | if (info.hospitalOutLongitude && info.hospitalOutLatitude) { |
| | |
| | | this.taskForm.hospitalIn.id = info.hospitalInId || null |
| | | this.taskForm.hospitalIn.name = info.hospitalInName || '' |
| | | this.taskForm.hospitalIn.department = info.hospitalInDepartment || '' |
| | | this.taskForm.hospitalIn.departmentId = info.hospitalInDepartmentId || null |
| | | this.taskForm.hospitalIn.bedNumber = info.hospitalInBedNumber || '' |
| | | this.taskForm.hospitalIn.address = info.hospitalInAddress || '' |
| | | console.log('转å
¥å»é¢ç§å®¤ID:', info.hospitalInDepartmentId) |
| | | |
| | | // å 载转å
¥å»é¢GPSåæ ï¼ä¸æ¾ç¤ºï¼ä½ä¿å卿°æ®ä¸ï¼ |
| | | if (info.hospitalInLongitude && info.hospitalInLatitude) { |
| | |
| | | } else { |
| | | console.warn('ä»»å¡è¯¦æ
䏿²¡æemergencyInfoåæ®µï¼å°è¯ä»ä¸»å¯¹è±¡è·åæ°æ®') |
| | | // å
¼å®¹å¤çï¼å¦æemergencyInfoä¸åå¨ï¼å°è¯ä»ä¸»å¯¹è±¡è·å |
| | | this.taskForm.transferTime = this.taskDetail.plannedStartTime || '' |
| | | const transferTime = this.taskDetail.plannedStartTime || '' |
| | | this.taskForm.transferTime = transferTime && transferTime.startsWith('1900') ? '' : transferTime |
| | | this.taskForm.transferDistance = this.taskDetail.estimatedDistance ? String(this.taskDetail.estimatedDistance) : '' |
| | | } |
| | | |
| | |
| | | console.log('è®¾ç½®ç®æ å°åæ :', this.taskDetail.destinationLongitude, this.taskDetail.destinationLatitude) |
| | | } |
| | | |
| | | // 设置æ§è¡äººåï¼ä¿®å¤ï¼ç¡®ä¿ assignees ä¸ä¸º nullï¼ |
| | | // 设置æ§è¡äººåï¼ä¿®å¤ï¼ç¡®ä¿ 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, |
| | | nickName: assignee.userName, |
| | | type: assignee.userType || 'driver', |
| | | phonenumber: '', |
| | | deptName: '' |
| | | })) |
| | | this.selectedStaff = this.taskDetail.assignees.map(assignee => { |
| | | console.log('å¤çæ§è¡äººå:', assignee) |
| | | console.log(' - userName:', assignee.userName) |
| | | console.log(' - nickName:', assignee.nickName) |
| | | console.log(' - phonenumber:', assignee.phonenumber) |
| | | console.log(' - phone:', assignee.phone) |
| | | |
| | | return { |
| | | userId: assignee.userId, |
| | | nickName: assignee.userName || assignee.nickName || '', |
| | | type: assignee.userType || 'driver', |
| | | phonenumber: assignee.phonenumber || assignee.phone || '', |
| | | deptName: assignee.deptName || '' |
| | | } |
| | | }) |
| | | console.log('å¤çåçæ§è¡äººåå表:', this.selectedStaff) |
| | | } else { |
| | | console.warn('任塿²¡æåé
æ§è¡äººåæassignees为空') |
| | |
| | | // 车è¾éæ©åå |
| | | onVehicleChange(vehicle) { |
| | | console.log('éä¸è½¦è¾:', vehicle) |
| | | }, |
| | | |
| | | // å è½½æææºæID |
| | | loadAllOrganizationIds() { |
| | | // éè¿ OrganizationSelector ç»ä»¶è·åæææºæ |
| | | const orgSelector = this.$refs.organizationSelector |
| | | if (orgSelector) { |
| | | orgSelector.reload().then(organizations => { |
| | | this.allOrganizationIds = organizations.map(org => org.deptId) |
| | | console.log('æææºæID:', this.allOrganizationIds) |
| | | }) |
| | | } else { |
| | | // 妿ç»ä»¶è¿æªæè½½,ç¨åéè¯ |
| | | setTimeout(() => { |
| | | this.loadAllOrganizationIds() |
| | | }, 100) |
| | | } |
| | | }, |
| | | |
| | | // å½å±æºæéæ©åå |
| | |
| | | this.loading = true |
| | | const submitData = this.buildSubmitData() |
| | | |
| | | console.log('æäº¤æ°æ® - 转åºå»é¢ç§å®¤ID:', submitData.hospitalOut.departmentId) |
| | | console.log('æäº¤æ°æ® - 转å
¥å»é¢ç§å®¤ID:', submitData.hospitalIn.departmentId) |
| | | |
| | | updateTask(submitData).then(response => { |
| | | this.loading = false |
| | | console.log('任塿´æ°ååº:', response) |
| | |
| | | package com.ruoyi.payment.infrastructure.channel.alipay; |
| | | |
| | | import com.ruoyi.payment.infrastructure.config.AlipayConfig; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.http.HttpEntity; |
| | | import org.apache.http.client.methods.CloseableHttpResponse; |
| | |
| | | import org.apache.http.impl.client.CloseableHttpClient; |
| | | import org.apache.http.impl.client.HttpClients; |
| | | import org.apache.http.util.EntityUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.math.BigDecimal; |
| | | |
| | | /** |
| | | * æ¯ä»å®ç¬¬ä¸æ¹æ¥å£å®¢æ·ç«¯ |
| | |
| | | @Component |
| | | public class AlipayThirdPartyClient { |
| | | |
| | | @Autowired |
| | | private AlipayConfig alipayConfig; |
| | | /** |
| | | * ç¬¬ä¸æ¹æ¯ä»å®å½é¢ä»æ¥å£å°å |
| | | */ |
| | | private static final String THIRD_PARTY_ALIPAY_URL = "https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php"; |
| | | // private String THIRD_PARTY_ALIPAY_URL = "https://sys.966120.com.cn/alipay_pay_QR_NotifyUrl.php"; |
| | | |
| | | /** |
| | | * ç¬¬ä¸æ¹æ¯ä»å®æ¥è¯¢æ¥å£å°å |
| | |
| | | outTradeNo, totalFee, serviceOrdId); |
| | | |
| | | try (CloseableHttpClient httpClient = HttpClients.createDefault()) { |
| | | HttpPost httpPost = new HttpPost(THIRD_PARTY_ALIPAY_URL); |
| | | HttpPost httpPost = new HttpPost(this.alipayConfig.getThirdParty().getUrl()); |
| | | |
| | | // 设置Cookie头 |
| | | httpPost.setHeader("Cookie", "CAMEName="); |
| | | |
| | | BigDecimal totalFeeYuan = BigDecimal.valueOf(totalFee / 100f); |
| | | // æå»ºmultipart/form-data请æ±ä½ |
| | | HttpEntity entity = MultipartEntityBuilder.create() |
| | | .addTextBody("notify_url", notifyUrl) |
| | | .addTextBody("out_trade_no", outTradeNo) |
| | | .addTextBody("total_fee", String.valueOf(totalFee)) |
| | | .addTextBody("total_fee", String.valueOf(totalFeeYuan)) |
| | | .addTextBody("ServiceOrdID", serviceOrdId) |
| | | .build(); |
| | | |
| | | httpPost.setEntity(entity); |
| | | |
| | | log.info("åé请æ±å°ç¬¬ä¸æ¹æ¥å£: {}", THIRD_PARTY_ALIPAY_URL); |
| | | log.info("åé请æ±å°ç¬¬ä¸æ¹æ¥å£: {}", this.alipayConfig.getThirdParty().getUrl()); |
| | | |
| | | // åéè¯·æ± |
| | | try (CloseableHttpResponse response = httpClient.execute(httpPost)) { |
| New file |
| | |
| | | # StaffSelector ç»ä»¶åå
¬å¸ç¨æ·å è½½åè½è¯´æ |
| | | |
| | | ## ð åè½æ¦è¿° |
| | | |
| | | ä¼å `StaffSelector` ç»ä»¶,æ¯æéè¿å¤é¨ä¼ å
¥åå
¬å¸IDå表æ¥å è½½æå®åå
¬å¸çç¨æ·,æä¾æ´çµæ´»ç人åéæ©åè½ã |
| | | |
| | | --- |
| | | |
| | | ## ð ä¿®æ¹å
容 |
| | | |
| | | ### 1. å端æ¥å£æ°å¢ |
| | | |
| | | #### **æä»¶**: `SysUserController.java` |
| | | |
| | | **æ°å¢æ¥å£**: `GET /system/user/branch/users/by-dept-ids` |
| | | |
| | | ```java |
| | | /** |
| | | * æ ¹æ®åå
¬å¸IDå表è·åç¨æ·(å°ç¨åºç«¯ä¸ç¨) |
| | | * æ¯æå¤é¨ä¼ å
¥åå
¬å¸IDæ°ç»,æ¥è¯¢è¿äºåå
¬å¸åå
¶ææåé¨é¨çç¨æ· |
| | | */ |
| | | @GetMapping("/branch/users/by-dept-ids") |
| | | public AjaxResult listUsersByBranchDeptIds(Long[] branchDeptIds) |
| | | ``` |
| | | |
| | | **åæ°**: |
| | | - `branchDeptIds`: åå
¬å¸IDæ°ç»,ä¾å¦ `[101, 102]` |
| | | |
| | | **è¿åæ°æ®**: |
| | | ```json |
| | | { |
| | | "code": 200, |
| | | "msg": "æä½æå", |
| | | "data": [ |
| | | { |
| | | "userId": 1, |
| | | "nickName": "å¼ ä¸", |
| | | "phonenumber": "13800138000", |
| | | "dept": { |
| | | "deptId": 201, |
| | | "deptName": "广å·åå
¬å¸-车éA" |
| | | }, |
| | | "posts": [...], |
| | | "roles": [...] |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ### 2. å端APIæ©å± |
| | | |
| | | #### **æä»¶**: `app/api/system/user.js` |
| | | |
| | | **æ°å¢æ¹æ³**: |
| | | ```javascript |
| | | // æ ¹æ®åå
¬å¸IDå表æ¥è¯¢ç¨æ·(å°ç¨åºç«¯ä¸ç¨) |
| | | export function listUsersByBranchDeptIds(branchDeptIds) { |
| | | return request({ |
| | | url: '/system/user/branch/users/by-dept-ids', |
| | | method: 'get', |
| | | params: { |
| | | branchDeptIds: branchDeptIds |
| | | } |
| | | }) |
| | | } |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ### 3. StaffSelector ç»ä»¶å级 |
| | | |
| | | #### **æä»¶**: `app/pagesTask/components/StaffSelector.vue` |
| | | |
| | | **æ°å¢ Props**: |
| | | ```javascript |
| | | props: { |
| | | // ... å
¶ä» props |
| | | |
| | | // åå
¬å¸IDå表(å¤é¨ä¼ å
¥,ç¨äºæå®å è½½åªäºåå
¬å¸çç¨æ·) |
| | | branchDeptIds: { |
| | | type: Array, |
| | | default: null |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | **æ ¸å¿é»è¾**: |
| | | ```javascript |
| | | loadStaffList() { |
| | | // å¦æä¼ å
¥äºåå
¬å¸ID,ä½¿ç¨æå®çåå
¬å¸ |
| | | if (this.branchDeptIds && this.branchDeptIds.length > 0) { |
| | | this.loadStaffByBranchDeptIds(this.branchDeptIds) |
| | | } else { |
| | | // å¦å使ç¨å½åç¨æ·çåå
¬å¸ |
| | | this.loadStaffByCurrentUser() |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | **çå¬åå**: |
| | | ```javascript |
| | | watch: { |
| | | branchDeptIds: { |
| | | handler(newVal, oldVal) { |
| | | if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) { |
| | | this.loadStaffList() |
| | | } |
| | | }, |
| | | deep: true |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## ð ä½¿ç¨æ¹å¼ |
| | | |
| | | ### æ¹å¼ä¸: é»è®¤å è½½(使ç¨å½åç¨æ·çåå
¬å¸) |
| | | |
| | | ```vue |
| | | <template> |
| | | <staff-selector |
| | | v-model="selectedStaff" |
| | | label="æ§è¡ä»»å¡äººå" |
| | | :required="true" |
| | | /> |
| | | </template> |
| | | |
| | | <script> |
| | | import StaffSelector from '@/components/StaffSelector.vue' |
| | | |
| | | export default { |
| | | components: { StaffSelector }, |
| | | data() { |
| | | return { |
| | | selectedStaff: [] |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | ``` |
| | | |
| | | **è¡ä¸º**: |
| | | - èªå¨è°ç¨ `/system/user/branch/users` æ¥å£ |
| | | - å è½½å½åç»å½ç¨æ·æå±åå
¬å¸åå
¶åé¨é¨çææç¨æ· |
| | | |
| | | --- |
| | | |
| | | ### æ¹å¼äº: æå®åå
¬å¸ID(å¤é¨ä¼ å
¥) |
| | | |
| | | ```vue |
| | | <template> |
| | | <view> |
| | | <!-- åå
¬å¸éæ©å¨ --> |
| | | <view class="form-item"> |
| | | <view class="form-label">éæ©åå
¬å¸</view> |
| | | <picker |
| | | mode="multiSelector" |
| | | @change="onBranchChange" |
| | | :value="selectedBranchIndex" |
| | | :range="branchList" |
| | | range-key="deptName" |
| | | > |
| | | <view class="picker-value"> |
| | | {{ selectedBranchNames || 'è¯·éæ©åå
¬å¸' }} |
| | | </view> |
| | | </picker> |
| | | </view> |
| | | |
| | | <!-- 人åéæ©å¨ --> |
| | | <staff-selector |
| | | v-model="selectedStaff" |
| | | label="æ§è¡ä»»å¡äººå" |
| | | :required="true" |
| | | :branch-dept-ids="selectedBranchIds" |
| | | /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import StaffSelector from '@/components/StaffSelector.vue' |
| | | import { listBranchCompany } from '@/api/system/dept' |
| | | |
| | | export default { |
| | | components: { StaffSelector }, |
| | | data() { |
| | | return { |
| | | branchList: [], // ææåå
¬å¸å表 |
| | | selectedBranchIds: [], // éä¸çåå
¬å¸IDæ°ç» |
| | | selectedBranchNames: '', // éä¸çåå
¬å¸åç§° |
| | | selectedStaff: [] // éä¸ç人å |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.loadBranchList() |
| | | }, |
| | | methods: { |
| | | // å è½½åå
¬å¸å表 |
| | | loadBranchList() { |
| | | listBranchCompany().then(response => { |
| | | this.branchList = response.data || [] |
| | | }) |
| | | }, |
| | | |
| | | // åå
¬å¸éæ©åå |
| | | onBranchChange(e) { |
| | | const indices = e.detail.value |
| | | const selected = indices.map(index => this.branchList[index]) |
| | | |
| | | this.selectedBranchIds = selected.map(dept => dept.deptId) |
| | | this.selectedBranchNames = selected.map(dept => dept.deptName).join('ã') |
| | | |
| | | console.log('éä¸çåå
¬å¸ID:', this.selectedBranchIds) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | ``` |
| | | |
| | | **è¡ä¸º**: |
| | | - ç¨æ·éæ©åå
¬å¸å,`selectedBranchIds` æ´æ° |
| | | - `StaffSelector` çå¬å° `branchDeptIds` åå |
| | | - èªå¨è°ç¨ `/system/user/branch/users/by-dept-ids?branchDeptIds=101,102` |
| | | - å è½½æå®åå
¬å¸åå
¶åé¨é¨çææç¨æ· |
| | | |
| | | --- |
| | | |
| | | ### æ¹å¼ä¸: 卿忢åå
¬å¸ |
| | | |
| | | ```vue |
| | | <template> |
| | | <view> |
| | | <!-- Tab 忢åå
¬å¸ --> |
| | | <view class="tabs"> |
| | | <view |
| | | class="tab-item" |
| | | :class="{ active: currentBranchId === branch.deptId }" |
| | | v-for="branch in branchList" |
| | | :key="branch.deptId" |
| | | @click="switchBranch(branch)" |
| | | > |
| | | {{ branch.deptName }} |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 人åéæ©å¨ --> |
| | | <staff-selector |
| | | v-model="selectedStaff" |
| | | label="æ§è¡ä»»å¡äººå" |
| | | :branch-dept-ids="[currentBranchId]" |
| | | /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import StaffSelector from '@/components/StaffSelector.vue' |
| | | import { listBranchCompany } from '@/api/system/dept' |
| | | |
| | | export default { |
| | | components: { StaffSelector }, |
| | | data() { |
| | | return { |
| | | branchList: [], |
| | | currentBranchId: null, |
| | | selectedStaff: [] |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.loadBranchList() |
| | | }, |
| | | methods: { |
| | | loadBranchList() { |
| | | listBranchCompany().then(response => { |
| | | this.branchList = response.data || [] |
| | | if (this.branchList.length > 0) { |
| | | // é»è®¤éä¸ç¬¬ä¸ä¸ªåå
¬å¸ |
| | | this.currentBranchId = this.branchList[0].deptId |
| | | } |
| | | }) |
| | | }, |
| | | |
| | | switchBranch(branch) { |
| | | this.currentBranchId = branch.deptId |
| | | // branchDeptIds ååä¼èªå¨è§¦å StaffSelector éæ°å è½½ |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## ð Props 说æ |
| | | |
| | | | Props | ç±»å | é»è®¤å¼ | 说æ | |
| | | |-------|------|--------|------| |
| | | | `value` | Array | `[]` | 已鿩ç人åå表(v-model) | |
| | | | `label` | String | `'æ§è¡ä»»å¡äººå'` | æ ç¾ææ¬ | |
| | | | `required` | Boolean | `false` | æ¯å¦å¿
å¡« | |
| | | | `autoAddCurrentUser` | Boolean | `true` | æ¯å¦èªå¨æ·»å å½åç¨æ· | |
| | | | `currentUserRemovable` | Boolean | `false` | å½åç¨æ·æ¯å¦å¯ç§»é¤ | |
| | | | **`branchDeptIds`** | Array | `null` | **åå
¬å¸IDå表(æ°å¢)** | |
| | | |
| | | --- |
| | | |
| | | ## ð æ°æ®æµç¨ |
| | | |
| | | ### é»è®¤æ¨¡å¼(ä¸ä¼ branchDeptIds) |
| | | |
| | | ``` |
| | | 1. ç»ä»¶ mounted |
| | | â |
| | | 2. loadStaffList() |
| | | â |
| | | 3. 夿 branchDeptIds 为空 |
| | | â |
| | | 4. è°ç¨ listBranchUsers() |
| | | â |
| | | 5. åç«¯æ ¹æ®å½åç¨æ·ç oaOrderClass æ deptId æ¥è¯¢åå
¬å¸ |
| | | â |
| | | 6. è¿å该åå
¬å¸åå
¶åé¨é¨çææç¨æ· |
| | | â |
| | | 7. processUserList() å¤çæ°æ® |
| | | â |
| | | 8. å±ç¤ºç¨æ·å表 |
| | | ``` |
| | | |
| | | ### æå®æ¨¡å¼(ä¼ å
¥ branchDeptIds) |
| | | |
| | | ``` |
| | | 1. ç»ä»¶ mounted |
| | | â |
| | | 2. loadStaffList() |
| | | â |
| | | 3. 夿 branchDeptIds æå¼ |
| | | â |
| | | 4. è°ç¨ listUsersByBranchDeptIds(branchDeptIds) |
| | | â |
| | | 5. åç«¯æ ¹æ®ä¼ å
¥çåå
¬å¸IDå表æ¥è¯¢ |
| | | â |
| | | 6. è¿åæå®åå
¬å¸åå
¶åé¨é¨çææç¨æ· |
| | | â |
| | | 7. processUserList() å¤çæ°æ® |
| | | â |
| | | 8. å±ç¤ºç¨æ·å表 |
| | | ``` |
| | | |
| | | ### 卿忢 |
| | | |
| | | ``` |
| | | 1. ç¶ç»ä»¶ä¿®æ¹ branchDeptIds |
| | | â |
| | | 2. StaffSelector çå¬å°åå(watch) |
| | | â |
| | | 3. éæ°æ§è¡ loadStaffList() |
| | | â |
| | | 4. æ ¹æ®æ°ç branchDeptIds å è½½ç¨æ· |
| | | â |
| | | 5. æ´æ°ç¨æ·å表 |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## â¡ æ§è½ä¼å |
| | | |
| | | ### 1. é¿å
éå¤å è½½ |
| | | ```javascript |
| | | watch: { |
| | | branchDeptIds: { |
| | | handler(newVal, oldVal) { |
| | | // åªæçæ£ååæ¶æéæ°å è½½ |
| | | if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) { |
| | | this.loadStaffList() |
| | | } |
| | | }, |
| | | deep: true |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ### 2. å端ç¼å |
| | | å¯ä»¥å¨ç¶ç»ä»¶å±é¢ç¼åç¨æ·å表: |
| | | ```javascript |
| | | data() { |
| | | return { |
| | | userCache: {} // { '101': [...users], '102': [...users] } |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## â
æµè¯ç¨ä¾ |
| | | |
| | | ### æµè¯1: é»è®¤å è½½ |
| | | **è¾å
¥**: ä¸ä¼ `branchDeptIds` |
| | | **颿**: å è½½å½åç¨æ·çåå
¬å¸ç¨æ· |
| | | **éªè¯**: æ£æ¥ç¨æ·å表æ¯å¦ç¬¦å颿 |
| | | |
| | | ### æµè¯2: å个åå
¬å¸ |
| | | **è¾å
¥**: `branchDeptIds: [101]` |
| | | **颿**: åªå è½½ ID=101 çåå
¬å¸åå
¶åé¨é¨ç¨æ· |
| | | **éªè¯**: æ£æ¥ç¨æ·é¨é¨æ¯å¦é½å±äºè¯¥åå
¬å¸ |
| | | |
| | | ### æµè¯3: å¤ä¸ªåå
¬å¸ |
| | | **è¾å
¥**: `branchDeptIds: [101, 102]` |
| | | **颿**: å 载两个åå
¬å¸åå
¶åé¨é¨çç¨æ· |
| | | **éªè¯**: æ£æ¥ç¨æ·é¨é¨æ¯å¦é½å±äºè¿ä¸¤ä¸ªåå
¬å¸ |
| | | |
| | | ### æµè¯4: 卿忢 |
| | | **æä½**: å
ä¼ `[101]`,åæ¹ä¸º `[102]` |
| | | **颿**: ç¨æ·åè¡¨å®æ¶æ´æ° |
| | | **éªè¯**: æ£æ¥ä¸¤æ¬¡å è½½çç¨æ·æ¯å¦ä¸å |
| | | |
| | | ### æµè¯5: 空æ°ç» |
| | | **è¾å
¥**: `branchDeptIds: []` |
| | | **颿**: è¿å空å表 |
| | | **éªè¯**: ç¨æ·å表为空 |
| | | |
| | | --- |
| | | |
| | | ## ð¡ 使ç¨å»ºè®® |
| | | |
| | | ### åºæ¯1: è·¨åå
¬å¸åä½ä»»å¡ |
| | | å½ä»»å¡éè¦å¤ä¸ªåå
¬å¸ç人åå使¶: |
| | | ```vue |
| | | <staff-selector |
| | | :branch-dept-ids="[101, 102, 103]" |
| | | label="è·¨åºååä½äººå" |
| | | /> |
| | | ``` |
| | | |
| | | ### åºæ¯2: åºåçé |
| | | æä¾åºåéæ©å¨,ç¨æ·èªéåå
¬å¸: |
| | | ```vue |
| | | <region-selector v-model="selectedRegions" /> |
| | | <staff-selector |
| | | :branch-dept-ids="selectedRegions.map(r => r.deptId)" |
| | | /> |
| | | ``` |
| | | |
| | | ### åºæ¯3: æéæ§å¶ |
| | | æ ¹æ®ç¨æ·æé卿éå¶å¯éåå
¬å¸: |
| | | ```javascript |
| | | computed: { |
| | | allowedBranchIds() { |
| | | // æ ¹æ®ç¨æ·è§è²è¿åå
许çåå
¬å¸ID |
| | | return this.currentUser.allowedBranches.map(b => b.deptId) |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## ð æ³¨æäºé¡¹ |
| | | |
| | | 1. **æ°ç»ä¼ é**: `branchDeptIds` å¿
é¡»æ¯æ°ç»ç±»å,å³ä½¿åªæä¸ä¸ªIDä¹è¦ç¨æ°ç» `[101]` |
| | | 2. **å¨ææ´æ°**: ä¿®æ¹ `branchDeptIds` ä¼èªå¨è§¦åéæ°å è½½,æ éæå¨è°ç¨ |
| | | 3. **空å¼å¤ç**: ä¼ å
¥ `null` æä¸ä¼ æ¶,使ç¨é»è®¤çå½åç¨æ·åå
¬å¸ |
| | | 4. **æéæ ¡éª**: åç«¯ä¼æ ¹æ®ç¨æ·æéè¿æ»¤æ°æ®,å端æ éé¢å¤å¤ç |
| | | |
| | | --- |
| | | |
| | | ## ð ç¸å
³æä»¶ |
| | | |
| | | ### å端 |
| | | - `SysUserController.java` - ç¨æ·æ§å¶å¨ |
| | | - `SysUserService.java` - ç¨æ·æå¡æ¥å£ |
| | | - `SysUserServiceImpl.java` - ç¨æ·æå¡å®ç° |
| | | - `SysUserMapper.xml` - SQLæ å°æä»¶ |
| | | |
| | | ### å端 |
| | | - `app/api/system/user.js` - ç¨æ·API |
| | | - `app/pagesTask/components/StaffSelector.vue` - 人åéæ©å¨ç»ä»¶ |
| | | - `app/api/system/dept.js` - é¨é¨API |
| | | |
| | | --- |
| | | |
| | | ## ð çæ¬åå² |
| | | |
| | | | çæ¬ | æ¥æ | 说æ | |
| | | |------|------|------| |
| | | | 1.0 | 2025-12-04 | åå§çæ¬,æ°å¢æåå
¬å¸IDå è½½ç¨æ·åè½ | |
| | |
| | | import com.ruoyi.system.service.ISysPostService; |
| | | import com.ruoyi.system.service.ISysRoleService; |
| | | import com.ruoyi.system.service.ISysUserService; |
| | | import com.ruoyi.system.domain.vo.BranchUserQueryVO; |
| | | |
| | | /** |
| | | * ç¨æ·ä¿¡æ¯ |
| | |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ®åå
¬å¸IDå表è·åç¨æ·(POSTæ¹å¼) |
| | | */ |
| | | @PostMapping("/branch/users") |
| | | public AjaxResult listUsersByBranchDepts(@RequestBody BranchUserQueryVO queryVO) |
| | | { |
| | | List<Long> branchDeptIds = queryVO.getBranchDeptIds(); |
| | | |
| | | if (branchDeptIds == null || branchDeptIds.isEmpty()) { |
| | | return success(new java.util.ArrayList<>()); |
| | | } |
| | | |
| | | // æ¥è¯¢è¿äºåå
¬å¸åå
¶ææåé¨é¨çç¨æ· |
| | | List<SysUser> users = userService.selectUsersByBranchDeptIds(branchDeptIds); |
| | | |
| | | return success(users); |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ®oaUserIdæ¥è¯¢ç¨æ·ä¿¡æ¯ |
| | | */ |
| | | @GetMapping("/oa-user/{oaUserId}") |
| | |
| | | import com.ruoyi.system.service.ISysTaskEmergencyService; |
| | | import com.ruoyi.system.service.ISysUserService; |
| | | import com.ruoyi.system.service.ITaskAttachmentSyncService; |
| | | import com.ruoyi.system.service.IWechatAccessTokenService; |
| | | import com.ruoyi.system.task.ITaskAttachmentService; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | |
| | | |
| | | @Autowired |
| | | private ISysUserService userService; |
| | | |
| | | @Autowired |
| | | private IWechatAccessTokenService wechatAccessTokenService; |
| | | /** |
| | | * æ¥è¯¢ä»»å¡éä»¶å表 |
| | | */ |
| | |
| | | |
| | | /** |
| | | * ä»å¾®ä¿¡mediaIdä¸ä¼ éä»¶ï¼å¾®ä¿¡å°ç¨åºä¸ç¨ï¼ |
| | | * 使ç¨åºç¨çº§ç¼åçAccessToken |
| | | */ |
| | | @Log(title = "ä»»å¡éä»¶", businessType = BusinessType.INSERT) |
| | | @PostMapping("/uploadFromWechat/{taskId}") |
| | |
| | | @RequestParam("mediaId") String mediaId, |
| | | @RequestParam(value = "category", required = false) String category) { |
| | | try { |
| | | // è·å微信AccessToken |
| | | String accessToken = WechatUtils.getAccessToken( |
| | | wechatConfig.getAppId(), |
| | | wechatConfig.getAppSecret() |
| | | ); |
| | | // è·å微信AccessTokenï¼ä½¿ç¨åºç¨çº§ç¼åï¼ |
| | | String accessToken = wechatAccessTokenService.getAppAccessToken(); |
| | | if (accessToken == null || accessToken.isEmpty()) { |
| | | return error("è·å微信AccessToken失败"); |
| | | } |
| | |
| | | import com.ruoyi.common.core.domain.entity.SysUser; |
| | | import com.ruoyi.common.utils.DateUtils; |
| | | import com.ruoyi.system.service.IWechatTaskNotifyService; |
| | | import com.ruoyi.system.service.IWechatAccessTokenService; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | |
| | | |
| | | @Autowired |
| | | private IWechatTaskNotifyService wechatTaskNotifyService; |
| | | |
| | | @Autowired |
| | | private IWechatAccessTokenService wechatAccessTokenService; |
| | | |
| | | /** |
| | | * è·å微信AccessToken |
| | | * è·å微信AccessTokenï¼ä½¿ç¨åºç¨çº§ç¼åï¼ |
| | | */ |
| | | @GetMapping("/accessToken") |
| | | public AjaxResult getAccessToken() { |
| | | try { |
| | | String accessToken = WechatUtils.getAccessToken( |
| | | wechatConfig.getAppId(), |
| | | wechatConfig.getAppSecret() |
| | | ); |
| | | String accessToken = wechatAccessTokenService.getAppAccessToken(); |
| | | if (accessToken == null || accessToken.isEmpty()) { |
| | | return error("è·å微信AccessToken失败"); |
| | | } |
| | |
| | | basename: i18n/messages |
| | | profiles: |
| | | # ç¯å¢ dev|test|prod |
| | | active: dev |
| | | active: prod |
| | | # æä»¶ä¸ä¼ |
| | | servlet: |
| | | multipart: |
| | |
| | | /** 微信æµç§° */ |
| | | private String wechatNickname; |
| | | |
| | | |
| | | |
| | | public SysUser() |
| | | { |
| | | |
| | |
| | | { |
| | | this.wechatNickname = wechatNickname; |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | public String toString() { |
| | |
| | | .append("openId", getOpenId()) |
| | | .append("unionId", getUnionId()) |
| | | .append("wechatNickname", getWechatNickname()) |
| | | |
| | | .toString(); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.ruoyi.system.domain.vo; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * åå
¬å¸ç¨æ·æ¥è¯¢VO |
| | | * |
| | | * @author ruoyi |
| | | */ |
| | | public class BranchUserQueryVO |
| | | { |
| | | /** åå
¬å¸IDå表 */ |
| | | private List<Long> branchDeptIds; |
| | | |
| | | public List<Long> getBranchDeptIds() |
| | | { |
| | | return branchDeptIds; |
| | | } |
| | | |
| | | public void setBranchDeptIds(List<Long> branchDeptIds) |
| | | { |
| | | this.branchDeptIds = branchDeptIds; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() |
| | | { |
| | | return "BranchUserQueryVO{" + |
| | | "branchDeptIds=" + branchDeptIds + |
| | | '}'; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.ruoyi.system.service; |
| | | |
| | | /** |
| | | * 微信AccessTokenæå¡æ¥å£ |
| | | * æä¾åºç¨çº§AccessTokençç»ä¸ç®¡çï¼ä½¿ç¨sys_config表ç¼å |
| | | * |
| | | * @author ruoyi |
| | | * @date 2025-12-04 |
| | | */ |
| | | public interface IWechatAccessTokenService { |
| | | |
| | | /** |
| | | * è·ååºç¨çº§å¾®ä¿¡AccessTokenï¼å¸¦ç¼åï¼ |
| | | * ä¼å
ä»sys_config读åå¹¶å¤ææææï¼è¿æåéæ°è·åå¹¶ååsys_config |
| | | * |
| | | * @return Access Tokenï¼å¤±è´¥è¿ånull |
| | | */ |
| | | String getAppAccessToken(); |
| | | |
| | | /** |
| | | * 强å¶å·æ°AccessToken |
| | | * 忽ç¥ç¼åï¼ç´æ¥ä»å¾®ä¿¡è·åæ°Tokenå¹¶æ´æ°ç¼å |
| | | * |
| | | * @return æ°çAccess Tokenï¼å¤±è´¥è¿ånull |
| | | */ |
| | | String refreshAppAccessToken(); |
| | | } |
| | |
| | | if (sysUser != null) { |
| | | TaskCreateVO.AssigneeInfo assigneeInfo = new TaskCreateVO.AssigneeInfo(); |
| | | assigneeInfo.setUserId(sysUser.getUserId()); // 使ç¨ç³»ç»ç¨æ·ID |
| | | assigneeInfo.setUserName(sysUser.getUserName()); |
| | | assigneeInfo.setUserName(sysUser.getNickName()); |
| | | // æ ¹æ®EntourageStateç¡®å®è§è²ç±»å |
| | | // 1,2 叿ºï¼3,5 å»çï¼4,6 æ¤å£« |
| | | if ("1".equals(entourageState) || "2".equals(entourageState)) { |
| | |
| | | * å°æ°ç³»ç»æ¯ä»è®°å½åæ¥å°æ§ç³»ç»PaidMoney表 |
| | | */ |
| | | @Override |
| | | @Transactional |
| | | public boolean syncPaymentToLegacy(SysTaskPayment payment) { |
| | | Long paymentId = payment.getId(); |
| | | try { |
| | |
| | | } |
| | | } |
| | | } |
| | | if (createVO.getHospitalOut().getLongitude() != null) { |
| | | existingInfo.setHospitalOutLongitude(createVO.getHospitalOut().getLongitude()); |
| | | } |
| | | if (createVO.getHospitalOut().getLatitude() != null) { |
| | | existingInfo.setHospitalOutLatitude(createVO.getHospitalOut().getLatitude()); |
| | | } |
| | | |
| | | } |
| | | |
| | | // æ´æ°è½¬å
¥å»é¢ä¿¡æ¯ |
| | |
| | | } |
| | | } |
| | | } |
| | | if (createVO.getHospitalIn().getLongitude() != null) { |
| | | existingInfo.setHospitalInLongitude(createVO.getHospitalIn().getLongitude()); |
| | | } |
| | | if (createVO.getHospitalIn().getLatitude() != null) { |
| | | existingInfo.setHospitalInLatitude(createVO.getHospitalIn().getLatitude()); |
| | | } |
| | | |
| | | } |
| | | |
| | | // æ´æ°è´¹ç¨ä¿¡æ¯ |
| | |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Calendar; |
| | | import java.util.Date; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | |
| | | } |
| | | } |
| | | |
| | | // 4. æ¥è¯¢è¯¥æ¥æç任塿¶é´åºé´ï¼è®¡ç®ä»»å¡éç¨åéä»»å¡éç¨ |
| | | List<TaskTimeInterval> taskIntervals = vehicleMileageStatsMapper.selectTaskTimeIntervals(vehicleId, dayStart, dayEnd); |
| | | |
| | | // 4. 计ç®ä»»å¡éç¨åéä»»å¡éç¨ï¼ä¼åï¼ä¼å
使ç¨task_idç´æ¥èåï¼ |
| | | BigDecimal taskMileage = BigDecimal.ZERO; |
| | | BigDecimal nonTaskMileage = BigDecimal.ZERO; |
| | | int taskCount = 0; // 任塿°é |
| | | |
| | | // 4.1 ç»è®¡ætask_idçåæ®µæ°é |
| | | int segmentsWithTask = 0; |
| | | for (VehicleGpsSegmentMileage segment : segments) { |
| | | Date segStart = segment.getSegmentStartTime(); |
| | | Date segEnd = segment.getSegmentEndTime(); |
| | | BigDecimal segDistance = segment.getSegmentDistance() != null ? segment.getSegmentDistance() : BigDecimal.ZERO; |
| | | if (segment.getTaskId() != null) { |
| | | segmentsWithTask++; |
| | | } |
| | | } |
| | | |
| | | // 4.2 妿大é¨ååæ®µé½ætask_idï¼ä½¿ç¨ä¼åæ¹æ¡ï¼ç´æ¥ætask_idèåï¼ |
| | | if (segmentsWithTask > segments.size() * 0.8) { |
| | | logger.debug("车è¾ID: {} æ¥æ: {} 使ç¨ä¼åæ¹æ¡ï¼ç´æ¥ætask_idèåï¼{}ä¸ªåæ®µætask_idï¼å æ¯{}%ï¼", |
| | | vehicleId, statDate, segmentsWithTask, (segmentsWithTask * 100.0 / segments.size())); |
| | | |
| | | // 计ç®è¯¥å段ä¸ä»»å¡æ¶æ®µçéå æ¯ä¾ |
| | | double taskRatio = calculateTaskOverlapRatio(segStart, segEnd, taskIntervals); |
| | | // 使ç¨Setç»è®¡å»éçä»»å¡IDæ°é |
| | | Set<Long> uniqueTaskIds = new HashSet<>(); |
| | | |
| | | // åæéç¨ |
| | | BigDecimal taskDist = segDistance.multiply(BigDecimal.valueOf(taskRatio)); |
| | | BigDecimal nonTaskDist = segDistance.multiply(BigDecimal.valueOf(1 - taskRatio)); |
| | | // ç´æ¥ætask_idåç»èå |
| | | for (VehicleGpsSegmentMileage segment : segments) { |
| | | BigDecimal segDistance = segment.getSegmentDistance() != null ? segment.getSegmentDistance() : BigDecimal.ZERO; |
| | | |
| | | if (segment.getTaskId() != null) { |
| | | // æä»»å¡IDï¼è®¡å
¥ä»»å¡éç¨ |
| | | taskMileage = taskMileage.add(segDistance); |
| | | uniqueTaskIds.add(segment.getTaskId()); |
| | | } else { |
| | | // 没æä»»å¡IDï¼è®¡å
¥éä»»å¡éç¨ |
| | | nonTaskMileage = nonTaskMileage.add(segDistance); |
| | | } |
| | | } |
| | | |
| | | taskMileage = taskMileage.add(taskDist); |
| | | nonTaskMileage = nonTaskMileage.add(nonTaskDist); |
| | | // 设置å»éåç任塿°é |
| | | taskCount = uniqueTaskIds.size(); |
| | | |
| | | } else { |
| | | // 4.3 éçº§æ¹æ¡ï¼ä½¿ç¨åæçæ¶é´éå è®¡ç®æ¹å¼ |
| | | logger.debug("车è¾ID: {} æ¥æ: {} 使ç¨éçº§æ¹æ¡ï¼æ¶é´éå 计ç®ï¼åªæ{}ä¸ªåæ®µætask_idï¼å æ¯{}%ï¼", |
| | | vehicleId, statDate, segmentsWithTask, (segmentsWithTask * 100.0 / segments.size())); |
| | | |
| | | List<TaskTimeInterval> taskIntervals = vehicleMileageStatsMapper.selectTaskTimeIntervals(vehicleId, dayStart, dayEnd); |
| | | |
| | | for (VehicleGpsSegmentMileage segment : segments) { |
| | | Date segStart = segment.getSegmentStartTime(); |
| | | Date segEnd = segment.getSegmentEndTime(); |
| | | BigDecimal segDistance = segment.getSegmentDistance() != null ? segment.getSegmentDistance() : BigDecimal.ZERO; |
| | | |
| | | // 计ç®è¯¥å段ä¸ä»»å¡æ¶æ®µçéå æ¯ä¾ |
| | | double taskRatio = calculateTaskOverlapRatio(segStart, segEnd, taskIntervals); |
| | | |
| | | // åæéç¨ |
| | | BigDecimal taskDist = segDistance.multiply(BigDecimal.valueOf(taskRatio)); |
| | | BigDecimal nonTaskDist = segDistance.multiply(BigDecimal.valueOf(1 - taskRatio)); |
| | | |
| | | taskMileage = taskMileage.add(taskDist); |
| | | nonTaskMileage = nonTaskMileage.add(nonTaskDist); |
| | | } |
| | | |
| | | // è®¾ç½®ä»»å¡æ°é |
| | | taskCount = taskIntervals == null ? 0 : taskIntervals.size(); |
| | | } |
| | | |
| | | // 计ç®ä»»å¡éç¨å æ¯ |
| | |
| | | stats.setNonTaskMileage(nonTaskMileage.setScale(2, RoundingMode.HALF_UP)); |
| | | stats.setTaskRatio(taskRatio); |
| | | stats.setGpsPointCount(totalGpsPoints); |
| | | stats.setTaskCount(taskIntervals == null ? 0 : taskIntervals.size()); |
| | | stats.setTaskCount(taskCount); |
| | | stats.setSegmentCount(segments.size()); |
| | | stats.setDataSource("segment"); // æ è®°æ°æ®æ¥æºä¸ºåæ®µæ±æ» |
| | | |
| New file |
| | |
| | | package com.ruoyi.system.service.impl; |
| | | |
| | | import com.ruoyi.common.config.WechatConfig; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.common.utils.WechatUtils; |
| | | import com.ruoyi.system.domain.SysConfig; |
| | | import com.ruoyi.system.mapper.SysConfigMapper; |
| | | import com.ruoyi.system.service.ISysConfigService; |
| | | import com.ruoyi.system.service.IWechatAccessTokenService; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | /** |
| | | * 微信AccessTokenæå¡å®ç° |
| | | * æä¾åºç¨çº§AccessTokençç»ä¸ç®¡çï¼ä½¿ç¨sys_config表ç¼å |
| | | * |
| | | * @author ruoyi |
| | | * @date 2025-12-04 |
| | | */ |
| | | @Service |
| | | public class WechatAccessTokenServiceImpl implements IWechatAccessTokenService { |
| | | |
| | | private static final Logger log = LoggerFactory.getLogger(WechatAccessTokenServiceImpl.class); |
| | | |
| | | @Autowired |
| | | private WechatConfig wechatConfig; |
| | | |
| | | @Autowired |
| | | private ISysConfigService configService; |
| | | |
| | | @Autowired |
| | | private SysConfigMapper configMapper; |
| | | |
| | | /** |
| | | * è·ååºç¨çº§å¾®ä¿¡AccessTokenï¼å¸¦ç¼åï¼ |
| | | * ä¼å
ä»sys_config读åå¹¶å¤ææææï¼è¿æåéæ°è·åå¹¶ååsys_config |
| | | */ |
| | | @Override |
| | | public String getAppAccessToken() { |
| | | try { |
| | | String appId = wechatConfig.getAppId(); |
| | | String tokenKey = "weixin.access_token." + appId; |
| | | String expireKey = "weixin.access_token_expires." + appId; |
| | | |
| | | String cachedToken = configService.selectConfigByKey(tokenKey); |
| | | String cachedExpireStr = configService.selectConfigByKey(expireKey); |
| | | long now = System.currentTimeMillis(); |
| | | long expireTs = 0L; |
| | | if (StringUtils.isNotEmpty(cachedExpireStr)) { |
| | | try { |
| | | expireTs = Long.parseLong(cachedExpireStr); |
| | | } catch (NumberFormatException e) { |
| | | expireTs = 0L; |
| | | } |
| | | } |
| | | |
| | | // ç¼åææä¸æªè¿æï¼é¢ç60ç§å®å
¨è¾¹çï¼ |
| | | if (StringUtils.isNotEmpty(cachedToken) && expireTs > now + 60000L) { |
| | | log.debug("使ç¨ç¼åçAccessTokenï¼å©ä½æææï¼{}ç§", (expireTs - now) / 1000); |
| | | return cachedToken; |
| | | } |
| | | |
| | | // éæ°è·åï¼å¹¶åå
¥sys_config |
| | | log.info("AccessTokenå·²è¿ææä¸åå¨ï¼éæ°è·å"); |
| | | return refreshAppAccessToken(); |
| | | } catch (Exception e) { |
| | | log.error("è·ååºç¨çº§å¾®ä¿¡AccessToken失败", e); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 强å¶å·æ°AccessToken |
| | | * 忽ç¥ç¼åï¼ç´æ¥ä»å¾®ä¿¡è·åæ°Tokenå¹¶æ´æ°ç¼å |
| | | */ |
| | | @Override |
| | | public String refreshAppAccessToken() { |
| | | try { |
| | | String appId = wechatConfig.getAppId(); |
| | | String appSecret = wechatConfig.getAppSecret(); |
| | | String tokenKey = "weixin.access_token." + appId; |
| | | String expireKey = "weixin.access_token_expires." + appId; |
| | | |
| | | String newToken = WechatUtils.getAccessToken(appId, appSecret); |
| | | if (StringUtils.isEmpty(newToken)) { |
| | | log.error("ä»å¾®ä¿¡è·åAccessToken失败"); |
| | | return null; |
| | | } |
| | | |
| | | long now = System.currentTimeMillis(); |
| | | long newExpireTs = now + 7200L * 1000L; // 7200ç§ |
| | | |
| | | upsertConfig(tokenKey, newToken); |
| | | upsertConfig(expireKey, String.valueOf(newExpireTs)); |
| | | |
| | | log.info("AccessTokenå·æ°æåï¼æææï¼7200ç§"); |
| | | return newToken; |
| | | } catch (Exception e) { |
| | | log.error("å·æ°åºç¨çº§å¾®ä¿¡AccessToken失败", e); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ®configKeyåå
¥ææ´æ°sys_config |
| | | */ |
| | | private void upsertConfig(String key, String value) { |
| | | SysConfig exist = configMapper.checkConfigKeyUnique(key); |
| | | if (exist != null && exist.getConfigId() != null) { |
| | | exist.setConfigValue(value); |
| | | configMapper.updateConfig(exist); |
| | | } else { |
| | | SysConfig cfg = new SysConfig(); |
| | | cfg.setConfigKey(key); |
| | | cfg.setConfigName(key); |
| | | cfg.setConfigValue(value); |
| | | cfg.setConfigType("Y"); // å
ç½®åæ° |
| | | configMapper.insertConfig(cfg); |
| | | } |
| | | } |
| | | } |
| | |
| | | import com.ruoyi.common.core.domain.entity.SysUser; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.common.utils.http.HttpUtils; |
| | | import com.ruoyi.system.domain.SysConfig; |
| | | import com.ruoyi.system.mapper.SysConfigMapper; |
| | | import com.ruoyi.system.service.ISysConfigService; |
| | | import com.ruoyi.system.service.IWechatLoginService; |
| | | import com.ruoyi.system.service.ISysUserService; |
| | | |
| | |
| | | @Autowired |
| | | private ISysUserService userService; |
| | | |
| | | @Autowired |
| | | private ISysConfigService configService; |
| | | |
| | | @Autowired |
| | | private SysConfigMapper configMapper; |
| | | |
| | | /** |
| | | * 微信API - code2Session |
| | | */ |
| | |
| | | */ |
| | | private static final String GET_PHONE_NUMBER_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber"; |
| | | |
| | | private static final String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token"; |
| | | |
| | | /** |
| | | * 微信API - è·åaccess_token |
| | | */ |
| | | private static final String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token"; |
| | | /** |
| | | * æ ¹æ®configKeyåå
¥ææ´æ°sys_config |
| | | */ |
| | | private void upsertConfig(String key, String value) { |
| | | SysConfig exist = configMapper.checkConfigKeyUnique(key); |
| | | if (exist != null && exist.getConfigId() != null) { |
| | | exist.setConfigValue(value); |
| | | configMapper.updateConfig(exist); |
| | | } else { |
| | | SysConfig cfg = new SysConfig(); |
| | | cfg.setConfigKey(key); |
| | | cfg.setConfigName(key); |
| | | cfg.setConfigValue(value); |
| | | cfg.setConfigType("Y"); |
| | | configMapper.insertConfig(cfg); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * éè¿å¾®ä¿¡codeè·åopenidåsession_key |
| | |
| | | return result; |
| | | } |
| | | |
| | | // ä¼°ç®è¿ææ¶é´(微信access_tokené»è®¤7200ç§ææ) |
| | | java.util.Date accessTokenExpiresAt = new java.util.Date(System.currentTimeMillis() + 7200L * 1000L); |
| | | |
| | | // æå»ºè¯·æ±URL |
| | | String url = GET_PHONE_NUMBER_URL + "?access_token=" + accessToken; |
| | | |
| | |
| | | result.put("phoneNumber", phoneInfo.getString("phoneNumber")); |
| | | result.put("purePhoneNumber", phoneInfo.getString("purePhoneNumber")); |
| | | result.put("countryCode", phoneInfo.getString("countryCode")); |
| | | // è¿åå½å使ç¨çaccess_tokenåæææï¼ä¾¿äºè°ç¨æ¹åå¨ |
| | | result.put("accessToken", accessToken); |
| | | result.put("accessTokenExpiresAt", accessTokenExpiresAt); |
| | | |
| | | return result; |
| | | } |
| | |
| | | return result; |
| | | } |
| | | |
| | | // 5. æ´æ°ç¨æ·çå¾®ä¿¡ä¿¡æ¯ |
| | | // 5. æ´æ°ç¨æ·ç微信信æ¯(å
æ¬access_tokenä¸è¿ææ¶é´) |
| | | SysUser updateUser = new SysUser(); |
| | | updateUser.setUserId(user.getUserId()); |
| | | updateUser.setOpenId(openId); |
| | |
| | | { |
| | | updateUser.setUnionId(unionId); |
| | | } |
| | | // ä¿åæ¬æ¬¡è°ç¨ä½¿ç¨çaccess_tokenåè¿ææ¶é´å°åºç¨çº§é
ç½®ï¼ä¾¿äºåç»å¤ç¨ï¼ |
| | | Object at = phoneResult.get("accessToken"); |
| | | Object atExp = phoneResult.get("accessTokenExpiresAt"); |
| | | if (at != null) { |
| | | String appId = wechatConfig.getAppId(); |
| | | String tokenKey = "weixin.access_token." + appId; |
| | | String expireKey = "weixin.access_token_expires." + appId; |
| | | upsertConfig(tokenKey, at.toString()); |
| | | long expireTs = (atExp instanceof java.util.Date) ? ((java.util.Date) atExp).getTime() : (System.currentTimeMillis() + 7200L * 1000L); |
| | | upsertConfig(expireKey, String.valueOf(expireTs)); |
| | | } |
| | | userService.updateUser(updateUser); |
| | | |
| | | log.info("ç¨æ·{}å¾®ä¿¡ä¿¡æ¯æ´æ°æå", user.getUserName()); |
| | |
| | | import com.ruoyi.common.utils.DateUtils; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.common.utils.WechatUtils; |
| | | import com.ruoyi.system.domain.SysTask; |
| | | import com.ruoyi.system.domain.SysTaskEmergency; |
| | | import com.ruoyi.system.domain.SysConfig; |
| | | import com.ruoyi.system.mapper.SysConfigMapper; |
| | | import com.ruoyi.system.mapper.SysTaskEmergencyMapper; |
| | | import com.ruoyi.system.mapper.SysTaskMapper; |
| | | import com.ruoyi.system.domain.SysTask; |
| | | import com.ruoyi.system.domain.SysTaskEmergency; |
| | | import com.ruoyi.system.mapper.SysUserMapper; |
| | | import com.ruoyi.system.service.IWechatTaskNotifyService; |
| | | import com.ruoyi.system.service.IWechatAccessTokenService; |
| | | import com.ruoyi.system.service.ISysConfigService; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | |
| | | @Autowired |
| | | private WechatConfig wechatConfig; |
| | | |
| | | @Autowired |
| | | private IWechatAccessTokenService wechatAccessTokenService; |
| | | |
| | | @Autowired |
| | | private ISysConfigService configService; |
| | | |
| | | |
| | | |
| | | |
| | | /** |
| | | * æ£æ¥æ¯å¦å¯ç¨è®¢é
æ¶æ¯åé |
| | | * |
| | | * @return true=å¯ç¨ï¼false=ç¦ç¨ |
| | | */ |
| | | private boolean isSubscribeMessageEnabled() { |
| | | try { |
| | | String enabled = configService.selectConfigByKey("wechat.subscribe.message.enabled"); |
| | | return "true".equalsIgnoreCase(enabled); |
| | | } catch (Exception e) { |
| | | log.warn("è·å订é
æ¶æ¯å¼å
³é
置失败ï¼é»è®¤å¯ç¨", e); |
| | | return true; // é»è®¤å¯ç¨ |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * åéä»»å¡éç¥æ¶æ¯ç»æå®ç¨æ·å表 |
| | | * |
| | |
| | | */ |
| | | @Override |
| | | public int sendTaskNotifyMessage(Long taskId, List<Long> userIds, Long excludeUserId) { |
| | | // æ£æ¥è®¢é
æ¶æ¯å¼å
³ |
| | | if (!isSubscribeMessageEnabled()) { |
| | | log.info("订é
æ¶æ¯åéå·²å
³éï¼è·³è¿åéï¼taskId={}", taskId); |
| | | return 0; |
| | | } |
| | | if (taskId == null || userIds == null || userIds.isEmpty()) { |
| | | log.warn("åé微信任å¡éç¥åæ°ä¸å®æ´ï¼taskId={}, userIds={}", taskId, userIds); |
| | | return 0; |
| | |
| | | // æ¥è¯¢æ¥æä¿¡æ¯ |
| | | SysTaskEmergency emergency = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(taskId); |
| | | |
| | | // è·å微信AccessToken |
| | | String accessToken = WechatUtils.getAccessToken(wechatConfig.getAppId(), wechatConfig.getAppSecret()); |
| | | // è·å微信AccessTokenï¼èµ°åºç¨çº§ç¼åï¼ |
| | | String accessToken = wechatAccessTokenService.getAppAccessToken(); |
| | | if (StringUtils.isEmpty(accessToken)) { |
| | | log.error("è·å微信AccessTokenå¤±è´¥ï¼æ æ³åéä»»å¡éç¥"); |
| | | return 0; |
| | |
| | | <if test="oaOrderClass != null">oa_order_class = #{oaOrderClass},</if> |
| | | <if test="openId != null and openId != ''">open_id = #{openId},</if> |
| | | <if test="unionId != null and unionId != ''">union_id = #{unionId},</if> |
| | | <if test="wechatNickname != null and wechatNickname != ''">wechat_nickname = #{wechatNickname},</if> |
| | | <if test="loginIp != null and loginIp != ''">login_ip = #{loginIp},</if> |
| | | <if test="loginDate != null">login_date = #{loginDate},</if> |
| | | <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> |
| | |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // æ¥è¯¢è½¦è¾æå®æ¥æçGPSåæ®µéç¨æç» |
| | | export function getSegmentsByDateRange(vehicleId, startDate, endDate) { |
| | | return request({ |
| | | url: '/system/gpsSegment/range', |
| | | method: 'get', |
| | | params: { |
| | | vehicleId: vehicleId, |
| | | startDate: startDate, |
| | | endDate: endDate |
| | | } |
| | | }) |
| | | } |
| | |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="GPSç¹æ°" align="center" prop="gpsPointCount" width="90" /> |
| | | <el-table-column label="任塿°" align="center" prop="taskCount" width="80" /> |
| | | <el-table-column label="åæ®µæ°" align="center" prop="segmentCount" width="80" /> |
| | | |
| | | <el-table-column label="ç»è®¡æ¶é´" align="center" prop="createTime" width="160"> |
| | | <template slot-scope="scope"> |
| | | <span>{{ parseTime(scope.row.createTime) }}</span> |
| | |
| | | </el-dialog> |
| | | |
| | | <!-- 详æ
å¯¹è¯æ¡ --> |
| | | <el-dialog title="éç¨ç»è®¡è¯¦æ
" :visible.sync="detailOpen" width="600px" append-to-body> |
| | | <el-dialog title="éç¨ç»è®¡è¯¦æ
" :visible.sync="detailOpen" width="900px" append-to-body> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="车çå·">{{ detailData.vehicleNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="å½å±åå
¬å¸">{{ detailData.deptName || '-' }}</el-descriptions-item> |
| | |
| | | {{ formatRatio(detailData.taskRatio) }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="GPSç¹æ°">{{ detailData.gpsPointCount }}</el-descriptions-item> |
| | | <el-descriptions-item label="任塿°">{{ detailData.taskCount }}</el-descriptions-item> |
| | | <el-descriptions-item label="åæ®µæ°">{{ detailData.segmentCount }}</el-descriptions-item> |
| | | <el-descriptions-item label="æ°æ®æ¥æº"> |
| | | <el-tag :type="detailData.dataSource === 'segment' ? 'success' : 'info'" size="small"> |
| | | {{ detailData.dataSource === 'segment' ? 'ä»åæ®µæ±æ»' : 'ç´æ¥è®¡ç®' }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç»è®¡æ¶é´" :span="2"> |
| | | {{ parseTime(detailData.createTime) }} |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <!-- åæ®µæç»è¡¨æ ¼ --> |
| | | <div v-if="segmentList.length > 0" style="margin-top: 20px;"> |
| | | <el-divider content-position="left"> |
| | | <i class="el-icon-tickets"></i> éç¨å段æç» |
| | | </el-divider> |
| | | |
| | | <el-table |
| | | :data="segmentList" |
| | | size="small" |
| | | :max-height="400" |
| | | stripe |
| | | border |
| | | v-loading="segmentLoading" |
| | | > |
| | | <el-table-column type="index" label="åºå·" width="50" align="center" /> |
| | | <el-table-column label="å¼å§æ¶é´" prop="segmentStartTime" width="160" align="center"> |
| | | <template slot-scope="scope"> |
| | | {{ parseTime(scope.row.segmentStartTime, '{y}-{m}-{d} {h}:{i}:{s}') }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç»ææ¶é´" prop="segmentEndTime" width="160" align="center"> |
| | | <template slot-scope="scope"> |
| | | {{ parseTime(scope.row.segmentEndTime, '{y}-{m}-{d} {h}:{i}:{s}') }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="éç¨(km)" prop="segmentDistance" width="100" align="center"> |
| | | <template slot-scope="scope"> |
| | | <span class="mileage-value">{{ (scope.row.segmentDistance || 0).toFixed(2) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column label="å
³èä»»å¡" prop="taskCode" align="center" min-width="120"> |
| | | <template slot-scope="scope"> |
| | | <el-tag v-if="scope.row.taskCode" size="small" type="success">{{ scope.row.taskCode }}</el-tag> |
| | | <span v-else style="color: #909399;">æ ä»»å¡</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="è®¡ç®æ¹å¼" prop="calculateMethod" align="center" width="100"> |
| | | <template slot-scope="scope"> |
| | | <el-tag size="small" :type="scope.row.calculateMethod === 'haversine' ? 'primary' : 'info'"> |
| | | {{ scope.row.calculateMethod === 'haversine' ? 'çé¢è·ç¦»' : 'ç´çº¿è·ç¦»' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div slot="footer" class="dialog-footer"> |
| | | <el-button @click="detailOpen = false">å
³ é</el-button> |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import { listMileageStats, getMileageStats, delMileageStats, calculateMileageStats, batchCalculateMileageStats } from "@/api/system/mileageStats"; |
| | | import { listMileageStats, getMileageStats, delMileageStats, calculateMileageStats, batchCalculateMileageStats, getSegmentsByDateRange } from "@/api/system/mileageStats"; |
| | | import { listDept } from "@/api/system/dept"; |
| | | |
| | | export default { |
| | |
| | | detailOpen: false, |
| | | // 详æ
æ°æ® |
| | | detailData: {}, |
| | | // åæ®µæç»å表 |
| | | segmentList: [], |
| | | // åæ®µæç»å è½½ç¶æ |
| | | segmentLoading: false, |
| | | // æå¨ç»è®¡å è½½ç¶æ |
| | | calculateLoading: false, |
| | | // æ¹éç»è®¡å è½½ç¶æ |
| | |
| | | /** æ¥ç详æ
æé®æä½ */ |
| | | handleView(row) { |
| | | const statsId = row.statsId; |
| | | |
| | | // éç½®åæ®µæç» |
| | | this.segmentList = []; |
| | | |
| | | // å è½½ç»è®¡è¯¦æ
|
| | | getMileageStats(statsId).then(response => { |
| | | this.detailData = response.data; |
| | | this.detailOpen = true; |
| | | |
| | | // 妿æè½¦è¾IDåç»è®¡æ¥æï¼å è½½åæ®µæç» |
| | | if (this.detailData.vehicleId && this.detailData.statDate) { |
| | | this.loadSegmentDetails(this.detailData.vehicleId, this.detailData.statDate); |
| | | } |
| | | }); |
| | | }, |
| | | |
| | | /** å è½½åæ®µæç» */ |
| | | loadSegmentDetails(vehicleId, statDate) { |
| | | this.segmentLoading = true; |
| | | |
| | | // æ ¼å¼åæ¥æï¼ç»è®¡æ¥æçå¼å§åç»ææ¶é´ |
| | | const startDate = this.parseTime(statDate, '{y}-{m}-{d}'); |
| | | const endDate = this.parseTime(statDate, '{y}-{m}-{d}'); |
| | | |
| | | // æ¥è¯¢è¯¥æ¥æçåæ®µéç¨æç» |
| | | getSegmentsByDateRange(vehicleId, startDate, endDate).then(response => { |
| | | if (response.code === 200 && response.data) { |
| | | this.segmentList = response.data; |
| | | console.log('åæ®µæç»æ°æ®ï¼', this.segmentList); |
| | | } else { |
| | | this.segmentList = []; |
| | | } |
| | | }).catch(error => { |
| | | console.error('å è½½åæ®µæç»å¤±è´¥', error); |
| | | this.segmentList = []; |
| | | this.$modal.msgWarning('å è½½åæ®µæç»å¤±è´¥ï¼ä½ä¸å½±åç»è®¡æ°æ®æ¥ç'); |
| | | }).finally(() => { |
| | | this.segmentLoading = false; |
| | | }); |
| | | }, |
| | | /** å é¤æé®æä½ */ |
| New file |
| | |
| | | -- 微信ç»å½tokenåå¨å段 |
| | | -- å¨sys_user表æ°å¢åå¨å¾®ä¿¡access_tokenä¸è¿ææ¶é´çåæ®µ |
| | | |
| | | ALTER TABLE `sys_user` |
| | | ADD COLUMN `wechat_access_token` VARCHAR(1024) NULL COMMENT '微信AccessTokenï¼ç½é¡µææææè¿è·åï¼' AFTER `wechat_nickname`, |
| | | ADD COLUMN `wechat_token_expires_at` DATETIME NULL COMMENT '微信AccessTokenè¿ææ¶é´' AFTER `wechat_access_token`; |
| | | |
| | | -- ç´¢å¼å¯éï¼å¦éætokenæ¥è¯¢ææ¸
çï¼ |
| | | -- CREATE INDEX `idx_wechat_token_expire` ON `sys_user` (`wechat_token_expires_at`); |
| New file |
| | |
| | | -- ======================================== |
| | | -- ä¿®å¤GPSåæ®µéç¨çä»»å¡å
³è |
| | | -- ç¨éï¼ä¸ºå·²æçåæ®µéç¨æ°æ®è¡¥å
task_id å task_code |
| | | -- ä¼å说æï¼è¡¥å
task_idåï¼ç»è®¡è®¡ç®å°ä½¿ç¨ä¼åæ¹æ¡ï¼ç´æ¥SQLèåï¼æ´å¿«ï¼ |
| | | -- ======================================== |
| | | |
| | | -- 1. æ¥çå½åæªå
³èä»»å¡çåæ®µæ°é |
| | | SELECT COUNT(*) as 'æªå
³èä»»å¡çåæ®µæ°' |
| | | FROM tb_vehicle_gps_segment_mileage |
| | | WHERE task_id IS NULL; |
| | | |
| | | -- 2. æ¥çæå¤å°ä»»å¡å¯ä»¥è¢«å
³è |
| | | SELECT COUNT(DISTINCT t.task_id) as 'å¯å
³èç任塿°' |
| | | FROM sys_task t |
| | | INNER JOIN sys_task_vehicle tv ON t.task_id = tv.task_id |
| | | WHERE t.del_flag = '0' |
| | | AND t.task_status NOT IN ('PENDING', 'CANCELLED'); |
| | | |
| | | -- 3. æ´æ°é»è¾ï¼æ ¹æ®è½¦è¾IDåæ¶é´éå å
³èä»»å¡ |
| | | -- 注æï¼è¿ä¸ªæ¥è¯¢ä¼æ¯è¾æ
¢ï¼å»ºè®®åæ¹æ§è¡æå¨éé«å³°ææ§è¡ |
| | | |
| | | UPDATE tb_vehicle_gps_segment_mileage seg |
| | | INNER JOIN ( |
| | | SELECT |
| | | seg2.segment_id, |
| | | t.task_id, |
| | | t.task_code |
| | | FROM tb_vehicle_gps_segment_mileage seg2 |
| | | INNER JOIN sys_task_vehicle tv ON seg2.vehicle_id = tv.vehicle_id |
| | | INNER JOIN sys_task t ON tv.task_id = t.task_id |
| | | WHERE seg2.task_id IS NULL |
| | | AND t.del_flag = '0' |
| | | -- AND t.task_status NOT IN ('PENDING', 'CANCELLED') |
| | | -- æ¶é´éå æ¡ä»¶ï¼å段å¼å§æ¶é´ < ä»»å¡ç»ææ¶é´ AND åæ®µç»ææ¶é´ > ä»»å¡å¼å§æ¶é´ |
| | | AND seg2.segment_start_time < COALESCE(t.actual_end_time, t.planned_end_time, DATE_ADD(t.create_time, INTERVAL 24 HOUR)) |
| | | AND seg2.segment_end_time > COALESCE(t.actual_start_time, t.planned_start_time, t.create_time) |
| | | GROUP BY seg2.segment_id, t.task_id, t.task_code |
| | | ) task_match ON seg.segment_id = task_match.segment_id |
| | | SET |
| | | seg.task_id = task_match.task_id, |
| | | seg.task_code = task_match.task_code; |
| | | |
| | | -- 4. æ¥çä¿®å¤ç»æ |
| | | SELECT |
| | | 'å·²å
³èä»»å¡' as 'ç±»å', |
| | | COUNT(*) as 'æ°é', |
| | | CONCAT(ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM tb_vehicle_gps_segment_mileage), 2), '%') as 'å æ¯' |
| | | FROM tb_vehicle_gps_segment_mileage |
| | | WHERE task_id IS NOT NULL |
| | | UNION ALL |
| | | SELECT |
| | | 'æªå
³èä»»å¡' as 'ç±»å', |
| | | COUNT(*) as 'æ°é', |
| | | CONCAT(ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM tb_vehicle_gps_segment_mileage), 2), '%') as 'å æ¯' |
| | | FROM tb_vehicle_gps_segment_mileage |
| | | WHERE task_id IS NULL; |
| | | |
| | | -- 5. æ¥çæ¯ä¸ªè½¦è¾çå
³èæ
åµ |
| | | SELECT |
| | | seg.vehicle_id, |
| | | seg.vehicle_no, |
| | | COUNT(*) as 'æ»å段æ°', |
| | | SUM(CASE WHEN seg.task_id IS NOT NULL THEN 1 ELSE 0 END) as 'å·²å
³èåæ®µæ°', |
| | | CONCAT(ROUND(SUM(CASE WHEN seg.task_id IS NOT NULL THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2), '%') as 'å
³èç' |
| | | FROM tb_vehicle_gps_segment_mileage seg |
| | | GROUP BY seg.vehicle_id, seg.vehicle_no |
| | | ORDER BY COUNT(*) DESC |
| | | LIMIT 20; |
| | | |
| | | -- 6. åææªè½å
³èçåå |
| | | -- æ¥çæä¸ªæªå
³èåæ®µç详ç»ä¿¡æ¯ |
| | | SELECT |
| | | seg.segment_id, |
| | | seg.vehicle_id, |
| | | seg.vehicle_no, |
| | | seg.segment_start_time, |
| | | seg.segment_end_time, |
| | | 'è¯¥æ¶æ®µè¯¥è½¦è¾çä»»å¡' as '说æ', |
| | | t.task_id, |
| | | t.task_code, |
| | | t.task_status, |
| | | t.create_time, |
| | | COALESCE(t.actual_start_time, t.planned_start_time) as 'ä»»å¡å¼å§æ¶é´', |
| | | COALESCE(t.actual_end_time, t.planned_end_time) as 'ä»»å¡ç»ææ¶é´' |
| | | FROM tb_vehicle_gps_segment_mileage seg |
| | | LEFT JOIN sys_task_vehicle tv ON seg.vehicle_id = tv.vehicle_id |
| | | LEFT JOIN sys_task t ON tv.task_id = t.task_id |
| | | AND t.del_flag = '0' |
| | | AND seg.segment_start_time < COALESCE(t.actual_end_time, t.planned_end_time, DATE_ADD(t.create_time, INTERVAL 24 HOUR)) |
| | | AND seg.segment_end_time > COALESCE(t.actual_start_time, t.planned_start_time, t.create_time) |
| | | WHERE seg.task_id IS NULL |
| | | ORDER BY seg.segment_start_time DESC |
| | | LIMIT 10; |
| | | |
| | | -- ======================================== |
| | | -- 使ç¨è¯´æï¼ |
| | | -- 1. å
æ§è¡æ¥è¯¢è¯å¥ï¼1ã2ï¼æ¥çæ°æ®æ
åµ |
| | | -- 2. å¨éé«å³°ææ§è¡æ´æ°è¯å¥ï¼3ï¼ |
| | | -- 3. æ§è¡ç»ææ¥è¯¢ï¼4ã5ã6ï¼éªè¯ä¿®å¤ææ |
| | | -- 4. å¦ææ°æ®é大ï¼å»ºè®®æ·»å LIMIT åæ¹æ§è¡ |
| | | -- ======================================== |
| | | |
| | | -- ======================================== |
| | | -- é¢é²æªæ½ï¼ç¡®ä¿å®æ¶ä»»å¡æ£å¸¸è¿è¡ |
| | | -- ======================================== |
| | | |
| | | -- 7. æ£æ¥GPSåæ®µéç¨è®¡ç®å®æ¶ä»»å¡ç¶æ |
| | | SELECT |
| | | job_id, |
| | | job_name, |
| | | job_group, |
| | | invoke_target, |
| | | cron_expression, |
| | | status as 'ç¶æ(0=æ£å¸¸,1=æå)', |
| | | create_time, |
| | | update_time |
| | | FROM sys_job |
| | | WHERE job_name LIKE '%GPS%' OR job_name LIKE '%éç¨%' |
| | | ORDER BY create_time DESC; |
| | | |
| | | -- 8. 妿宿¶ä»»å¡æ¯æåç¶æï¼å¯å¨å® |
| | | -- UPDATE sys_job SET status = '0' WHERE job_name = 'GPSåæ®µéç¨å®æ¶è®¡ç®'; |
| | | |
| | | -- 9. æ£æ¥æè¿çGPS计ç®è®°å½ |
| | | SELECT |
| | | vehicle_id, |
| | | vehicle_no, |
| | | MAX(segment_end_time) as 'æåè®¡ç®æ¶é´', |
| | | COUNT(*) as 'åæ®µæ°', |
| | | SUM(CASE WHEN task_id IS NOT NULL THEN 1 ELSE 0 END) as 'æä»»å¡å
³èçåæ®µæ°' |
| | | FROM tb_vehicle_gps_segment_mileage |
| | | WHERE segment_end_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) |
| | | GROUP BY vehicle_id, vehicle_no |
| | | ORDER BY MAX(segment_end_time) DESC; |
| New file |
| | |
| | | -- ç§»é¤ç¨æ·è¡¨ä¸ç微信token䏿ææåæ®µï¼æ¹ä¸ºåºç¨çº§ç»ä¸åå¨ï¼ |
| | | -- åæ»ææ¸
çèæ¬ï¼å é¤ sys_user.wechat_access_token / sys_user.wechat_token_expires_at |
| | | |
| | | ALTER TABLE `sys_user` |
| | | DROP COLUMN `wechat_access_token`; |
| | | |
| | | ALTER TABLE `sys_user` |
| | | DROP COLUMN `wechat_token_expires_at`; |
| New file |
| | |
| | | -- ======================================== |
| | | -- æµè¯éç¨ç»è®¡ä¼åææ |
| | | -- ç¨éï¼å¯¹æ¯ä¼åååçæ§è½ååç¡®æ§ |
| | | -- ======================================== |
| | | |
| | | -- 1. æ¥çæä¸ªè½¦è¾æå¤©çåæ®µæ°æ®task_idè¦çç |
| | | -- æ¿æ¢åæ°ï¼@vehicleId, @statDate |
| | | SET @vehicleId = 1; -- æ¿æ¢ä¸ºå®é
车è¾ID |
| | | SET @statDate = '2025-12-04'; -- æ¿æ¢ä¸ºå®é
æ¥æ |
| | | |
| | | SELECT |
| | | 'åæ®µç»è®¡' as 'ç±»å', |
| | | COUNT(*) as 'æ»å段æ°', |
| | | SUM(CASE WHEN task_id IS NOT NULL THEN 1 ELSE 0 END) as 'æä»»å¡IDçåæ®µæ°', |
| | | CONCAT(ROUND(SUM(CASE WHEN task_id IS NOT NULL THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2), '%') as 'task_idè¦çç' |
| | | FROM tb_vehicle_gps_segment_mileage |
| | | WHERE vehicle_id = @vehicleId |
| | | AND DATE(segment_start_time) = @statDate; |
| | | |
| | | -- 2. 对æ¯ä¸¤ç§è®¡ç®æ¹å¼çç»æ |
| | | -- æ¹å¼Aï¼ä¼åæ¹æ¡ï¼ç´æ¥ætask_idèåï¼ |
| | | SELECT |
| | | 'ä¼åæ¹æ¡' as 'è®¡ç®æ¹å¼', |
| | | SUM(segment_distance) as 'æ»éç¨', |
| | | SUM(CASE WHEN task_id IS NOT NULL THEN segment_distance ELSE 0 END) as 'ä»»å¡éç¨', |
| | | SUM(CASE WHEN task_id IS NULL THEN segment_distance ELSE 0 END) as 'éä»»å¡éç¨', |
| | | CONCAT(ROUND(SUM(CASE WHEN task_id IS NOT NULL THEN segment_distance ELSE 0 END) * 100.0 / |
| | | NULLIF(SUM(segment_distance), 0), 2), '%') as 'ä»»å¡å æ¯' |
| | | FROM tb_vehicle_gps_segment_mileage |
| | | WHERE vehicle_id = @vehicleId |
| | | AND DATE(segment_start_time) = @statDate; |
| | | |
| | | -- æ¹å¼Bï¼å½åç»è®¡è¡¨ä¸çæ°æ®ï¼å¯è½ä½¿ç¨äºæ¶é´éå 计ç®ï¼ |
| | | SELECT |
| | | 'ç»è®¡è¡¨æ°æ®' as 'è®¡ç®æ¹å¼', |
| | | total_mileage as 'æ»éç¨', |
| | | task_mileage as 'ä»»å¡éç¨', |
| | | non_task_mileage as 'éä»»å¡éç¨', |
| | | CONCAT(ROUND(task_ratio * 100, 2), '%') as 'ä»»å¡å æ¯' |
| | | FROM tb_vehicle_mileage_stats |
| | | WHERE vehicle_id = @vehicleId |
| | | AND DATE(stat_date) = @statDate; |
| | | |
| | | -- 3. æ¥ç使ç¨ä¼åæ¹æ¡çè½¦è¾æ°éï¼task_idè¦çç > 80%ï¼ |
| | | SELECT |
| | | DATE(segment_start_time) as 'æ¥æ', |
| | | vehicle_id, |
| | | vehicle_no, |
| | | COUNT(*) as 'æ»å段æ°', |
| | | SUM(CASE WHEN task_id IS NOT NULL THEN 1 ELSE 0 END) as 'ætask_idåæ®µæ°', |
| | | CONCAT(ROUND(SUM(CASE WHEN task_id IS NOT NULL THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2), '%') as 'è¦çç', |
| | | CASE |
| | | WHEN SUM(CASE WHEN task_id IS NOT NULL THEN 1 ELSE 0 END) > COUNT(*) * 0.8 |
| | | THEN 'â 使ç¨ä¼åæ¹æ¡' |
| | | ELSE 'à 使ç¨éçº§æ¹æ¡' |
| | | END as 'æ¹æ¡éæ©' |
| | | FROM tb_vehicle_gps_segment_mileage |
| | | WHERE segment_start_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) |
| | | GROUP BY DATE(segment_start_time), vehicle_id, vehicle_no |
| | | HAVING COUNT(*) > 10 -- åªç»è®¡å段æ°å¤§äº10ç |
| | | ORDER BY DATE(segment_start_time) DESC, vehicle_id |
| | | LIMIT 20; |
| | | |
| | | -- 4. ç»è®¡æ´ä½ä¼åææ |
| | | SELECT |
| | | 'æ´ä½ç»è®¡' as 'ç»è®¡ç±»å', |
| | | COUNT(DISTINCT CONCAT(vehicle_id, '_', DATE(segment_start_time))) as '车è¾-æ¥æç»åæ»æ°', |
| | | SUM(CASE WHEN task_coverage > 0.8 THEN 1 ELSE 0 END) as 'å¯ä½¿ç¨ä¼åæ¹æ¡çæ°é', |
| | | CONCAT(ROUND(SUM(CASE WHEN task_coverage > 0.8 THEN 1 ELSE 0 END) * 100.0 / |
| | | COUNT(DISTINCT CONCAT(vehicle_id, '_', DATE(segment_start_time))), 2), '%') as 'ä¼åè¦çç' |
| | | FROM ( |
| | | SELECT |
| | | vehicle_id, |
| | | DATE(segment_start_time) as stat_date, |
| | | SUM(CASE WHEN task_id IS NOT NULL THEN 1 ELSE 0 END) * 1.0 / COUNT(*) as task_coverage |
| | | FROM tb_vehicle_gps_segment_mileage |
| | | WHERE segment_start_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) |
| | | GROUP BY vehicle_id, DATE(segment_start_time) |
| | | HAVING COUNT(*) > 10 |
| | | ) coverage_stats; |
| | | |
| | | -- 5. æ§è½æµè¯ï¼æ§è¡æ¶é´å¯¹æ¯ |
| | | -- 说æï¼ä½¿ç¨EXPLAIN ANALYZEæ¥çæ§è¡è®¡åï¼MySQL 8.0+ï¼æä½¿ç¨BENCHMARK彿° |
| | | |
| | | -- ä¼åæ¹æ¡æ¥è¯¢ï¼ç´æ¥èåï¼ |
| | | EXPLAIN |
| | | SELECT |
| | | vehicle_id, |
| | | SUM(CASE WHEN task_id IS NOT NULL THEN segment_distance ELSE 0 END) as task_mileage, |
| | | SUM(CASE WHEN task_id IS NULL THEN segment_distance ELSE 0 END) as non_task_mileage |
| | | FROM tb_vehicle_gps_segment_mileage |
| | | WHERE vehicle_id = @vehicleId |
| | | AND DATE(segment_start_time) = @statDate |
| | | GROUP BY vehicle_id; |
| | | |
| | | -- 6. 建议çä¼åæªæ½ |
| | | SELECT |
| | | 'ä¼å建议' as 'ç±»å', |
| | | 'è¡¥å
å岿°æ®çtask_id' as 'æªæ½1', |
| | | 'ç¡®ä¿å®æ¶ä»»å¡æ£å¸¸è¿è¡' as 'æªæ½2', |
| | | 'ç¡®ä¿ä»»å¡ç¶ææ£ç¡®æ´æ°' as 'æªæ½3', |
| | | 'å®ææ£æ¥task_idè¦çç' as 'æªæ½4'; |
| | | |
| | | -- ======================================== |
| | | -- 使ç¨è¯´æï¼ |
| | | -- 1. è®¾ç½®åæ°ï¼ä¿®æ¹ç¬¬7-8è¡ç@vehicleIdå@statDate |
| | | -- 2. æ§è¡æ¥è¯¢1-4æ¥çä¼åææ |
| | | -- 3. 妿è¦ççä½äº80%ï¼æ§è¡fix_segment_mileage_task_association.sqlä¿®å¤ |
| | | -- 4. éæ°ç»è®¡ï¼è§å¯æ§è½æå |
| | | -- ======================================== |
| New file |
| | |
| | | -- 微信订é
æ¶æ¯åéå¼å
³é
ç½® |
| | | -- ç¨äºæ§å¶ç³»ç»æ¯å¦å¯ç¨å¾®ä¿¡è®¢é
æ¶æ¯æ¨éåè½ |
| | | |
| | | -- æå
¥è®¢é
æ¶æ¯å¼å
³é
ç½®ï¼é»è®¤å¼å¯ï¼ |
| | | INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark) |
| | | VALUES |
| | | ('微信订é
æ¶æ¯å¼å
³', 'wechat.subscribe.message.enabled', 'true', 'N', 'admin', NOW(), 'admin', NOW(), |
| | | 'æ§å¶æ¯å¦å¯ç¨å¾®ä¿¡è®¢é
æ¶æ¯æ¨éåè½ãtrue=å¯ç¨ï¼false=ç¦ç¨ãå
³éåç³»ç»å°ä¸ååéä»»ä½è®¢é
æ¶æ¯ã'); |
| | | |
| | | -- 说æï¼ |
| | | -- 1. wechat.subscribe.message.enabled æ§å¶å
¨å±è®¢é
æ¶æ¯åéå¼å
³ |
| | | -- 2. 设置为 true æ¶ï¼ç³»ç»æ£å¸¸åé订é
æ¶æ¯ |
| | | -- 3. 设置为 false æ¶ï¼ææè®¢é
æ¶æ¯åéå°è¢«è·³è¿ |
| | | -- 4. éç¨åºæ¯ï¼ |
| | | -- - ç产ç¯å¢ä¸´æ¶å
³éæ¶æ¯æ¨é |
| | | -- - æµè¯ç¯å¢é¿å
è¯¯åæ¶æ¯ |
| | | -- - ç³»ç»ç»´æ¤æé´æåéç¥ |
| | | -- 5. å¯å¨ç³»ç»ç®¡ç->åæ°è®¾ç½®ä¸å¨æä¿®æ¹ï¼æ ééå¯æå¡ |
| New file |
| | |
| | | # 微信订é
æ¶æ¯å¼å
³ä½¿ç¨è¯´æ |
| | | |
| | | ## ä¸ãé
置项说æ |
| | | |
| | | ### é
ç½®é®ï¼`wechat.subscribe.message.enabled` |
| | | |
| | | | 屿§ | å¼ | |
| | | |------|-----| |
| | | | é
ç½®åç§° | 微信订é
æ¶æ¯å¼å
³ | |
| | | | é
ç½®é® | wechat.subscribe.message.enabled | |
| | | | é
置类å | N (æ®éåæ°) | |
| | | | é»è®¤å¼ | true | |
| | | | å¯éå¼ | true / false | |
| | | |
| | | ### é
置说æ |
| | | |
| | | - **true**: å¯ç¨å¾®ä¿¡è®¢é
æ¶æ¯æ¨éï¼é»è®¤ï¼ |
| | | - **false**: ç¦ç¨å¾®ä¿¡è®¢é
æ¶æ¯æ¨é |
| | | |
| | | ## äºãSQLèæ¬ |
| | | |
| | | æ§è¡ä»¥ä¸SQLèæ¬æ·»å é
ç½®ï¼ |
| | | |
| | | ```sql |
| | | INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark) |
| | | VALUES |
| | | ('微信订é
æ¶æ¯å¼å
³', 'wechat.subscribe.message.enabled', 'true', 'N', 'admin', NOW(), 'admin', NOW(), |
| | | 'æ§å¶æ¯å¦å¯ç¨å¾®ä¿¡è®¢é
æ¶æ¯æ¨éåè½ãtrue=å¯ç¨ï¼false=ç¦ç¨ãå
³éåç³»ç»å°ä¸ååéä»»ä½è®¢é
æ¶æ¯ã'); |
| | | ``` |
| | | |
| | | ## ä¸ãåè½è¯´æ |
| | | |
| | | ### 1. å¼å
³æ§å¶èå´ |
| | | |
| | | 该å¼å
³æ§å¶ä»¥ä¸åºæ¯ç订é
æ¶æ¯åéï¼ |
| | | |
| | | - â
æ°ä»»å¡å建æ¶éç¥æ§è¡äºº |
| | | - â
任塿´æ°æ¶éç¥ç¸å
³äººå |
| | | - â
æ§ç³»ç»åæ¥ä»»å¡æ¶çéç¥ |
| | | - â
æå¨è§¦åçä»»å¡éç¥ |
| | | - â
ææéè¿ `IWechatTaskNotifyService` åéçæ¶æ¯ |
| | | |
| | | ### 2. å¼å
³å
³éåçè¡¨ç° |
| | | |
| | | å½å¼å
³è®¾ç½®ä¸º `false` æ¶ï¼ |
| | | - ç³»ç»å°è·³è¿ææè®¢é
æ¶æ¯åé |
| | | - æ¥å¿è®°å½ï¼`订é
æ¶æ¯åéå·²å
³éï¼è·³è¿åéï¼taskId=xxx` |
| | | - ä¸å½±åå
¶ä»ä¸å¡é»è¾ |
| | | - è¿åå¼ä¸º 0ï¼åéæåæ°é为0ï¼ |
| | | |
| | | ### 3. å¼å¸¸å¤ç |
| | | |
| | | - 妿é
置读å失败ï¼é»è®¤ä¸º **å¯ç¨** ç¶æ |
| | | - ç¡®ä¿ç³»ç»æ£å¸¸è¿è¡ï¼é¿å
å é
ç½®å¼å¸¸å¯¼è´åè½ä¸æ |
| | | |
| | | ## åã使ç¨åºæ¯ |
| | | |
| | | ### 1. æµè¯ç¯å¢ |
| | | |
| | | 卿µè¯ç¯å¢ä¸å
³éæ¶æ¯æ¨éï¼é¿å
æµè¯æ°æ®è§¦åç宿¶æ¯ï¼ |
| | | |
| | | ```sql |
| | | UPDATE sys_config |
| | | SET config_value = 'false' |
| | | WHERE config_key = 'wechat.subscribe.message.enabled'; |
| | | ``` |
| | | |
| | | ### 2. çäº§ç»´æ¤ |
| | | |
| | | å¨ç³»ç»ç»´æ¤æé´ä¸´æ¶å
³éæ¶æ¯æ¨éï¼ |
| | | |
| | | ```sql |
| | | -- å
³éæ¶æ¯æ¨é |
| | | UPDATE sys_config |
| | | SET config_value = 'false' |
| | | WHERE config_key = 'wechat.subscribe.message.enabled'; |
| | | |
| | | -- ç»´æ¤å®æåæ¢å¤ |
| | | UPDATE sys_config |
| | | SET config_value = 'true' |
| | | WHERE config_key = 'wechat.subscribe.message.enabled'; |
| | | ``` |
| | | |
| | | ### 3. æ
éææ¥ |
| | | |
| | | å½å¾®ä¿¡æ¥å£å¼å¸¸æ¶ï¼ä¸´æ¶å
³éæ¶æ¯æ¨éï¼é¿å
大éé误æ¥å¿ï¼ |
| | | |
| | | ```sql |
| | | UPDATE sys_config |
| | | SET config_value = 'false' |
| | | WHERE config_key = 'wechat.subscribe.message.enabled'; |
| | | ``` |
| | | |
| | | ## äºã管ççé¢æä½ |
| | | |
| | | ### éè¿åå°ç®¡ççé¢ä¿®æ¹ |
| | | |
| | | 1. ç»å½ç³»ç»ç®¡çåå° |
| | | 2. è¿å
¥ **ç³»ç»ç®¡ç > åæ°è®¾ç½®** |
| | | 3. æç´¢ **微信订é
æ¶æ¯å¼å
³** |
| | | 4. ä¿®æ¹åæ°å¼ä¸º `true` æ `false` |
| | | 5. ä¿åå **ç«å³çæ**ï¼æ ééå¯æå¡ |
| | | |
| | | ## å
ãææ¯å®ç° |
| | | |
| | | ### 代ç ä½ç½® |
| | | |
| | | - **é
ç½®SQL**: `/sql/wechat_subscribe_message_config.sql` |
| | | - **æå¡å®ç°**: `/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatTaskNotifyServiceImpl.java` |
| | | |
| | | ### æ ¸å¿ä»£ç |
| | | |
| | | ```java |
| | | /** |
| | | * æ£æ¥æ¯å¦å¯ç¨è®¢é
æ¶æ¯åé |
| | | */ |
| | | private boolean isSubscribeMessageEnabled() { |
| | | try { |
| | | String enabled = configService.selectConfigByKey("wechat.subscribe.message.enabled"); |
| | | return "true".equalsIgnoreCase(enabled); |
| | | } catch (Exception e) { |
| | | log.warn("è·å订é
æ¶æ¯å¼å
³é
置失败ï¼é»è®¤å¯ç¨", e); |
| | | return true; // é»è®¤å¯ç¨ |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ## ä¸ã注æäºé¡¹ |
| | | |
| | | 1. â ï¸ ä¿®æ¹é
ç½®åç«å³çæï¼æ ééå¯æå¡ |
| | | 2. â ï¸ å
³éå¼å
³ä¼å½±åææè®¢é
æ¶æ¯åé |
| | | 3. â ï¸ é»è®¤å¼ä¸º `true`ï¼å¯ç¨ï¼ï¼ç¡®ä¿æ£å¸¸ä¸å¡ä¸åå½±å |
| | | 4. â ï¸ å»ºè®®å¨ç产ç¯å¢ä¿æå¼å¯ç¶æ |
| | | 5. â ï¸ æµè¯ç¯å¢å»ºè®®å
³éï¼é¿å
è¯¯åæ¶æ¯ç»çå®ç¨æ· |
| | | |
| | | ## å
«ãéªè¯æ¹æ³ |
| | | |
| | | ### 1. éªè¯é
置已添å |
| | | |
| | | ```sql |
| | | SELECT * FROM sys_config WHERE config_key = 'wechat.subscribe.message.enabled'; |
| | | ``` |
| | | |
| | | ### 2. éªè¯å¼å
³çæ |
| | | |
| | | **å
³éå¼å
³ï¼** |
| | | ```sql |
| | | UPDATE sys_config SET config_value = 'false' WHERE config_key = 'wechat.subscribe.message.enabled'; |
| | | ``` |
| | | |
| | | **å建æµè¯ä»»å¡ï¼** |
| | | - è§å¯æ¥å¿æ¯å¦è¾åºï¼`订é
æ¶æ¯åéå·²å
³éï¼è·³è¿åé` |
| | | - 确认没æå®é
åéå¾®ä¿¡æ¶æ¯ |
| | | |
| | | **æ¢å¤å¼å
³ï¼** |
| | | ```sql |
| | | UPDATE sys_config SET config_value = 'true' WHERE config_key = 'wechat.subscribe.message.enabled'; |
| | | ``` |
| | | |
| | | ## ä¹ãç¸å
³é
ç½® |
| | | |
| | | 该åè½é
å以ä¸é
置使ç¨ï¼ |
| | | |
| | | - `weixin.access_token.{appId}` - 微信AccessTokenç¼å |
| | | - `weixin.access_token_expires.{appId}` - AccessTokenè¿ææ¶é´ |
| | | - `wechat.task.notify.template.id` - ä»»å¡éç¥æ¨¡æ¿ID (application.yml) |
| | | - `wechat.task.detail.page` - ä»»å¡è¯¦æ
é¡µè·¯å¾ (application.yml) |