From fa5ea853099e88be253fca4fb2b0c2b7af5f396e Mon Sep 17 00:00:00 2001
From: wlzboy <66905212@qq.com>
Date: 星期日, 09 十一月 2025 15:57:04 +0800
Subject: [PATCH] feat:微信登录

---
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/HospDataController.java            |   20 
 ruoyi-framework/src/main/java/com/ruoyi/framework/security/WechatAuthenticationToken.java    |   86 ++++
 ruoyi-system/src/main/java/com/ruoyi/system/service/IWechatLoginService.java                 |   36 +
 ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml                              |    9 
 app/api/login.js                                                                             |   44 ++
 sql/wechat_login_fields.sql                                                                  |   22 +
 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/WechatLoginService.java        |  175 ++++++++
 app/pages/task/create-emergency.vue                                                          |  105 ++++
 ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java    |   12 
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java             |   12 
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java            |   87 ++++
 app/pages/login.vue                                                                          |  169 +++++++
 ruoyi-admin/src/main/resources/application-prod.yml                                          |   11 
 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java                     |    8 
 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java                  |   42 ++
 ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java                 |   13 
 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java                        |    8 
 ruoyi-framework/src/main/java/com/ruoyi/framework/security/WechatAuthenticationProvider.java |   85 ++++
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatLoginServiceImpl.java         |  291 +++++++++++++
 ruoyi-admin/src/main/resources/application-dev.yml                                           |   11 
 20 files changed, 1,213 insertions(+), 33 deletions(-)

diff --git a/app/api/login.js b/app/api/login.js
index 6ce7e7c..baa7af2 100644
--- a/app/api/login.js
+++ b/app/api/login.js
@@ -57,3 +57,47 @@
     timeout: 20000
   })
 }
+
+// 寰俊涓�閿櫥褰� - 閫氳繃OpenID鍜孶nionID鐧诲綍
+export function loginByOpenId(openId, unionId) {
+  const data = { openId }
+  // 濡傛灉unionId瀛樺湪,涓�骞朵紶鍏ヨ繘琛屽弻閲嶉獙璇�
+  if (unionId) {
+    data.unionId = unionId
+  }
+  return request({
+    url: '/wechat/login/openid',
+    headers: {
+      isToken: false
+    },
+    method: 'post',
+    data: data
+  })
+}
+
+// 寰俊涓�閿櫥褰� - 缁戝畾鎵嬫満鍙�
+export function bindWechatAndLogin(data) {
+  return request({
+    url: '/wechat/login/bind',
+    headers: {
+      isToken: false
+    },
+    method: 'post',
+    data: data
+  })
+}
+
+// 寰俊鎵嬫満鍙风櫥褰�(鎺ㄨ崘浣跨敤)
+export function loginByWechatPhone(loginCode, phoneCode) {
+  return request({
+    url: '/wechat/login/phone',
+    headers: {
+      isToken: false
+    },
+    method: 'post',
+    data: {
+      loginCode: loginCode,
+      phoneCode: phoneCode
+    }
+  })
+}
diff --git a/app/pages/login.vue b/app/pages/login.vue
index 94efe80..3f67b74 100644
--- a/app/pages/login.vue
+++ b/app/pages/login.vue
@@ -37,13 +37,33 @@
       
       <view class="action-btn">
         <button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">鐧诲綍</button>
+        <!-- 寰俊涓�閿櫥褰曟寜閽�(浠呭湪寰俊灏忕▼搴忕幆澧冩樉绀�) -->
+        <!-- #ifdef MP-WEIXIN -->
+        <button 
+          v-if="isWechat && wechatOpenId" 
+          @click="loginByOpenId" 
+          class="wechat-login-btn cu-btn block bg-green lg round"
+          style="margin-top: 20rpx;">
+          <text class="cuIcon-wechat" style="margin-right: 10rpx;"></text>
+          寰俊涓�閿櫥褰�
+        </button>
+        <button 
+          v-else-if="isWechat"
+          open-type="getPhoneNumber" 
+          @getphonenumber="onGetPhoneNumber"
+          class="wechat-login-btn cu-btn block bg-green lg round"
+          style="margin-top: 20rpx;">
+          <text class="cuIcon-wechat" style="margin-right: 10rpx;"></text>
+          寰俊涓�閿櫥褰�
+        </button>
+        <!-- #endif -->
       </view>
     </view>
   </scroll-view>
 </template>
 
 <script>
-  import { getCodeImg } from '@/api/login'
+  import { getCodeImg, loginByOpenId, loginByWechatPhone } from '@/api/login'
 
   export default {
     data() {
@@ -60,11 +80,17 @@
           password: "",
           code: "",
           uuid: ''
-        }
+        },
+        // 寰俊涓�閿櫥褰曠浉鍏�
+        isWechat: false, // 鏄惁涓哄井淇″皬绋嬪簭鐜
+        wechatOpenId: '', // 寰俊OpenID
+        wechatUnionId: '', // 寰俊UnionID
       }
     },
     created() {
       this.getCode()
+      this.checkWechatEnv()
+      this.tryAutoLogin()
     },
     methods: {
       // 鐢ㄦ埛娉ㄥ唽
@@ -128,6 +154,137 @@
           // 瑙﹀彂鐧诲綍鎴愬姛浜嬩欢锛屽惎鍔ㄦ秷鎭疆璇�
           uni.$emit('user-login')
           this.$tab.reLaunch('/pages/index')
+        })
+      },
+      
+      // ==================== 寰俊涓�閿櫥褰曠浉鍏虫柟娉� ====================
+      
+      // 妫�鏌ユ槸鍚﹀湪寰俊灏忕▼搴忕幆澧�
+      checkWechatEnv() {
+        // #ifdef MP-WEIXIN
+        this.isWechat = true
+        console.log('褰撳墠鐜:寰俊灏忕▼搴�')
+        // #endif
+        
+        // #ifndef MP-WEIXIN
+        this.isWechat = false
+        console.log('褰撳墠鐜:闈炲井淇″皬绋嬪簭')
+        // #endif
+      },
+      
+      // 灏濊瘯鑷姩鐧诲綍(妫�鏌ユ湰鍦版槸鍚︽湁淇濆瓨鐨凮penID)
+      tryAutoLogin() {
+        if (!this.isWechat) {
+          return
+        }
+        
+        // 浠庢湰鍦板瓨鍌ㄤ腑鑾峰彇OpenID鍜孶nionID
+        const savedOpenId = uni.getStorageSync('wechat_openid')
+        const savedUnionId = uni.getStorageSync('wechat_unionid')
+        
+        if (savedOpenId) {
+          console.log('妫�娴嬪埌宸蹭繚瀛樼殑OpenID锛屽皾璇曡嚜鍔ㄧ櫥褰�')
+          this.wechatOpenId = savedOpenId
+          this.wechatUnionId = savedUnionId // 鍙兘涓簄ull
+          this.loginByOpenId()
+        }
+      },
+      
+      // 澶勭悊鑾峰彇鎵嬫満鍙风殑鍥炶皟
+      onGetPhoneNumber(e) {
+        console.log('鑾峰彇鎵嬫満鍙峰洖璋�:', e)
+        
+        if (!this.agreedToPolicy) {
+          this.$modal.msgError("璇峰厛闃呰骞跺悓鎰忕敤鎴峰崗璁拰闅愮鏀跨瓥")
+          return
+        }
+        
+        if (e.detail.errMsg === 'getPhoneNumber:ok') {
+          // 鐢ㄦ埛鍚屾剰鎺堟潈
+          const { code } = e.detail
+          
+          this.$modal.loading("姝e湪鑾峰彇鎵嬫満鍙�...")
+          
+          // 鍏堣幏鍙栧井淇$櫥褰昪ode
+          uni.login({
+            provider: 'weixin',
+            success: (loginRes) => {
+              console.log('寰俊鐧诲綍code:', loginRes.code)
+              console.log('鎵嬫満鍙穋ode:', code)
+              
+              // 璋冪敤鍚庣鎺ュ彛,浼犲叆loginCode鍜宲honeCode
+              loginByWechatPhone(loginRes.code, code).then(response => {
+                this.$modal.closeLoading()
+                
+                if (response.code === 200) {
+                  // 鐧诲綍鎴愬姛,淇濆瓨OpenID鍜孶nionID
+                  const openId = response.openId
+                  const unionId = response.unionId
+                  
+                  uni.setStorageSync('wechat_openid', openId)
+                  this.wechatOpenId = openId
+                  
+                  if (unionId) {
+                    uni.setStorageSync('wechat_unionid', unionId)
+                    this.wechatUnionId = unionId
+                  }
+                  
+                  // 淇濆瓨token鍒版湰鍦板瓨鍌ㄥ拰Vuex
+                  const token = response.token
+                  this.$store.commit('SET_TOKEN', token)
+                  // 蹇呴』璋冪敤setToken淇濆瓨鍒版湰鍦板瓨鍌�
+                  const { setToken } = require('@/utils/auth')
+                  setToken(token)
+                  
+                  this.loginSuccess()
+                } else {
+                  this.$modal.msgError(response.msg || '鐧诲綍澶辫触')
+                }
+              }).catch(error => {
+                this.$modal.closeLoading()
+                console.error('鐧诲綍澶辫触:', error)
+                this.$modal.msgError('鐧诲綍澶辫触,璇烽噸璇�')
+              })
+            },
+            fail: (err) => {
+              this.$modal.closeLoading()
+              console.error('鑾峰彇寰俊鐧诲綍code澶辫触:', err)
+              this.$modal.msgError('鑾峰彇寰俊淇℃伅澶辫触')
+            }
+          })
+        } else {
+          // 鐢ㄦ埛鎷掔粷鎺堟潈
+          this.$modal.msgError('闇�瑕佹偍鐨勬墜鏈哄彿鎵嶈兘鐧诲綍')
+        }
+      },
+      
+      // 閫氳繃OpenID鍜孶nionID鐧诲綍
+      loginByOpenId() {
+        this.$modal.loading("姝e湪鐧诲綍...")
+        
+        // 鍚屾椂浼犲叆openId鍜寀nionId杩涜鍙岄噸楠岃瘉
+        loginByOpenId(this.wechatOpenId, this.wechatUnionId).then(response => {
+          this.$modal.closeLoading()
+          
+          if (response.code === 200) {
+            // 鐧诲綍鎴愬姛,淇濆瓨token鍒版湰鍦板瓨鍌ㄥ拰Vuex
+            const token = response.token
+            this.$store.commit('SET_TOKEN', token)
+            // 蹇呴』璋冪敤setToken淇濆瓨鍒版湰鍦板瓨鍌�
+            const { setToken } = require('@/utils/auth')
+            setToken(token)
+            
+            this.loginSuccess()
+          } else {
+            // OpenID鏈粦瀹氭垨楠岃瘉澶辫触锛岄渶瑕佽幏鍙栨墜鏈哄彿缁戝畾
+            console.log('璇penID灏氭湭缁戝畾鎴栭獙璇佸け璐ワ紝闇�瑕佽幏鍙栨墜鏈哄彿')
+            this.$modal.closeLoading()
+            this.$modal.msgError(response.msg || '璇ュ井淇¤处鍙峰皻鏈粦瀹氾紝璇风偣鍑诲井淇′竴閿櫥褰曟寜閽幏鍙栨墜鏈哄彿鎺堟潈')
+          }
+        }).catch(error => {
+          this.$modal.closeLoading()
+          console.error('鐧诲綍澶辫触:', error)
+          this.$modal.msgError('鐧诲綍澶辫触锛岃閲嶈瘯')
         })
       }
     }
