7de1396e315896dbc72a9d54e44f77434ea90f18..cfe0b79fbea0fb1d7a5a796e71ada7d3b7812046
4 天以前 wlzboy
feat: 企业微信发送微信小程序cetd
cfe0b7 对比 | 目录
4 天以前 wlzboy
feat: 优化企业微信判断,优化gps分断处理
06a17c 对比 | 目录
5 天以前 wlzboy
feat:增加企业微信登录判断
3bbd80 对比 | 目录
2个文件已添加
13个文件已修改
733 ■■■■ 已修改文件
app/App.vue 159 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/login.vue 98 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/qylogin.vue 161 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/permission.js 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/static/images/loading.gif 补丁 | 查看 | 原始文档 | blame | 历史
app/utils/auth.js 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/utils/wechat.js 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleMileageStatsController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/listener/TaskMessageListener.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/IQyWechatService.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/NotifyDispatchServiceImpl.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/QyWechatServiceImpl.java 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/VehicleGpsSegmentMileageMapper.xml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/App.vue
@@ -4,6 +4,7 @@
  import { getToken } from '@/utils/auth'
  import { getUnreadCount } from '@/api/message'
  import storage from '@/utils/storage'
  import { redirectToLoginByEnvironment } from '@/utils/wechat'
  export default {
    data() {
@@ -13,6 +14,7 @@
      }
    },
    onLaunch: function(options) {
      console.log('App onLaunch, options:', options);
      this.lastToken = getToken()
      this.initApp(options)
      
@@ -26,7 +28,7 @@
        if (token) {
          this.lastToken = token
          this.updateUnreadMessageBadge()
          this.startMessagePolling()
          // this.startMessagePolling()
        }
      })
      