@@ -246,6 +403,14 @@
           height: 90rpx;
           font-size: 32rpx;
         }
+        
+        .wechat-login-btn {
+          height: 90rpx;
+          font-size: 32rpx;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+        }
       }
 
       .reg {
diff --git a/app/pages/task/create-emergency.vue b/app/pages/task/create-emergency.vue
index 7d96be1..4ad6a63 100644
--- a/app/pages/task/create-emergency.vue
+++ b/app/pages/task/create-emergency.vue
@@ -189,7 +189,10 @@
               :key="hospital.hospId"
               @click="selectHospitalOut(hospital)"
             >
-              <view class="hospital-name">{{ hospital.hospName }}</view>
+              <view class="hospital-name">
+                {{ hospital.hospName }}
+                <text class="hospital-short" v-if="hospital.hospShort">{{ hospital.hospShort }}</text>
+              </view>
               <view class="hospital-address">{{ buildFullAddress(hospital) }}</view>
             </view>
           </view>
@@ -198,12 +201,21 @@
       
       <view class="form-item">
         <view class="form-label required">绉戝</view>
-        <picker mode="selector" :range="departmentOptions" range-key="text" @change="onHospitalOutDepartmentChange">
+        <picker 
+          v-if="taskForm.hospitalOut.name !== '瀹朵腑'"
+          mode="selector" 
+          :range="departmentOptions" 
+          range-key="text" 
+          @change="onHospitalOutDepartmentChange"
+        >
           <view class="form-input picker-input">
             {{ taskForm.hospitalOut.department || '璇烽�夋嫨绉戝' }}
             <uni-icons type="arrowright" size="16" color="#999"></uni-icons>
           </view>
         </picker>
+        <view v-else class="form-input picker-input disabled">
+          鍏跺畠
+        </view>
       </view>
       
       <view class="form-item">
@@ -259,7 +271,10 @@
               :key="hospital.hospId"
               @click="selectHospitalIn(hospital)"
             >
-              <view class="hospital-name">{{ hospital.hospName }}</view>
+              <view class="hospital-name">
+                {{ hospital.hospName }}
+                <text class="hospital-short" v-if="hospital.hospShort">{{ hospital.hospShort }}</text>
+              </view>
               <view class="hospital-address">{{ buildFullAddress(hospital) }}</view>
             </view>
           </view>
@@ -268,12 +283,21 @@
       
       <view class="form-item">
         <view class="form-label required">绉戝</view>
-        <picker mode="selector" :range="departmentOptions" range-key="text" @change="onHospitalInDepartmentChange">
+        <picker 
+          v-if="taskForm.hospitalIn.name !== '瀹朵腑'"
+          mode="selector" 
+          :range="departmentOptions" 
+          range-key="text" 
+          @change="onHospitalInDepartmentChange"
+        >
           <view class="form-input picker-input">
             {{ taskForm.hospitalIn.department || '璇烽�夋嫨绉戝' }}
             <uni-icons type="arrowright" size="16" color="#999"></uni-icons>
           </view>
         </picker>
+        <view v-else class="form-input picker-input disabled">
+          鍏跺畠
+        </view>
       </view>
       
       <view class="form-item">
@@ -917,22 +941,30 @@
       })
     },
     
-    // 鎸夊湴鍩熸帓搴忓尰闄細鏈湴鍖哄煙浼樺厛
+    // 鎸夊湴鍩熸帓搴忓尰闄細鏈湴鍖哄煙浼樺厛锛�"瀹朵腑"濮嬬粓鍦ㄦ渶鍓嶉潰
     sortHospitalsByRegion(hospitals) {
-      if (!this.selectedRegion || !hospitals || hospitals.length === 0) {
+      if (!hospitals || hospitals.length === 0) {
         return hospitals
       }
       
       const region = this.selectedRegion
-      const localHospitals = []
-      const otherHospitals = []
+      const homeHospital = []  // "瀹朵腑"
+      const localHospitals = []  // 鏈湴鍖婚櫌
+      const otherHospitals = []  // 鍏朵粬鍖婚櫌
       
       hospitals.forEach(hospital => {
+        // "瀹朵腑"浼樺厛澶勭悊锛屾斁鍦ㄦ渶鍓嶉潰
+        if (hospital.hospName === '瀹朵腑') {
+          homeHospital.push(hospital)
+          return
+        }
+        
         // 鍒ゆ柇鍖婚櫌鏄惁鍦ㄦ湰鍦板尯鍩燂紙鐪併�佸競銆佸尯浠讳竴鍖呭惈鍦板煙鍏抽敭璇嶏級
-        const isLocal = 
+        const isLocal = region && (
           (hospital.hopsProvince && hospital.hopsProvince.includes(region)) ||
           (hospital.hopsCity && hospital.hopsCity.includes(region)) ||
           (hospital.hopsArea && hospital.hopsArea.includes(region))
+        )
         
         if (isLocal) {
           localHospitals.push(hospital)
@@ -941,8 +973,8 @@
         }
       })
       
-      // 鏈湴鍖婚櫌鍦ㄥ墠锛屽叾浠栧尰闄㈠湪鍚�
-      return [...localHospitals, ...otherHospitals]
+      // "瀹朵腑"鍦ㄦ渶鍓嶏紝鏈湴鍖婚櫌鍏舵锛屽叾浠栧尰闄㈠湪鍚�
+      return [...homeHospital, ...localHospitals, ...otherHospitals]
     },
     
     // 杞嚭鍖婚櫌杈撳叆妗嗚幏寰楃劍鐐�
@@ -958,23 +990,28 @@
         // 鍚﹀垯閲嶆柊鍔犺浇甯哥敤鍖婚櫌
         if (this.selectedOrganizationServiceOrderClass) {
           getFrequentOutHospitals(this.selectedOrganizationServiceOrderClass, this.selectedRegion).then(response => {
-            this.hospitalOutResults = response.data || []
+            const hospitals = response.data || []
+            // 纭繚"瀹朵腑"鍦ㄦ渶鍓嶉潰
+            this.hospitalOutResults = this.sortHospitalsByRegion(hospitals)
             // 濡傛灉娌℃湁甯哥敤鍖婚櫌锛岄檷绾т负鏅�氭悳绱�
             if (this.hospitalOutResults.length === 0) {
               searchHospitals('', this.selectedRegion).then(res => {
-                this.hospitalOutResults = res.data || []
+                const hospitals = res.data || []
+                this.hospitalOutResults = this.sortHospitalsByRegion(hospitals)
               })
             }
           }).catch(error => {
             console.error('鍔犺浇甯哥敤杞嚭鍖婚櫌澶辫触:', error)
             searchHospitals('', this.selectedRegion).then(res => {
-              this.hospitalOutResults = res.data || []
+              const hospitals = res.data || []
+              this.hospitalOutResults = this.sortHospitalsByRegion(hospitals)
             })
           })
         } else {
           // 娌℃湁鏈嶅姟鍗曠紪鐮侊紝浣跨敤鏅�氭悳绱�
           searchHospitals('', this.selectedRegion).then(response => {
-            this.hospitalOutResults = response.data || []
+            const hospitals = response.data || []
+            this.hospitalOutResults = this.sortHospitalsByRegion(hospitals)
           }).catch(error => {
             console.error('鍔犺浇杞嚭鍖婚櫌澶辫触:', error)
             this.hospitalOutResults = []
@@ -994,10 +1031,12 @@
         clearTimeout(this.searchTimer)
       }
       
-      // 濡傛灉鍏抽敭璇嶄负绌猴紝鍙樉绀哄綋鍓嶅尯鍩熺殑鍖婚櫌
+      // 濡傛灉鍏抽敭璇嶄负绌猴紝鏄剧ず褰撳墠鍖哄煙鐨勫尰闄�
       if (!keyword || keyword.trim() === '') {
         searchHospitals('', this.selectedRegion).then(response => {
-          this.hospitalOutResults = response.data || []
+          const hospitals = response.data || []
+          // 纭繚"瀹朵腑"鍦ㄦ渶鍓嶉潰
+          this.hospitalOutResults = this.sortHospitalsByRegion(hospitals)
         }).catch(error => {
           console.error('鍔犺浇杞嚭鍖婚櫌澶辫触:', error)
           this.hospitalOutResults = []
@@ -1016,7 +1055,9 @@
     searchHospitalOut(keyword) {
       // 浼犲叆鍏抽敭璇嶅拰鍦板煙杩囨护锛屽彧鎼滅储褰撳墠鍖哄煙鐨勫尰闄�
       searchHospitals(keyword, this.selectedRegion).then(response => {
-        this.hospitalOutResults = response.data || []
+        const hospitals = response.data || []
+        // 纭繚"瀹朵腑"鍦ㄦ渶鍓嶉潰
+        this.hospitalOutResults = this.sortHospitalsByRegion(hospitals)
         this.showHospitalOutResults = true
         console.log('鎼滅储杞嚭鍖婚櫌:', keyword, '鍖哄煙:', this.selectedRegion, '缁撴灉鏁�:', this.hospitalOutResults.length)
       }).catch(error => {
@@ -1032,10 +1073,18 @@
       // 濡傛灉閫夋嫨鐨勬槸"瀹朵腑"锛屾竻绌哄湴鍧�璁╃敤鎴锋墜鍔ㄨ緭鍏ワ紱鍚﹀垯鑷姩濉厖鍦板潃
       if (hospital.hospName === '瀹朵腑') {
         this.taskForm.hospitalOut.address = ''
+        // 绉戝鑷姩璁剧疆涓�"鍏跺畠"
+        this.taskForm.hospitalOut.department = '鍏跺畠'
+        this.taskForm.hospitalOut.departmentId = null
       } else {
         // 鍚堝苟鐪佸競鍖� + 璇︾粏鍦板潃
         const fullAddress = this.buildFullAddress(hospital)
         this.taskForm.hospitalOut.address = fullAddress
+        // 娓呯┖绉戝锛岃鐢ㄦ埛閲嶆柊閫夋嫨
+        if (this.taskForm.hospitalOut.department === '鍏跺畠') {
+          this.taskForm.hospitalOut.department = ''
+          this.taskForm.hospitalOut.departmentId = null
+        }
       }
       this.hospitalOutSearchKeyword = hospital.hospName
       this.showHospitalOutResults = false
@@ -1115,7 +1164,7 @@
       if (!keyword || keyword.trim() === '') {
         searchHospitals('', '').then(response => {
           const allHospitals = response.data || []
-          // 鎸夊湴鍩熸帓搴忥細鏈湴鍖哄煙浼樺厛
+          // 鎸夊湴鍩熸帓搴忥細"瀹朵腑"鏈�鍓嶏紝鏈湴鍖哄煙浼樺厛
           this.hospitalInResults = this.sortHospitalsByRegion(allHospitals)
         }).catch(error => {
           console.error('鍔犺浇杞叆鍖婚櫌澶辫触:', error)
@@ -1136,7 +1185,7 @@
       // 浼犲叆鍏抽敭璇嶏紝涓嶄紶鍦板煙杩囨护锛堟悳绱㈡墍鏈夊尯鍩燂級
       searchHospitals(keyword, '').then(response => {
         const allHospitals = response.data || []
-        // 鎸夊湴鍩熸帓搴忥細鏈湴鍖哄煙浼樺厛
+        // 鎸夊湴鍩熸帓搴忥細"瀹朵腑"鏈�鍓嶏紝鏈湴鍖哄煙浼樺厛
         this.hospitalInResults = this.sortHospitalsByRegion(allHospitals)
         this.showHospitalInResults = true
         console.log('鎼滅储杞叆鍖婚櫌:', keyword, '缁撴灉鏁�:', this.hospitalInResults.length)
@@ -1153,10 +1202,18 @@
       // 濡傛灉閫夋嫨鐨勬槸"瀹朵腑"锛屾竻绌哄湴鍧�璁╃敤鎴锋墜鍔ㄨ緭鍏ワ紱鍚﹀垯鑷姩濉厖鍦板潃
       if (hospital.hospName === '瀹朵腑') {
         this.taskForm.hospitalIn.address = ''
+        // 绉戝鑷姩璁剧疆涓�"鍏跺畠"
+        this.taskForm.hospitalIn.department = '鍏跺畠'
+        this.taskForm.hospitalIn.departmentId = null
       } else {
         // 鍚堝苟鐪佸競鍖� + 璇︾粏鍦板潃
         const fullAddress = this.buildFullAddress(hospital)
         this.taskForm.hospitalIn.address = fullAddress
+        // 娓呯┖绉戝锛岃鐢ㄦ埛閲嶆柊閫夋嫨
+        if (this.taskForm.hospitalIn.department === '鍏跺畠') {
+          this.taskForm.hospitalIn.department = ''
+          this.taskForm.hospitalIn.departmentId = null
+        }
       }
       this.hospitalInSearchKeyword = hospital.hospName
       this.showHospitalInResults = false
@@ -2006,6 +2063,14 @@
               color: #333;
               font-weight: bold;
               margin-bottom: 8rpx;
+              
+              .hospital-short {
+                display: block;
+                font-size: 22rpx;
+                color: #999;
+                font-weight: normal;
+                margin-top: 6rpx;
+              }
             }
             
             .hospital-address {
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/HospDataController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/HospDataController.java
index cf35021..a83ee32 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/HospDataController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/HospDataController.java
@@ -36,6 +36,11 @@
             @RequestParam(value = "keyword", required = false) String keyword,
             @RequestParam(value = "region", required = false) String region) {
         List<HospData> list = hospDataMapper.searchHospitals(keyword, region);
+        Integer homeHospId=hospDataMapper.getHomeHospId();
+        if(homeHospId>0 && list.stream().count()>0 && list.stream().filter(hospData -> hospData.getHospId().equals(homeHospId)).count()<=0) {
+            HospData hospData=   hospDataMapper.selectHospDataById(homeHospId);
+            list.add(0,hospData);
+        }
         return success(list);
     }
     
@@ -62,12 +67,10 @@
         if (hospIds == null || hospIds.isEmpty()) {
             return success();
         }
-        Integer homeHospId=hospDataMapper.getHomeHospId();
-        if (!hospIds.contains(homeHospId)) {
-            hospIds.add(0,homeHospId);
-        }
+
         // 鏍规嵁ID鍒楄〃鏌ヨ鍖婚櫌璇︽儏
         List<HospData> hospitals = hospDataMapper.selectHospDataByIds(hospIds, region);
+
         return success(hospitals);
     }
     
@@ -86,11 +89,14 @@
             return success();
         }
         Integer homeHospId=hospDataMapper.getHomeHospId();
-        if (!hospIds.contains(homeHospId)) {
-            hospIds.add(0,homeHospId);
-        }
+
         // 鏍规嵁ID鍒楄〃鏌ヨ鍖婚櫌璇︽儏
         List<HospData> hospitals = hospDataMapper.selectHospDataByIds(hospIds, region);
+        if(homeHospId>0) {
+
+         HospData hospData=   hospDataMapper.selectHospDataById(homeHospId);
+         hospitals.add(0,hospData);
+        }
         return success(hospitals);
     }
 }
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
index f989b8b..4bef3fb 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
@@ -2,6 +2,8 @@
 
 import java.util.List;
 import java.util.Set;
+
+import com.ruoyi.system.service.ISysUserService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -18,8 +20,10 @@
 import com.ruoyi.framework.web.service.SysLoginService;
 import com.ruoyi.framework.web.service.SysPermissionService;
 import com.ruoyi.framework.web.service.TokenService;
+import com.ruoyi.framework.web.service.WechatLoginService;
 import com.ruoyi.system.service.ISysDeptService;
 import com.ruoyi.system.service.ISysMenuService;
+import com.ruoyi.common.annotation.Anonymous;
 
 /**
  * 鐧诲綍楠岃瘉
@@ -33,6 +37,8 @@
     private SysLoginService loginService;
 
     @Autowired
+    private ISysUserService userService;
+    @Autowired
     private ISysMenuService menuService;
 
     @Autowired
@@ -43,6 +49,87 @@
 
     @Autowired
     private ISysDeptService deptService;
+    
+    @Autowired
+    private WechatLoginService wechatLogin;
+
+    /**
+     * 寰俊涓�閿櫥褰� - 閫氳繃OpenID鍜孶nionID鐧诲綍
+     * 浣跨敤WechatLoginService杩涜璁よ瘉
+     * 
+     * @param requestBody 鍖呭惈openId鍜寀nionId鐨勮姹備綋
+     * @return 缁撴灉
+     */
+    @Anonymous
+    @PostMapping("/wechat/login/openid")
+    public AjaxResult loginByOpenId(@RequestBody java.util.Map<String, Object> requestBody)
+    {
+        String openId = (String) requestBody.get("openId");
+        String unionId = (String) requestBody.get("unionId");
+        
+        if (com.ruoyi.common.utils.StringUtils.isEmpty(openId))
+        {
+            return AjaxResult.error("缂哄皯openId鍙傛暟");
+        }
+        
+        try
+        {
+            // 璋冪敤WechatLoginService杩涜璁よ瘉
+            String token = wechatLogin.loginByOpenId(openId, unionId);
+            
+            AjaxResult ajax = AjaxResult.success("鐧诲綍鎴愬姛");
+            ajax.put(Constants.TOKEN, token);
+            return ajax;
+        }
+        catch (Exception e)
+        {
+            return AjaxResult.error(e.getMessage());
+        }
+    }
+    
+
+    /**
+     * 寰俊鎵嬫満鍙风櫥褰�(鎺ㄨ崘浣跨敤)
+     * 
+     * @param requestBody 鍖呭惈loginCode(寰俊鐧诲綍code)鍜宲honeCode(鎵嬫満鍙锋巿鏉僣ode)
+     * @return 缁撴灉
+     */
+    @Anonymous
+    @PostMapping("/wechat/login/phone")
+    public AjaxResult loginByWechatPhone(@RequestBody java.util.Map<String, Object> requestBody)
+    {
+        String loginCode = (String) requestBody.get("loginCode");
+        String phoneCode = (String) requestBody.get("phoneCode");
+        
+        if (com.ruoyi.common.utils.StringUtils.isEmpty(loginCode))
+        {
+            return AjaxResult.error("缂哄皯寰俊鐧诲綍code");
+        }
+        
+        if (com.ruoyi.common.utils.StringUtils.isEmpty(phoneCode))
+        {
+            return AjaxResult.error("缂哄皯鎵嬫満鍙锋巿鏉僣ode");
+        }
+        
+        try
+        {
+            // 璋冪敤WechatLoginService杩涜璁よ瘉
+            java.util.Map<String, Object> loginResult = wechatLogin.loginByWechatPhone(loginCode, phoneCode);
+            
+            AjaxResult ajax = AjaxResult.success("鐧诲綍鎴愬姛");
+            ajax.put(Constants.TOKEN, loginResult.get("token"));
+            ajax.put("openId", loginResult.get("openId"));
+            if (loginResult.containsKey("unionId"))
+            {
+                ajax.put("unionId", loginResult.get("unionId"));
+            }
+            return ajax;
+        }
+        catch (Exception e)
+        {
+            return AjaxResult.error(e.getMessage());
+        }
+    }
 
     /**
      * 鐧诲綍鏂规硶
diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml
index 8c0477f..73d26f3 100644
--- a/ruoyi-admin/src/main/resources/application-dev.yml
+++ b/ruoyi-admin/src/main/resources/application-dev.yml
@@ -91,6 +91,17 @@
 # 浜岀淮鐮侀厤缃�
 qrcode:
   defaultUrl: http://localhost:81/evaluation?vehicle={vehicleNo}  # 榛樿浜岀淮鐮乁RL妯℃澘锛寋vehicleNo}浼氳鏇挎崲涓哄疄闄呰溅鐗屽彿
+
+
+# 寰俊閰嶇疆
+evaluationWechat:
+  appId: wx70f6a7346ee842c0
+  appSecret: 2d6c59de85e876b7eadebeba62e5417a
+  redirectUri: http://yourdomain.com/evaluation
+# 璋冨害鐢ㄧ殑weixin閰嶇疆
+transferConfigWeixin:
+  appId: wx40692cc44953a8cb
+  appSecret: 9638b7d8bb988e4daaac7ac35457f296
 # 鏃х郴缁熼厤缃�
 legacy:
   system:
diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml
index d9fdcc9..a406505 100644
--- a/ruoyi-admin/src/main/resources/application-prod.yml
+++ b/ruoyi-admin/src/main/resources/application-prod.yml
@@ -89,9 +89,18 @@
   apiUrl: https://api.966120.com.cn/v1/   #娴嬭瘯鐜锛歭ocalhost:8011
 qrcode:
   defaultUrl: https://gzgj.966120.com.cn/evaluation?vehicle={vehicleNo}
-wechat:
+
+
+
+# 寰俊閰嶇疆
+evaluationWechat:
   appId: wx70f6a7346ee842c0
   appSecret: 2d6c59de85e876b7eadebeba62e5417a
+  redirectUri: http://yourdomain.com/evaluation
+# 璋冨害鐢ㄧ殑weixin閰嶇疆
+transferConfigWeixin:
+  appId: wx40692cc44953a8cb
+  appSecret: 9638b7d8bb988e4daaac7ac35457f296
 # 鑵捐鍦板浘閰嶇疆
 tencent:
   map:
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
index a8e12cd..f568f97 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
@@ -94,6 +94,15 @@
 
     /** OA绯荤粺鐨勮鍗曠紪鐮佸垪琛紙濡傦細BF,AB,SA锛� */
     private String oaOrderClass;
+    
+    /** 寰俊OpenID */
+    private String openId;
+    
+    /** 寰俊UnionID */
+    private String unionId;
+    
+    /** 寰俊鏄电О */
+    private String wechatNickname;
 
     public SysUser()
     {
@@ -322,6 +331,36 @@
     {
         this.oaOrderClass = oaOrderClass;
     }
+    
+    public String getOpenId()
+    {
+        return openId;
+    }
+    
+    public void setOpenId(String openId)
+    {
+        this.openId = openId;
+    }
+    
+    public String getUnionId()
+    {
+        return unionId;
+    }
+    
+    public void setUnionId(String unionId)
+    {
+        this.unionId = unionId;
+    }
+    
+    public String getWechatNickname()
+    {
+        return wechatNickname;
+    }
+    
+    public void setWechatNickname(String wechatNickname)
+    {
+        this.wechatNickname = wechatNickname;
+    }
 
     @Override
     public String toString() {
@@ -347,6 +386,9 @@
             .append("dept", getDept())
             .append("oaUserId", getOaUserId())
             .append("oaOrderClass", getOaOrderClass())
+            .append("openId", getOpenId())
+            .append("unionId", getUnionId())
+            .append("wechatNickname", getWechatNickname())
             .toString();
     }
 }
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
index 6ab9fe5..d9e5371 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
@@ -20,6 +20,7 @@
 import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
 import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;
 import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
+import com.ruoyi.framework.security.WechatAuthenticationProvider;
 import com.ruoyi.common.annotation.Anonymous;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.web.method.HandlerMethod;
@@ -78,6 +79,12 @@
     private RequestMappingHandlerMapping requestMappingHandlerMapping;
 
     /**
+     * 寰俊璁よ瘉鎻愪緵鑰�
+     */
+    @Autowired
+    private WechatAuthenticationProvider wechatAuthenticationProvider;
+
+    /**
      * 鑾峰彇鎵�鏈夋爣娉ㄤ簡@Anonymous鐨刄RL
      */
     private Set<String> getAnonymousUrls() {
@@ -98,14 +105,18 @@
 
     /**
      * 韬唤楠岃瘉瀹炵幇
+     * 鏀寔鐢ㄦ埛鍚嶅瘑鐮佽璇佸拰寰俊璁よ瘉
      */
     @Bean
     public AuthenticationManager authenticationManager()
     {
+        // 鐢ㄦ埛鍚嶅瘑鐮佽璇佹彁渚涜��
         DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
         daoAuthenticationProvider.setUserDetailsService(userDetailsService);
         daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
-        return new ProviderManager(daoAuthenticationProvider);
+        
+        // 杩斿洖ProviderManager,鏀寔澶氱璁よ瘉鏂瑰紡
+        return new ProviderManager(daoAuthenticationProvider, wechatAuthenticationProvider);
     }
 
     /**
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/WechatAuthenticationProvider.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/WechatAuthenticationProvider.java
new file mode 100644
index 0000000..0bc8aaa
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/WechatAuthenticationProvider.java
@@ -0,0 +1,85 @@
+package com.ruoyi.framework.security;
+
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.web.service.SysPermissionService;
+import com.ruoyi.system.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.stereotype.Component;
+import java.util.Set;
+
+/**
+ * 寰俊鐧诲綍璁よ瘉鎻愪緵鑰�
+ * 绫讳技浜嶥aoAuthenticationProvider
+ * 
+ * @author ruoyi
+ */
+@Component
+public class WechatAuthenticationProvider implements AuthenticationProvider
+{
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private SysPermissionService permissionService;
+
+    @Override
+    public Authentication authenticate(Authentication authentication) throws AuthenticationException
+    {
+        WechatAuthenticationToken wechatToken = (WechatAuthenticationToken) authentication;
+        
+        String openId = (String) wechatToken.getPrincipal();
+        String unionId = (String) wechatToken.getCredentials();
+        
+        // 鏍规嵁OpenID鏌ヨ鐢ㄦ埛
+        SysUser user = userService.selectUserByOpenId(openId);
+        
+        if (user == null)
+        {
+            throw new BadCredentialsException("璇ュ井淇¤处鍙峰皻鏈粦瀹氱郴缁熺敤鎴�");
+        }
+        
+        // 濡傛灉浼犲叆浜唘nionId,杩涜棰濆楠岃瘉
+        if (StringUtils.isNotEmpty(unionId))
+        {
+            if (StringUtils.isNotEmpty(user.getUnionId()))
+            {
+                if (!unionId.equals(user.getUnionId()))
+                {
+                    throw new BadCredentialsException("寰俊璐﹀彿楠岃瘉澶辫触");
+                }
+            }
+        }
+        
+        // 妫�鏌ョ敤鎴风姸鎬�
+        if ("1".equals(user.getStatus()))
+        {
+            throw new BadCredentialsException("鐢ㄦ埛宸茶鍋滅敤锛岃鑱旂郴绠$悊鍛�");
+        }
+        
+        if ("1".equals(user.getDelFlag()))
+        {
+            throw new BadCredentialsException("鐢ㄦ埛宸茶鍒犻櫎锛岃鑱旂郴绠$悊鍛�");
+        }
+        
+        // 鑾峰彇鐢ㄦ埛鏉冮檺
+        Set<String> permissions = permissionService.getMenuPermission(user);
+        
+        // 鍒涘缓LoginUser瀵硅薄
+        LoginUser loginUser = new LoginUser(user.getUserId(), user.getDeptId(), user, permissions);
+        
+        // 杩斿洖宸茶璇佺殑Token
+        return new WechatAuthenticationToken(loginUser, unionId, loginUser.getAuthorities());
+    }
+
+    @Override
+    public boolean supports(Class<?> authentication)
+    {
+        return WechatAuthenticationToken.class.isAssignableFrom(authentication);
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/security/WechatAuthenticationToken.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/WechatAuthenticationToken.java
new file mode 100644
index 0000000..9b40489
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/security/WechatAuthenticationToken.java
@@ -0,0 +1,86 @@
+package com.ruoyi.framework.security;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import java.util.Collection;
+
+/**
+ * 寰俊鐧诲綍璁よ瘉Token
+ * 绫讳技浜嶶sernamePasswordAuthenticationToken
+ * 
+ * @author ruoyi
+ */
+public class WechatAuthenticationToken extends AbstractAuthenticationToken
+{
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 璁よ瘉涓讳綋(鐧诲綍鍓嶄负openId,鐧诲綍鍚庝负LoginUser)
+     */
+    private final Object principal;
+
+    /**
+     * 璁よ瘉鍑瘉(鍙�夌殑unionId)
+     */
+    private Object credentials;
+
+    /**
+     * 鍒涘缓鏈璇佺殑Token(鐧诲綍鍓�)
+     * 
+     * @param openId 寰俊OpenID
+     * @param unionId 寰俊UnionID(鍙��)
+     */
+    public WechatAuthenticationToken(String openId, String unionId)
+    {
+        super(null);
+        this.principal = openId;
+        this.credentials = unionId;
+        setAuthenticated(false);
+    }
+
+    /**
+     * 鍒涘缓宸茶璇佺殑Token(鐧诲綍鍚�)
+     * 
+     * @param principal 鐧诲綍鐢ㄦ埛淇℃伅
+     * @param credentials 鍑瘉
+     * @param authorities 鏉冮檺鍒楄〃
+     */
+    public WechatAuthenticationToken(Object principal, Object credentials,
+                                     Collection<? extends GrantedAuthority> authorities)
+    {
+        super(authorities);
+        this.principal = principal;
+        this.credentials = credentials;
+        super.setAuthenticated(true);
+    }
+
+    @Override
+    public Object getCredentials()
+    {
+        return this.credentials;
+    }
+
+    @Override
+    public Object getPrincipal()
+    {
+        return this.principal;
+    }
+
+    @Override
+    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException
+    {
+        if (isAuthenticated)
+        {
+            throw new IllegalArgumentException(
+                "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
+        }
+        super.setAuthenticated(false);
+    }
+
+    @Override
+    public void eraseCredentials()
+    {
+        super.eraseCredentials();
+        credentials = null;
+    }
+}
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java
index de6196c..4680343 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java
@@ -37,11 +37,19 @@
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
     {
-        // 灏濊瘯鍒ゆ柇鏄墜鏈哄彿杩樻槸鐢ㄦ埛鍚�
+
+        // 灏濊瘯鍒ゆ柇鏄墜鏈哄彿銆乷penId杩樻槸鐢ㄦ埛鍚�
         SysUser user = null;
         
+        // 鍒ゆ柇鏄惁涓哄井淇penID锛堥�氬父浠�"o"寮�澶达紝28浣嶅瓧绗︼級
+        if (username.startsWith("o") && username.length() == 28)
+        {
+            // 寰俊OpenID鐧诲綍
+            log.info("灏濊瘯浣跨敤寰俊OpenID鐧诲綍锛歿}", username);
+            user = userService.selectUserByOpenId(username);
+        }
         // 鍒ゆ柇鏄惁涓烘墜鏈哄彿锛堢畝鍗曞垽鏂細鍏ㄦ槸鏁板瓧涓�11浣嶏級
-        if (username.matches("^1[3-9]\\d{9}$"))
+        else if (username.matches("^1[3-9]\\d{9}$"))
         {
             // 鎵嬫満鍙风櫥褰�
             log.info("灏濊瘯浣跨敤鎵嬫満鍙风櫥褰曪細{}", username);
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/WechatLoginService.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/WechatLoginService.java
new file mode 100644
index 0000000..16c6ecc
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/WechatLoginService.java
@@ -0,0 +1,175 @@
+package com.ruoyi.framework.web.service;
+
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.entity.SysUser;
+import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.utils.MessageUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.manager.AsyncManager;
+import com.ruoyi.framework.manager.factory.AsyncFactory;
+import com.ruoyi.framework.security.WechatAuthenticationToken;
+import com.ruoyi.system.service.ISysUserService;
+import com.ruoyi.system.service.IWechatLoginService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 寰俊鐧诲綍鏍¢獙鏂规硶
+ * 绫讳技浜嶴ysLoginService
+ * 
+ * @author ruoyi
+ */
+@Component
+public class WechatLoginService
+{
+    @Autowired
+    private TokenService tokenService;
+
+    @Autowired
+    private AuthenticationManager authenticationManager;
+
+    @Autowired
+    private SysLoginService sysLoginService;
+
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private IWechatLoginService wechatApiService;
+
+    /**
+     * 寰俊OpenID鐧诲綍楠岃瘉
+     * 
+     * @param openId 寰俊OpenID
+     * @param unionId 寰俊UnionID(鍙��)
+     * @return token
+     */
+    public String loginByOpenId(String openId, String unionId)
+    {
+        try
+        {
+            // 鍒涘缓寰俊璁よ瘉Token
+            WechatAuthenticationToken authenticationToken = new WechatAuthenticationToken(openId, unionId);
+            
+            // 浣跨敤AuthenticationManager杩涜璁よ瘉
+            Authentication authentication = authenticationManager.authenticate(authenticationToken);
+            
+            // 璁よ瘉鎴愬姛,鑾峰彇LoginUser
+            LoginUser loginUser = (LoginUser) authentication.getPrincipal();
+            
+            // 璁板綍鐧诲綍鎴愬姛鏃ュ織
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(
+                loginUser.getUsername(), 
+                Constants.LOGIN_SUCCESS, 
+                "寰俊OpenID鐧诲綍鎴愬姛"));
+            
+            // 璁板綍鐧诲綍淇℃伅(IP鍜屾椂闂�)
+            sysLoginService.recordLoginInfo(loginUser.getUserId());
+            
+            // 鐢熸垚token
+            return tokenService.createToken(loginUser);
+        }
+        catch (BadCredentialsException e)
+        {
+            // 璁板綍鐧诲綍澶辫触鏃ュ織
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(
+                openId, 
+                Constants.LOGIN_FAIL, 
+                e.getMessage()));
+            throw e;
+        }
+        catch (Exception e)
+        {
+            // 璁板綍鐧诲綍澶辫触鏃ュ織
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(
+                openId, 
+                Constants.LOGIN_FAIL, 
+                e.getMessage()));
+            throw new BadCredentialsException(e.getMessage());
+        }
+    }
+
+    /**
+     * 寰俊鎵嬫満鍙风櫥褰曢獙璇�
+     * 閫氳繃寰俊API鑾峰彇鎵嬫満鍙峰悗杩涜鐧诲綍
+     * 
+     * @param loginCode 寰俊鐧诲綍code
+     * @param phoneCode 鎵嬫満鍙锋巿鏉僣ode
+     * @return 鍖呭惈token銆乷penId銆乽nionId鐨凪ap
+     */
+    public Map<String, Object> loginByWechatPhone(String loginCode, String phoneCode)
+    {
+        Map<String, Object> loginResult = new HashMap<>();
+        
+        try
+        {
+            // 1. 璋冪敤寰俊API鏈嶅姟鑾峰彇鐢ㄦ埛淇℃伅
+            Map<String, Object> result = wechatApiService.loginByWechatPhone(loginCode, phoneCode);
+            
+            if (!(Boolean)result.get("success"))
+            {
+                String errorMsg = (String)result.get("message");
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(
+                    "寰俊鐢ㄦ埛", 
+                    Constants.LOGIN_FAIL, 
+                    errorMsg));
+                throw new BadCredentialsException(errorMsg);
+            }
+            
+            // 2. 鑾峰彇鐢ㄦ埛淇℃伅鍜宱penId
+            SysUser user = (SysUser) result.get("user");
+            String openId = (String) result.get("openId");
+            String unionId = (String) result.get("unionId");
+            
+            // 3. 浣跨敤openId鍜寀nionId杩涜璁よ瘉
+            WechatAuthenticationToken authenticationToken = new WechatAuthenticationToken(openId, unionId);
+            Authentication authentication = authenticationManager.authenticate(authenticationToken);
+            
+            // 4. 璁よ瘉鎴愬姛,鑾峰彇LoginUser
+            LoginUser loginUser = (LoginUser) authentication.getPrincipal();
+            
+            // 5. 璁板綍鐧诲綍鎴愬姛鏃ュ織
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(
+                loginUser.getUsername(), 
+                Constants.LOGIN_SUCCESS, 
+                "寰俊鎵嬫満鍙风櫥褰曟垚鍔�"));
+            
+            // 6. 璁板綍鐧诲綍淇℃伅(IP鍜屾椂闂�)
+            sysLoginService.recordLoginInfo(loginUser.getUserId());
+            
+            // 7. 鐢熸垚token
+            String token = tokenService.createToken(loginUser);
+            
+            // 8. 杩斿洖缁撴灉(鍖呭惈token銆乷penId銆乽nionId)
+            loginResult.put("token", token);
+            loginResult.put("openId", openId);
+            if (StringUtils.isNotEmpty(unionId))
+            {
+                loginResult.put("unionId", unionId);
+            }
+            
+            return loginResult;
+        }
+        catch (BadCredentialsException e)
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(
+                "寰俊鐢ㄦ埛", 
+                Constants.LOGIN_FAIL, 
+                e.getMessage()));
+            throw e;
+        }
+        catch (Exception e)
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(
+                "寰俊鐢ㄦ埛", 
+                Constants.LOGIN_FAIL, 
+                e.getMessage()));
+            throw new BadCredentialsException(e.getMessage());
+        }
+    }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java
index 0f06903..c6a30d0 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java
@@ -139,4 +139,12 @@
      * @return 鐢ㄦ埛淇℃伅
      */
     public SysUser selectUserByOaUserId(@Param("oaUserId") Integer oaUserId);
+    
+    /**
+     * 閫氳繃寰俊OpenID鏌ヨ鐢ㄦ埛
+     * 
+     * @param openId 寰俊OpenID
+     * @return 鐢ㄦ埛瀵硅薄淇℃伅
+     */
+    public SysUser selectUserByOpenId(@Param("openId") String openId);
 }
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java
index 4d4efcd..43a0ec4 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java
@@ -49,6 +49,14 @@
      * @return 鐢ㄦ埛瀵硅薄淇℃伅
      */
     public SysUser selectUserByPhonenumber(String phonenumber);
+    
+    /**
+     * 閫氳繃寰俊OpenID鏌ヨ鐢ㄦ埛
+     * 
+     * @param openId 寰俊OpenID
+     * @return 鐢ㄦ埛瀵硅薄淇℃伅
+     */
+    public SysUser selectUserByOpenId(String openId);
 
     /**
      * 閫氳繃鐢ㄦ埛ID鏌ヨ鐢ㄦ埛
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IWechatLoginService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IWechatLoginService.java
new file mode 100644
index 0000000..f732b67
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IWechatLoginService.java
@@ -0,0 +1,36 @@
+package com.ruoyi.system.service;
+
+import java.util.Map;
+
+/**
+ * 寰俊鐧诲綍鏈嶅姟鎺ュ彛
+ * 
+ * @author ruoyi
+ */
+public interface IWechatLoginService
+{
+    /**
+     * 閫氳繃寰俊code鑾峰彇openid鍜宻ession_key
+     * 
+     * @param code 寰俊鐧诲綍code
+     * @return 鍖呭惈openid銆乽nionid銆乻ession_key鐨凪ap
+     */
+    Map<String, Object> getWechatSession(String code);
+    
+    /**
+     * 鑾峰彇寰俊鐢ㄦ埛鎵嬫満鍙�
+     * 
+     * @param code 鎵嬫満鍙锋巿鏉僣ode
+     * @return 鍖呭惈鎵嬫満鍙蜂俊鎭殑Map
+     */
+    Map<String, Object> getPhoneNumber(String code);
+    
+    /**
+     * 寰俊鎵嬫満鍙风櫥褰�
+     * 
+     * @param loginCode 寰俊鐧诲綍code
+     * @param phoneCode 鎵嬫満鍙锋巿鏉僣ode
+     * @return 鐧诲綍缁撴灉
+     */
+    Map<String, Object> loginByWechatPhone(String loginCode, String phoneCode);
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
index 5add3ac..e6eb938 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
@@ -139,6 +139,18 @@
     {
         return userMapper.selectUserById(userId);
     }
+    
+    /**
+     * 閫氳繃寰俊OpenID鏌ヨ鐢ㄦ埛
+     * 
+     * @param openId 寰俊OpenID
+     * @return 鐢ㄦ埛瀵硅薄淇℃伅
+     */
+    @Override
+    public SysUser selectUserByOpenId(String openId)
+    {
+        return userMapper.selectUserByOpenId(openId);
+    }
 
     /**
      * 鏌ヨ鐢ㄦ埛鎵�灞炶鑹茬粍
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatLoginServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatLoginServiceImpl.java
new file mode 100644
index 0000000..cfbed27
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WechatLoginServiceImpl.java
@@ -0,0 +1,291 @@
+package com.ruoyi.system.service.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.common.config.WechatConfig;
+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.service.IWechatLoginService;
+import com.ruoyi.system.service.ISysUserService;
+
+/**
+ * 寰俊鐧诲綍鏈嶅姟瀹炵幇
+ * 
+ * @author ruoyi
+ */
+@Service
+public class WechatLoginServiceImpl implements IWechatLoginService
+{
+    private static final Logger log = LoggerFactory.getLogger(WechatLoginServiceImpl.class);
+    
+    @Autowired
+    private WechatConfig wechatConfig;
+    
+    @Autowired
+    private ISysUserService userService;
+    
+    /**
+     * 寰俊API - code2Session
+     */
+    private static final String JSCODE_2_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session";
+    
+    /**
+     * 寰俊API - 鑾峰彇鎵嬫満鍙�
+     */
+    private static final String GET_PHONE_NUMBER_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber";
+    
+    /**
+     * 寰俊API - 鑾峰彇access_token
+     */
+    private static final String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
+    
+    /**
+     * 閫氳繃寰俊code鑾峰彇openid鍜宻ession_key
+     * 
+     * @param code 寰俊鐧诲綍code
+     * @return 鍖呭惈openid銆乽nionid銆乻ession_key鐨凪ap
+     */
+    @Override
+    public Map<String, Object> getWechatSession(String code)
+    {
+        Map<String, Object> result = new HashMap<>();
+        
+        try
+        {
+            // 鏋勫缓璇锋眰鍙傛暟
+            String params = "appid=" + wechatConfig.getAppId() +
+                          "&secret=" + wechatConfig.getAppSecret() +
+                          "&js_code=" + code +
+                          "&grant_type=authorization_code";
+            
+            log.info("璋冪敤寰俊jscode2session鎺ュ彛, code: {}", code);
+            
+            // 鍙戦�佽姹�
+            String response = HttpUtils.sendGet(JSCODE_2_SESSION_URL, params);
+            
+            log.info("寰俊jscode2session鍝嶅簲: {}", response);
+            
+            // 瑙f瀽鍝嶅簲
+            JSONObject jsonResponse = JSONObject.parseObject(response);
+            
+            if (jsonResponse.containsKey("errcode") && jsonResponse.getInteger("errcode") != 0)
+            {
+                log.error("寰俊jscode2session澶辫触: {}", response);
+                result.put("success", false);
+                result.put("message", "鑾峰彇寰俊浼氳瘽澶辫触: " + jsonResponse.getString("errmsg"));
+                return result;
+            }
+            
+            result.put("success", true);
+            result.put("openid", jsonResponse.getString("openid"));
+            result.put("session_key", jsonResponse.getString("session_key"));
+            
+            // unionid鍙兘涓虹┖(闇�瑕佸皬绋嬪簭缁戝畾寰俊寮�鏀惧钩鍙�)
+            if (jsonResponse.containsKey("unionid"))
+            {
+                result.put("unionid", jsonResponse.getString("unionid"));
+            }
+            
+            return result;
+        }
+        catch (Exception e)
+        {
+            log.error("璋冪敤寰俊jscode2session鎺ュ彛寮傚父", e);
+            result.put("success", false);
+            result.put("message", "鑾峰彇寰俊浼氳瘽寮傚父: " + e.getMessage());
+            return result;
+        }
+    }
+    
+    /**
+     * 鑾峰彇寰俊access_token
+     */
+    private String getAccessToken()
+    {
+        try
+        {
+            String params = "grant_type=client_credential" +
+                          "&appid=" + wechatConfig.getAppId() +
+                          "&secret=" + wechatConfig.getAppSecret();
+            
+            String response = HttpUtils.sendGet(GET_ACCESS_TOKEN_URL, params);
+            JSONObject jsonResponse = JSONObject.parseObject(response);
+            
+            if (jsonResponse.containsKey("errcode"))
+            {
+                log.error("鑾峰彇access_token澶辫触: {}", response);
+                return null;
+            }
+            
+            return jsonResponse.getString("access_token");
+        }
+        catch (Exception e)
+        {
+            log.error("鑾峰彇access_token寮傚父", e);
+            return null;
+        }
+    }
+    
+    /**
+     * 鑾峰彇寰俊鐢ㄦ埛鎵嬫満鍙�
+     * 
+     * @param code 鎵嬫満鍙锋巿鏉僣ode
+     * @return 鍖呭惈鎵嬫満鍙蜂俊鎭殑Map
+     */
+    @Override
+    public Map<String, Object> getPhoneNumber(String code)
+    {
+        Map<String, Object> result = new HashMap<>();
+        
+        try
+        {
+            // 鑾峰彇access_token
+            String accessToken = getAccessToken();
+            if (StringUtils.isEmpty(accessToken))
+            {
+                result.put("success", false);
+                result.put("message", "鑾峰彇access_token澶辫触");
+                return result;
+            }
+            
+            // 鏋勫缓璇锋眰URL
+            String url = GET_PHONE_NUMBER_URL + "?access_token=" + accessToken;
+            
+            // 鏋勫缓璇锋眰浣�
+            JSONObject requestBody = new JSONObject();
+            requestBody.put("code", code);
+            
+            log.info("璋冪敤寰俊getPhoneNumber鎺ュ彛, code: {}", code);
+            
+            // 鍙戦�丳OST璇锋眰
+            String response = HttpUtils.sendPost(url, requestBody.toJSONString());
+            
+            log.info("寰俊getPhoneNumber鍝嶅簲: {}", response);
+            
+            // 瑙f瀽鍝嶅簲
+            JSONObject jsonResponse = JSONObject.parseObject(response);
+            
+            if (jsonResponse.getInteger("errcode") != 0)
+            {
+                log.error("寰俊getPhoneNumber澶辫触: {}", response);
+                result.put("success", false);
+                result.put("message", "鑾峰彇鎵嬫満鍙峰け璐�: " + jsonResponse.getString("errmsg"));
+                return result;
+            }
+            
+            // 鑾峰彇鎵嬫満鍙蜂俊鎭�
+            JSONObject phoneInfo = jsonResponse.getJSONObject("phone_info");
+            
+            result.put("success", true);
+            result.put("phoneNumber", phoneInfo.getString("phoneNumber"));
+            result.put("purePhoneNumber", phoneInfo.getString("purePhoneNumber"));
+            result.put("countryCode", phoneInfo.getString("countryCode"));
+            
+            return result;
+        }
+        catch (Exception e)
+        {
+            log.error("璋冪敤寰俊getPhoneNumber鎺ュ彛寮傚父", e);
+            result.put("success", false);
+            result.put("message", "鑾峰彇鎵嬫満鍙峰紓甯�: " + e.getMessage());
+            return result;
+        }
+    }
+    
+    /**
+     * 寰俊鎵嬫満鍙风櫥褰�
+     * 
+     * @param loginCode 寰俊鐧诲綍code
+     * @param phoneCode 鎵嬫満鍙锋巿鏉僣ode
+     * @return 鐧诲綍缁撴灉
+     */
+    @Override
+    public Map<String, Object> loginByWechatPhone(String loginCode, String phoneCode)
+    {
+        Map<String, Object> result = new HashMap<>();
+        
+        try
+        {
+            // 1. 鑾峰彇寰俊session(openid銆乽nionid)
+            Map<String, Object> sessionResult = getWechatSession(loginCode);
+            if (!(Boolean)sessionResult.get("success"))
+            {
+                return sessionResult;
+            }
+            
+            String openId = (String) sessionResult.get("openid");
+            String unionId = (String) sessionResult.get("unionid");
+            
+            log.info("鑾峰彇鍒皁penid: {}, unionid: {}", openId, unionId);
+            
+            // 2. 鑾峰彇鎵嬫満鍙�
+            Map<String, Object> phoneResult = getPhoneNumber(phoneCode);
+            if (!(Boolean)phoneResult.get("success"))
+            {
+                return phoneResult;
+            }
+            
+            String phoneNumber = (String) phoneResult.get("purePhoneNumber");
+            
+            log.info("鑾峰彇鍒版墜鏈哄彿: {}", phoneNumber);
+            
+            // 3. 鏍规嵁鎵嬫満鍙锋煡鎵剧敤鎴�
+            SysUser user = userService.selectUserByPhonenumber(phoneNumber);
+            
+            if (user == null)
+            {
+                result.put("success", false);
+                result.put("message", "璇ユ墜鏈哄彿灏氭湭娉ㄥ唽,璇峰厛娉ㄥ唽璐﹀彿");
+                return result;
+            }
+            
+            // 4. 妫�鏌ョ敤鎴风姸鎬�
+            if ("1".equals(user.getStatus()))
+            {
+                result.put("success", false);
+                result.put("message", "鐢ㄦ埛宸茶鍋滅敤,璇疯仈绯荤鐞嗗憳");
+                return result;
+            }
+            
+            if ("1".equals(user.getDelFlag()))
+            {
+                result.put("success", false);
+                result.put("message", "鐢ㄦ埛宸茶鍒犻櫎,璇疯仈绯荤鐞嗗憳");
+                return result;
+            }
+            
+            // 5. 鏇存柊鐢ㄦ埛鐨勫井淇′俊鎭�
+            SysUser updateUser = new SysUser();
+            updateUser.setUserId(user.getUserId());
+            updateUser.setOpenId(openId);
+            if (StringUtils.isNotEmpty(unionId))
+            {
+                updateUser.setUnionId(unionId);
+            }
+            userService.updateUser(updateUser);
+            
+            log.info("鐢ㄦ埛{}寰俊淇℃伅鏇存柊鎴愬姛", user.getUserName());
+            
+            // 6. 杩斿洖鎴愬姛缁撴灉
+            result.put("success", true);
+            result.put("user", user);
+            result.put("openId", openId);
+            result.put("unionId", unionId);
+            
+            return result;
+        }
+        catch (Exception e)
+        {
+            log.error("寰俊鎵嬫満鍙风櫥褰曞紓甯�", e);
+            result.put("success", false);
+            result.put("message", "鐧诲綍寮傚父: " + e.getMessage());
+            return result;
+        }
+    }
+}
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
index 03654d9..206df95 100644
--- a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
@@ -234,6 +234,9 @@
  			<if test="status != null and status != ''">status = #{status},</if>
  			<if test="oaUserId != null">oa_user_id = #{oaUserId},</if>
  			<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>
@@ -274,4 +277,10 @@
 		limit 1
 	</select>
 	
+	<!-- 閫氳繃寰俊OpenID鏌ヨ鐢ㄦ埛 -->
+	<select id="selectUserByOpenId" parameterType="String" resultMap="SysUserResult">
+		<include refid="selectUserVo"/>
+		where u.open_id = #{openId} and u.del_flag = '0'
+	</select>
+	
 </mapper> 
\ No newline at end of file
diff --git a/sql/wechat_login_fields.sql b/sql/wechat_login_fields.sql
new file mode 100644
index 0000000..db487f4
--- /dev/null
+++ b/sql/wechat_login_fields.sql
@@ -0,0 +1,22 @@
+-- 寰俊涓�閿櫥褰曞姛鑳� - 娣诲姞sys_user琛ㄥ井淇$浉鍏冲瓧娈�
+-- 鎵ц鏃堕棿: 2025-11-09
+
+-- 娣诲姞寰俊OpenID瀛楁
+ALTER TABLE `sys_user` ADD COLUMN `open_id` VARCHAR(128) NULL COMMENT '寰俊OpenID' AFTER `oa_order_class`;
+
+-- 娣诲姞寰俊UnionID瀛楁
+ALTER TABLE `sys_user` ADD COLUMN `union_id` VARCHAR(128) NULL COMMENT '寰俊UnionID' AFTER `open_id`;
+
+-- 娣诲姞寰俊鏄电О瀛楁
+ALTER TABLE `sys_user` ADD COLUMN `wechat_nickname` VARCHAR(100) NULL COMMENT '寰俊鏄电О' AFTER `union_id`;
+
+-- 涓篛penID娣诲姞绱㈠紩,鎻愰珮鏌ヨ鏁堢巼
+CREATE INDEX `idx_open_id` ON `sys_user` (`open_id`);
+
+-- 涓篣nionID娣诲姞绱㈠紩,鎻愰珮鏌ヨ鏁堢巼
+CREATE INDEX `idx_union_id` ON `sys_user` (`union_id`);
+
+-- 璇存槑
+-- 1. open_id: 寰俊灏忕▼搴忕殑OpenID,姣忎釜灏忕▼搴忓敮涓�
+-- 2. union_id: 寰俊寮�鏀惧钩鍙扮殑UnionID,鍚屼竴涓讳綋澶氫釜搴旂敤鍏变韩(闇�瑕佸皬绋嬪簭缁戝畾寮�鏀惧钩鍙�)
+-- 3. wechat_nickname: 寰俊鏄电О,鐢ㄤ簬鏄剧ず鐢ㄦ埛淇℃伅

--
Gitblit v1.9.1