@@ -36,7 +38,11 @@
        this.stopMessagePolling()
        this.lastToken = null
        // 清除消息徽标
        uni.removeTabBarBadge({ index: 3 })
        try {
          uni.removeTabBarBadge({ index: 3 })
        } catch (e) {
          console.error('清除消息徽标失败:', e)
        }
      })
    },
    onShow: function() {
@@ -73,10 +79,14 @@
    methods: {
      // 初始化应用
      initApp(options) {
        // 初始化应用配置
        this.initConfig()
        // 检查用户登录状态并自动跳转到合适的登录页面
        this.checkLoginAndRedirect(options)
        try {
          // 初始化应用配置
          this.initConfig()
          // 检查用户登录状态并自动跳转到合适的登录页面
          this.checkLoginAndRedirect(options)
        } catch (e) {
          console.error('初始化应用失败:', e)
        }
        
        // 注意:不在应用启动时自动启动轮询
        // 只有在用户主动登录成功后才启动(通过 user-login 事件触发)
@@ -86,73 +96,40 @@
      },
      // 检查登录状态并自动跳转到合适的登录页面
      checkLoginAndRedirect(options) {
        if (!getToken()) {
          // 检查运行环境并跳转到对应的登录页面
          // #ifdef MP-WEIXIN
          // 在微信小程序环境中
        try {
          if (!getToken()) {
            console.log('用户未登录,准备跳转到登录页面')
            // 使用工具类根据环境自动跳转到合适的登录页面
            redirectToLoginByEnvironment(options, this.$tab);
          } else {
            console.log('用户已登录,无需跳转')
          }
        } catch (e) {
          console.error('检查登录状态并跳转失败:', e)
          // fallback到普通登录页面
          try {
            // 获取系统信息
            const systemInfo = uni.getSystemInfoSync()
            console.log('系统信息:', systemInfo)
            // 检查environment字段是否为wxwork
            if (systemInfo.environment === 'wxwork') {
              console.log('检测到企业微信环境,跳转到企业微信免登页面')
              // 构造带参数的URL
              let url = '/pages/qylogin'
              if (options && options.query) {
                const queryParams = Object.keys(options.query).map(key => `${key}=${encodeURIComponent(options.query[key])}`).join('&')
                if (queryParams) {
                  url += '?' + queryParams
                }
              }
              this.$tab.reLaunch(url)
              return
            } else {
              console.log('检测到普通微信环境,跳转到微信登录页面')
              // 构造带参数的URL
              let url = '/pages/login'
              if (options && options.query) {
                const queryParams = Object.keys(options.query).map(key => `${key}=${encodeURIComponent(options.query[key])}`).join('&')
                if (queryParams) {
                  url += '?' + queryParams
                }
              }
              this.$tab.reLaunch(url)
              return
            }
          } catch (e) {
            console.error('获取系统信息失败:', e)
            // 默认跳转到普通登录页面
            this.$tab.reLaunch('/pages/login')
            this.$tab.reLaunch('/pages/login');
          } catch (fallbackError) {
            console.error('fallback跳转也失败了:', fallbackError);
          }
          // #endif
          // #ifndef MP-WEIXIN
          // 非微信小程序环境,跳转到普通登录页面
          console.log('非微信小程序环境,跳转到普通登录页面')
          let url = '/pages/login'
          if (options && options.query) {
            const queryParams = Object.keys(options.query).map(key => `${key}=${encodeURIComponent(options.query[key])}`).join('&')
            if (queryParams) {
              url += '?' + queryParams
            }
          }
          this.$tab.reLaunch(url)
          // #endif
        }
      },
      
      // 判断当前是否在登录页面
      isLoginPage() {
        const pages = getCurrentPages()
        if (pages.length === 0) {
        try {
          const pages = getCurrentPages()
          if (pages.length === 0) {
            return false
          }
          const currentPage = pages[pages.length - 1]
          const route = currentPage.route || ''
          // 判断是否为登录相关页面
          return route.includes('login') || route.includes('register')
        } catch (e) {
          console.error('判断是否为登录页面失败:', e)
          return false
        }
        const currentPage = pages[pages.length - 1]
        const route = currentPage.route || ''
        // 判断是否为登录相关页面
        return route.includes('login') || route.includes('register')
      },
      
      // 更新未读消息徽标
@@ -169,15 +146,23 @@
          
          if (count > 0) {
            // 设置徽标
            uni.setTabBarBadge({
              index: 3, // 消息页面在tabBar中的索引位置(0开始)
              text: count > 99 ? '99+' : count.toString()
            })
            try {
              uni.setTabBarBadge({
                index: 3, // 消息页面在tabBar中的索引位置(0开始)
                text: count > 99 ? '99+' : count.toString()
              })
            } catch (e) {
              console.error('设置消息徽标失败:', e)
            }
          } else {
            // 移除徽标
            uni.removeTabBarBadge({
              index: 3
            })
            try {
              uni.removeTabBarBadge({
                index: 3
              })
            } catch (e) {
              console.error('移除消息徽标失败:', e)
            }
          }
        }).catch(error => {
          console.error('获取未读消息数量失败:', error)
@@ -186,22 +171,30 @@
      
      // 启动消息轮询
      startMessagePolling() {
        // 每30秒轮询一次
        this.messagePollingTimer = setInterval(() => {
          if (getToken()) {
            this.updateUnreadMessageBadge()
          } else {
            // 如果用户已登出,停止轮询
            this.stopMessagePolling()
          }
        }, 30000) // 30秒
        try {
          // 每30秒轮询一次
          this.messagePollingTimer = setInterval(() => {
            if (getToken()) {
              this.updateUnreadMessageBadge()
            } else {
              // 如果用户已登出,停止轮询
              this.stopMessagePolling()
            }
          }, 30000) // 30秒
        } catch (e) {
          console.error('启动消息轮询失败:', e)
        }
      },
      
      // 停止消息轮询
      stopMessagePolling() {
        if (this.messagePollingTimer) {
          clearInterval(this.messagePollingTimer)
          this.messagePollingTimer = null
        try {
          if (this.messagePollingTimer) {
            clearInterval(this.messagePollingTimer)
            this.messagePollingTimer = null
          }
        } catch (e) {
          console.error('停止消息轮询失败:', e)
        }
      },
      
app/pages/index.vue
@@ -279,7 +279,7 @@
    this.hasSubscribed = true;//subscribeManager.checkLocalSubscribeStatus();
    // 自动订阅(如果未订阅则显示确认弹窗)
    this.autoSubscribeOnLaunch();
    // this.autoSubscribeOnLaunch();
    // 加载用户绑定车辆信息
    this.loadUserVehicle();
app/pages/login.vue
@@ -64,6 +64,7 @@
<script>
  import { getCodeImg, loginByOpenId, loginByWechatPhone } from '@/api/login'
  import { isWxWorkEnvironment,redirectToQyLogin } from '@/utils/wechat'
  export default {
    data() {
@@ -92,6 +93,13 @@
    onLoad(options) {
      // 保存页面参数
      this.pageOptions = options || {}
      isWxWorkEnvironment().then(res=>{
        if(res){
          // console.log("企业微信环境 login.vue....")
          redirectToQyLogin(options,this.$tab);
        }
      });
    },
    created() {
      this.getCode()
@@ -159,15 +167,8 @@
        this.$store.dispatch('GetInfo').then(res => {
          // 触发登录成功事件,启动消息轮询
          uni.$emit('user-login')
          // 检查是否有redirect参数指定跳转页面
          if (this.pageOptions.redirect) {
            // 解码redirect参数
            const redirectUrl = decodeURIComponent(this.pageOptions.redirect)
            this.$tab.reLaunch(redirectUrl)
          } else {
            // 默认跳转到首页
            this.$tab.reLaunch('/pages/index')
          }
          // 处理自动跳转逻辑
          this.redirectAfterLogin()
        })
      },
      
@@ -184,6 +185,8 @@
        this.isWechat = false
        console.log('当前环境:非微信小程序')
        // #endif
      },
      
      // 尝试自动登录(检查本地是否有保存的OpenID)
@@ -251,15 +254,10 @@
                  const { setToken } = require('@/utils/auth')
                  setToken(token)
                  
                  // 检查是否有redirect参数指定跳转页面
                  if (this.pageOptions.redirect) {
                    // 解码redirect参数
                    const redirectUrl = decodeURIComponent(this.pageOptions.redirect)
                    this.$tab.reLaunch(redirectUrl)
                  } else {
                    // 默认跳转到首页
                    this.$tab.reLaunch('/pages/index')
                  }
                  // 获取用户信息并处理自动跳转逻辑
                  this.$store.dispatch('GetInfo').then(() => {
                    this.redirectAfterLogin()
                  });
                } else {
                  this.$modal.msgError(response.msg || '登录失败')
                }
@@ -297,15 +295,10 @@
            const { setToken } = require('@/utils/auth')
            setToken(token)
            
            // 检查是否有redirect参数指定跳转页面
            if (this.pageOptions.redirect) {
              // 解码redirect参数
              const redirectUrl = decodeURIComponent(this.pageOptions.redirect)
              this.$tab.reLaunch(redirectUrl)
            } else {
              // 默认跳转到首页
              this.$tab.reLaunch('/pages/index')
            }
            // 获取用户信息并处理自动跳转逻辑
            this.$store.dispatch('GetInfo').then(() => {
              this.redirectAfterLogin()
            });
          } else {
            // OpenID未绑定或验证失败,需要获取手机号绑定
            console.log('该OpenID尚未绑定或验证失败,需要获取手机号')
@@ -317,6 +310,57 @@
          console.error('登录失败:', error)
          this.$modal.msgError('登录失败,请重试')
        })
      },
      /**
       * 获取URL参数
       */
      getUrlParam(name) {
        // #ifdef H5
        const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
        const r = window.location.search.substr(1).match(reg);
        if (r != null) return decodeURIComponent(r[2]);
        // #endif
        // #ifdef MP-WEIXIN
        // 在小程序中,参数通过onLoad的options传递
        if (this.pageOptions && this.pageOptions[name]) {
          return decodeURIComponent(this.pageOptions[name]);
        }
        // #endif
        return null;
      },
      /**
       * 登录成功后的跳转处理
       */
      redirectAfterLogin() {
        try {
          // 检查是否有redirect参数指定跳转页面
          let redirectUrl = this.getUrlParam("redirect");
          // 如果没有redirect参数,检查是否有保存的目标页面
          if (!redirectUrl) {
            const { getTargetUrl } = require('@/utils/auth')
            redirectUrl = getTargetUrl()
          }
          if (redirectUrl) {
            // 解码redirect参数
            redirectUrl = decodeURIComponent(redirectUrl);
            console.log("自动跳转到指定页面:", redirectUrl);
            this.$tab.reLaunch(redirectUrl);
          } else {
            // 默认跳转到首页
            console.log("跳转到首页");
            this.$tab.reLaunch('/pages/index');
          }
        } catch (e) {
          console.error("跳转失败,使用默认跳转:", e);
          // 出现异常时,默认跳转到首页
          this.$tab.reLaunch('/pages/index');
        }
      }
    }
  }
app/pages/qylogin.vue
@@ -20,6 +20,7 @@
<script>
import { qyWechatAutoLogin } from "@/api/login";
import { buildUrlWithParams } from "@/utils/wechat";
export default {
  data() {
@@ -33,33 +34,21 @@
  },
  onLoad(options) {
    // 保存页面参数
    this.pageOptions = options || {};
    // 页面加载时执行免登流程
    this.qyWechatAutoLogin();
    // 添加容错处理
    try {
      // 保存页面参数
      this.pageOptions = options || {};
      // 页面加载时执行免登流程
      this.qyWechatAutoLogin();
    } catch (e) {
      console.error('页面加载出错:', e);
      this.loading = false;
      this.error = true;
      this.errorMessage = "页面初始化失败,请重试";
    }
  },
  methods: {
    async getLoginCode() {
      //这里要调用wx.qy.login获取code,是否有集成到uni-app中呢
      return new Promise((resolve, reject) => {
        wx.qy.login({
          success: (res) => {
            if (res.code) {
              console.log("企业微信小程序 ---> code:", res.code);
              resolve(res.code); // 返回 code 给后端
            } else {
              reject(new Error("获取 code 失败:" + res.errMsg));
            }
          },
          fail: (err) => {
            console.error("wx.qy.login 调用失败:", err);
            // 常见失败原因:非企业微信客户端打开、小程序未关联企业微信等
            reject(new Error("企业微信登录接口调用失败:" + err.errMsg));
          },
        });
      });
    },
    /**
     * 企业微信免登流程
     */
@@ -72,13 +61,11 @@
        // 在微信小程序环境中,通过企业微信免登
        console.log("企业微信小程序环境免登");
        // 获取URL参数中的code
        // 直接通过企业微信API获取code,不使用URL中的code
        const code = await this.getLoginCode();
        if (!code) {
          // 如果没有code,尝试通过企业微信API获取
          this.handleWxWorkLogin();
          return;
          throw new Error("无法获取企业微信授权code");
        }
        // 调用后端接口进行免登
@@ -89,7 +76,7 @@
          const token = response.data.token;
          
          this.$store.commit('SET_TOKEN', token)
           // 必须调用setToken保存到本地存储
          // 必须调用setToken保存到本地存储
          const { setToken } = require('@/utils/auth')
          setToken(token)
          // 获取用户信息
@@ -109,10 +96,10 @@
        }
        // 获取URL参数中的code
        const codeH5 = this.getUrlParam("code");
        let codeH5 = this.getUrlParam("code");
        // 如果URL中没有code,则跳转到企业微信授权页面
        if (!codeH5) {
          // 如果没有code,则跳转到企业微信授权页面
          this.redirectToWxWorkAuth();
          return;
        }
@@ -148,56 +135,46 @@
    },
    /**
     * 处理企业微信登录
     * 获取企业微信登录code
     */
    handleWxWorkLogin() {
      // #ifdef MP-WEIXIN
      // 在企业微信小程序中,可以直接调用企业微信登录API
      uni.login({
        provider: "weixin",
        success: (loginRes) => {
          console.log("企业微信登录成功", loginRes);
          // 调用后端接口进行免登
          qyWechatAutoLogin(loginRes.code)
            .then((response) => {
              if (response.code === 200) {
                // 免登成功,保存token
                const token = response.data.token;
                this.$store.commit("SET_TOKEN", token);
                uni.setStorageSync("token", token);
                // 获取用户信息
                this.$store.dispatch("GetInfo").then(() => {
                  // 跳转到首页或其他指定页面
                  this.redirectAfterLogin();
                });
              } else {
                throw new Error(response.msg || "免登失败");
              }
            })
            .catch((error) => {
              console.error("免登失败:", error);
              this.loading = false;
              this.error = true;
              this.errorMessage = error.message || "免登失败,请稍后重试";
            });
        },
        fail: (err) => {
          console.error("企业微信登录失败:", err);
          this.loading = false;
          this.error = true;
          this.errorMessage = "企业微信登录失败,请稍后重试";
        },
    getLoginCode() {
      return new Promise((resolve, reject) => {
        // #ifdef MP-WEIXIN
        // 使用企业微信小程序API获取code
        wx.qy.login({
          success: (res) => {
            if (res.code) {
              console.log("企业微信小程序 ---> code:", res.code);
              resolve(res.code);
            } else {
              reject(new Error("获取code失败:" + res.errMsg));
            }
          },
          fail: (err) => {
            console.error("wx.qy.login调用失败:", err);
            reject(new Error("企业微信登录接口调用失败:" + err.errMsg));
          },
        });
        // #endif
        // #ifndef MP-WEIXIN
        resolve(null);
        // #endif
      });
      // #endif
    },
    /**
     * 检查是否在企业微信环境
     */
    isWxWorkEnvironment() {
      // #ifdef H5
      const userAgent = navigator.userAgent.toLowerCase();
      return userAgent.includes("wxwork");
      // #endif
      // #ifndef H5
      return false;
      // #endif
    },
    /**
@@ -211,17 +188,20 @@
      // #endif
      // #ifdef MP-WEIXIN
      // 在小程序中可以通过其他方式获取参数
      // 这里简化处理,实际项目中可以根据需要调整
      // 在小程序中,参数通过onLoad的options传递
      if (this.pageOptions && this.pageOptions[name]) {
        return decodeURIComponent(this.pageOptions[name]);
      }
      // #endif
      return null;
    },
    /**
     * 跳转到企业微信授权页面
     * 跳转到企业微信授权页面(H5环境)
     */
    redirectToWxWorkAuth() {
      // #ifdef H5
      // 从全局配置中获取企业微信配置
      const config = getApp().globalData.config;
      const corpId = config.qyWechatCorpId || "your_corp_id"; // 企业ID
@@ -232,19 +212,36 @@
      const authUrl = `https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=${corpId}&agentid=${agentId}&redirect_uri=${redirectUri}&state=${state}`;
      window.location.href = authUrl;
      // #endif
    },
    /**
     * 登录成功后的跳转处理
     */
    redirectAfterLogin() {
      // 检查是否有redirect参数指定跳转页面
      if (this.pageOptions.redirect) {
        // 解码redirect参数
        const redirectUrl = decodeURIComponent(this.pageOptions.redirect);
        this.$tab.reLaunch(redirectUrl);
      } else {
        // 默认跳转到首页
      try {
        // 检查是否有redirect参数指定跳转页面
        let redirectUrl = this.getUrlParam("redirect");
        // 如果没有redirect参数,检查是否有保存的目标页面
        if (!redirectUrl) {
          const { getTargetUrl } = require('@/utils/auth')
          redirectUrl = getTargetUrl()
        }
        if (redirectUrl) {
          // 解码redirect参数
          redirectUrl = decodeURIComponent(redirectUrl);
          console.log("自动跳转到指定页面:", redirectUrl);
          this.$tab.reLaunch(redirectUrl);
        } else {
          // 默认跳转到首页
          console.log("跳转到首页");
          this.$tab.reLaunch("/pages/index");
        }
      } catch (e) {
        console.error("跳转失败,使用默认跳转:", e);
        // 出现异常时,默认跳转到首页
        this.$tab.reLaunch("/pages/index");
      }
    },
app/permission.js
@@ -19,20 +19,39 @@
  return whiteList.indexOf(path) !== -1
}
// 保存目标页面URL,用于登录后跳转
function saveTargetUrl(url) {
  try {
    uni.setStorageSync('targetUrl', url)
  } catch (e) {
    console.error('保存目标URL失败:', e)
  }
}
// 页面跳转验证拦截器
let list = ["navigateTo", "redirectTo", "reLaunch", "switchTab"]
list.forEach(item => {
  uni.addInterceptor(item, {
    invoke(to) {
      if (getToken()) {
        // 已登录
        if (to.url === loginPage) {
          // 如果已经登录还要访问登录页,直接跳转到首页
          uni.reLaunch({ url: "/" })
          return false
        }
        return true
      } else {
        // 未登录
        if (checkWhite(to.url)) {
          // 白名单页面可以直接访问
          return true
        }
        // 保存当前要访问的页面作为登录后跳转的目标
        saveTargetUrl(to.url)
        // 跳转到登录页面
        uni.reLaunch({ url: loginPage })
        return false
      }
app/static/images/loading.gif
app/utils/auth.js
@@ -11,3 +11,17 @@
export function removeToken() {
  return uni.removeStorageSync(TokenKey)
}
// 获取并清除目标页面URL
export function getTargetUrl() {
  try {
    const url = uni.getStorageSync('targetUrl')
    if (url) {
      uni.removeStorageSync('targetUrl')
    }
    return url
  } catch (e) {
    console.error('获取目标URL失败:', e)
    return null
  }
}
app/utils/wechat.js
New file
@@ -0,0 +1,127 @@
/**
 * 微信环境检测和跳转工具类
 */
/**
 * 检查是否为企业微信环境
 * @returns {Promise<boolean>} 是否为企业微信环境
 */
export function isWxWorkEnvironment() {
  return new Promise((resolve) => {
    // #ifdef MP-WEIXIN
    try {
      // 获取系统信息
      const systemInfo = uni.getSystemInfoSync();
      // 检查environment字段是否为wxwork
      const isWxWork = systemInfo.environment === 'wxwork';
      console.log('环境检测结果:', isWxWork, systemInfo);
      resolve(isWxWork);
    } catch (e) {
      console.error('获取系统信息失败:', e);
      // fallback检测
      resolve(false);
    }
    // #endif
    // #ifndef MP-WEIXIN
    resolve(false);
    // #endif
  });
}
/**
 * 构造带参数的URL
 * @param {string} baseUrl 基础URL
 * @param {Object} queryParameters 查询参数对象
 * @returns {string} 构造后的URL
 */
export function buildUrlWithParams(baseUrl, queryParameters) {
  if (!queryParameters) return baseUrl;
  const queryParams = Object.keys(queryParameters)
    .map(key => `${key}=${encodeURIComponent(queryParameters[key])}`)
    .join('&');
  if (queryParams) {
    return baseUrl + '?' + queryParams;
  }
  return baseUrl;
}
/**
 * 根据环境自动跳转到合适的登录页面
 * @param {Object} options 启动参数
 * @param {Object} tab tab导航对象
 */
export async function redirectToLoginByEnvironment(options, tab) {
  // #ifdef MP-WEIXIN
  // 在微信小程序环境中
  try {
    const isWxWork = await isWxWorkEnvironment();
    if (isWxWork) {
      console.log('检测到企业微信环境,跳转到企业微信免登页面');
      // 构造带参数的URL
      redirectToQyLogin(options, tab);
      return;
    } else {
      console.log('检测到普通微信环境,跳转到微信登录页面');
      redirectToLogin(options, tab);
      return;
    }
  } catch (e) {
    console.error('环境检测失败:', e);
    redirectToLogin(options, tab);
  }
  // #endif
  // #ifndef MP-WEIXIN
  // 非微信小程序环境,跳转到普通登录页面
  console.log('非微信小程序环境,跳转到普通登录页面');
  redirectToLogin(options, tab);
  // #endif
}
export function redirectToQyLogin(options, tab) {
  try {
    let url = '/pages/qylogin';
    if (options && options.query) {
      url = buildUrlWithParams(url, options.query);
    }
    console.log('跳转到企业微信登录页面:', url);
    tab.reLaunch(url);
  } catch (e) {
    console.error('跳转到企业微信登录页面失败:', e);
    // fallback到普通登录页面
    redirectToLogin(options, tab);
  }
}
export function redirectToLogin(options, tab) {
  try {
    let url = '/pages/login';
    if (options && options.query) {
      url = buildUrlWithParams(url, options.query);
    }
    console.log('跳转到普通登录页面:', url);
    tab.reLaunch(url);
  } catch (e) {
    console.error('跳转到普通登录页面失败:', e);
    // 如果跳转失败,至少确保不会卡在当前页面
    try {
      tab.reLaunch('/pages/login');
    } catch (fallbackError) {
      console.error('fallback跳转也失败了:', fallbackError);
    }
  }
}
export default {
  isWxWorkEnvironment,
  buildUrlWithParams,
  redirectToLogin,
  redirectToQyLogin,
  redirectToLoginByEnvironment
};
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleMileageStatsController.java
@@ -114,7 +114,7 @@
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            sdf.setLenient(false); // 严格解析日期
            Date date = sdf.parse(statDate.trim());
            logger.info("---> 手动计算车辆里程汇总,车辆ID: {}, 日期: {}", vehicleId, date);
//            logger.info("---> 手动计算车辆里程汇总,车辆ID: {}, 日期: {}", vehicleId, date);
            VehicleMileageStats stats = vehicleMileageStatsService.calculateAndSaveMileageStats(vehicleId, date);
            // 修复String到Date转换问题,添加更好的错误处理
            if (stats != null) {
ruoyi-system/src/main/java/com/ruoyi/system/listener/TaskMessageListener.java
@@ -260,7 +260,7 @@
            departure = task.getDepartureAddress();
        }
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        content.append(",出发时间:").append(df.format(dispatchTime));
        content.append(",出发时间:").append(df.format(dispatchTime));
        // 添加目的地信息
        String destination = null;
@@ -271,7 +271,6 @@
        }
        
        if (departure != null || destination != null) {
            content = new StringBuilder();
            if (departure != null) {
                content.append("出发地:").append(departure);
            }
ruoyi-system/src/main/java/com/ruoyi/system/service/IQyWechatService.java
@@ -19,6 +19,17 @@
    boolean sendNotifyMessage(Long userId, String title, String content, String notifyUrl);
    /**
     * 发送企业微信消息,带小程序路径链接
     * @param userId
     * @param title
     * @param content
     * @param appId
     * @param businessUrl 小程序访问路径
     * @return
     */
    boolean sendNotifyMessage(Long userId, String title, String content, String appId,String businessUrl);
    /**
     * 发送企业微信文本消息
     * 
     * @param qyUserId 企业微信用户ID
@@ -29,6 +40,8 @@
     */
    boolean sendTextMessage(String qyUserId, String title, String content, String notifyUrl);
    boolean sendTextMessage(String qyUserId, String title, String content, String appId,String businessUrl);
    /**
     * 获取用户的企业微信ID
     * 
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/NotifyDispatchServiceImpl.java
@@ -1,6 +1,7 @@
package com.ruoyi.system.service.impl;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.config.WechatConfig;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.*;
@@ -49,6 +50,9 @@
    @Autowired
    private IQyWechatService qyWechatService;
    @Autowired
    private WechatConfig wechatConfig;
    @Autowired
    private ISysEmergencyTaskService sysEmergencyTaskService;
@@ -348,17 +352,18 @@
           if(emergency==null){
               return false;
           }
           Long dispatchOrderId = emergency.getLegacyDispatchOrdId();
          String oldsiteUrl= sysConfigService.selectConfigByKey("oldsite.url");
          if(oldsiteUrl==null){
              oldsiteUrl="https://sys.966120.com.cn/m_DispatchOrder.gds?DispatchOrdID=";
          }
           String url=oldsiteUrl+dispatchOrderId;
//           Long dispatchOrderId = emergency.getLegacyDispatchOrdId();
//          String oldsiteUrl= sysConfigService.selectConfigByKey("oldsite.url");
//          if(oldsiteUrl==null){
//              oldsiteUrl="https://sys.966120.com.cn/m_DispatchOrder.gds?DispatchOrdID=";
//          }
            String appId=wechatConfig.getAppId();
           String pathPage="/pagesTask/detail?id="+taskId;
            // 发送企业微信消息
            boolean success = qyWechatService.sendNotifyMessage(
                    notifyTask.getUserId(), 
                    notifyTask.getTitle(), 
                    notifyTask.getContent(),url
                    notifyTask.getContent(),appId,pathPage
            );
            
            if (success) {
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/QyWechatServiceImpl.java
@@ -65,6 +65,30 @@
        }
    }
    @Override
    public boolean sendNotifyMessage(Long userId, String title, String content, String appId, String businessUrl) {
        try {
            // 检查服务是否启用
            if (!isEnabled()) {
                log.info("企业微信服务未启用,跳过消息发送");
                return false;
            }
            // 获取用户的企业微信ID
            String qyUserId = getQyUserIdByUserId(userId);
            if (StringUtils.isEmpty(qyUserId)) {
                log.warn("用户{}未绑定企业微信ID,无法发送消息", userId);
                return false;
            }
            // 发送文本消息
            return sendTextMessage(qyUserId, title, content, appId, businessUrl);
        } catch (Exception e) {
            log.error("企业微信消息发送异常,userId={}", userId, e);
            return false;
        }
    }
    /**
     * 发送企业微信文本消息
     */
@@ -101,6 +125,7 @@
            article.setTitle(title);
            article.setDescription(content);
            article.setUrl(notifyUrl);
            // 设置默认图片URL,您可以根据需要修改
            
@@ -141,6 +166,81 @@
        }
    }
    @Override
    public boolean sendTextMessage(String qyUserId, String title, String content, String appId, String businessUrl) {
        try {
            // 检查服务是否启用
            if (!isEnabled()) {
                log.info("企业微信服务未启用,跳过消息发送");
                return false;
            }
            // 获取企业微信配置
            String corpId = configService.selectConfigByKey("qy_wechat.corp_id");
            String corpSecret = configService.selectConfigByKey("qy_wechat.miniprogram_secret");
            if (StringUtils.isEmpty(corpId) || StringUtils.isEmpty(corpSecret)) {
                log.error("企业微信配置不完整,缺少corpId或corpSecret");
                return false;
            }
            // 获取AccessToken
            String accessToken = qyWechatAccessTokenService.getQyMiniAccessToken(corpId, corpSecret);
            if (StringUtils.isEmpty(accessToken)) {
                log.error("获取企业微信AccessToken失败");
                return false;
            }
            // 构造请求URL
            String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + accessToken;
            // 构造文章对象
            QyWechatArticle article = new QyWechatArticle();
            article.setTitle(title);
            article.setDescription(content);
            article.setAppid(appId);
            article.setPagepath(businessUrl);
            // 设置默认图片URL,您可以根据需要修改
            // 构造请求参数
            Map<String, Object> params = new HashMap<>();
            params.put("touser", qyUserId);
            params.put("msgtype", "news");
            params.put("agentid", Integer.parseInt(configService.selectConfigByKey("qywechat.miniprogram.agentid")));
            // 构造文章列表
            List<QyWechatArticle> articles = new ArrayList<>();
            articles.add(article);
            params.put("news", Collections.singletonMap("articles", articles));
            // 发送HTTP POST请求
            String response = sendHttpPostRequest(url, params);
            if (StringUtils.isEmpty(response)) {
                log.error("发送企业微信消息失败,响应为空");
                return false;
            }
            // 解析响应结果
            QyWechatResponse result = parseResponse(response);
            if (result != null && result.getErrcode() == 0) {
                log.info("企业微信消息发送成功,用户ID: {}", qyUserId);
                return true;
            } else {
                log.error("企业微信消息发送失败,错误码: {}, 错误信息: {}",
                        result != null ? result.getErrcode() : "unknown",
                        result != null ? result.getErrmsg() : response);
                return false;
            }
        } catch (Exception e) {
            log.error("企业微信文本消息发送异常,qyUserId={}", qyUserId, e);
            return false;
        }
    }
    /**
     * 获取用户的企业微信ID
     */
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
@@ -532,6 +532,11 @@
                }
            }
        }
        if(updateVO.getAssignees()!=null && !updateVO.getAssignees().isEmpty()){
           TaskCreateVO.AssigneeInfo assigneeInfo= updateVO.getAssignees().get(0);
            task.setAssigneeId(assigneeInfo.getUserId());
            task.setAssigneeName(assigneeInfo.getUserName());
        }
        // 用于跟踪是否需要重新同步(车辆、人员、地址、成交价变更)
        boolean needResync = false;
        int result = sysTaskMapper.updateSysTask(task);
@@ -629,7 +634,7 @@
        if(updateVO.getActualEndTime() != null) {
            task.setActualEndTime(updateVO.getActualEndTime());
        }
        task.setAssigneeId(updateVO.getAssigneeId());
//        task.setAssigneeId(updateVO.getAssigneeId());
        task.setUpdateBy(userName);
        task.setUpdateTime(DateUtils.getNowDate());
        task.setRemark(updateVO.getRemark());
@@ -642,7 +647,11 @@
        task.setDepartureLatitude(updateVO.getDepartureLatitude());
        task.setDestinationLongitude(updateVO.getDestinationLongitude());
        task.setDestinationLatitude(updateVO.getDestinationLatitude());
        if(updateVO.getAssignees()!=null && !updateVO.getAssignees().isEmpty()){
            TaskCreateVO.AssigneeInfo assigneeInfo= updateVO.getAssignees().get(0);
            task.setAssigneeId(assigneeInfo.getUserId());
            task.setAssigneeName(assigneeInfo.getUserName());
        }
        // 如果更新了部门ID
        if (updateVO.getDeptId() != null) {
            task.setDeptId(updateVO.getDeptId());
ruoyi-system/src/main/resources/mapper/system/VehicleGpsSegmentMileageMapper.xml
@@ -66,8 +66,7 @@
        WHERE vehicle_id = #{vehicleId}
          AND segment_start_time &lt;= #{endDate}
          AND segment_end_time &gt;= #{startDate}
          AND segment_start_time IS NOT NULL
          AND segment_end_time IS NOT NULL
          AND segment_distance &gt;0
        ORDER BY segment_start_time
    </select>