import { useAppStore } from "@/stores/app"; import { toLogin, checkLogin } from '@/libs/login' import { wxLogin, updateOpenId,getUserInfo } from "@/api/user.js"; import { useToast } from "@/hooks/useToast"; import Cache from "./cache"; import { USER_INFO, TOKEN } from "@/config/cache"; const { Toast } = useToast(); import { HTTP_REQUEST_URL, TOKENNAME } from '@/config/app'; // 全局状态管理:防止重复请求 let isLoggingIn = false; export function telEncrypt(tel = '') { let str = tel + ""; let enStr = str.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2") return enStr } export function uniLogin() { console.log('uniLogin', checkLogin()); if (checkLogin()) { const userInfo = Cache.get(USER_INFO) ? JSON.parse(Cache.get(USER_INFO)) : {}; if (userInfo.openId) return; uni.getProvider({ service: 'oauth', success: function(res) { console.log('getProvider', res.provider) if (~res.provider.indexOf('weixin')) { uni.login({ provider: 'weixin', success: async function(loginRes) { updateOpenId({ jsCode: loginRes.code }).then(res => { if (res.code == 200) { const appStore = useAppStore(); appStore.USERINFO(); } }); } }); } } }); } } /** * 统一登录方法:同时获取openid和手机号 * @param {Object} e - 微信获取手机号事件对象 * @param {Object} options - 配置选项 * @returns {Promise} */ export async function getPhoneNumber(e, options = {}) { return new Promise(async (resolve, reject) => { const appStore = useAppStore(); // 防重复点击 if (isLoggingIn) { // 移除了Toast,只在页面显示 reject(new Error('登录请求中,请稍候')); return; } isLoggingIn = true; try { // 用户拒绝授权 if (e.detail.errMsg !== 'getPhoneNumber:ok') { const errorCode = e.detail.errMsg; let errorMessage = '请授权手机号以完成登录'; if (errorCode.includes('deny')) { errorMessage = '您已拒绝授权,如需登录请重新授权'; } else if (errorCode.includes('timeout')) { errorMessage = '授权超时,请重试'; } else if (errorCode.includes('fail')) { errorMessage = '授权失败,请重试'; } // 移除了Toast,只在页面显示 // 执行失败回调 if (options.onFail) { options.onFail({ type: 'auth_denied', message: errorMessage }); } reject(new Error(errorMessage)); return; } // 授权成功 if (!e.detail.code) { // 移除了Toast,只在页面显示 if (options.onFail) { options.onFail({ type: 'auth_code_missing', message: '授权码获取失败' }); } reject(new Error('授权码获取失败')); return; } console.log('开始登录流程,手机号授权码:', e.detail.code.substring(0, 20) + '...'); // 方案1:先获取微信登录code,再调用后端统一接口 // 这种方式更推荐,因为可以保证两个code都是最新的 const result = await handleLoginWithPhoneCode(e.detail.code, options); // 执行成功回调 if (options.onSuccess) { options.onSuccess(result); } resolve(result); } catch (error) { console.error('登录失败:', error); // 处理特定错误 let errorMessage = '登录失败,请重试'; if (error.message && error.message.includes('超时')) { errorMessage = '请求超时,请检查网络'; } else if (error.code === 40029) { errorMessage = '授权码无效,请重新获取'; } else if (error.code === 40001) { errorMessage = '登录凭证已过期,请重试'; } else if (error.code === 401 || error.code === 40101) { errorMessage = '登录已过期,请重新登录'; } // 移除了Toast,只在页面显示 // 执行失败回调 if (options.onFail) { options.onFail({ type: 'login_failed', message: errorMessage, originalError: error.message || errorMessage }); } // 特殊错误处理:token过期 if (error.code === 401 || error.code === 40101) { // 清除本地token,让用户重新登录 Cache.remove(TOKEN); Cache.remove(USER_INFO); appStore.LOGOUT(); setTimeout(() => { uni.showModal({ title: '登录已过期', content: '您的登录状态已过期,请重新登录', showCancel: false, confirmText: '重新登录' }); }, 1000); } reject(error); } finally { isLoggingIn = false; } }); } /** * 处理登录和获取手机号 * 方案1:先获取微信登录code,再调用后端统一接口 */ async function handleLoginWithPhoneCode(phoneCode, options) { // 1. 先获取微信登录code const loginRes = await new Promise((resolve, reject) => { uni.login({ provider: 'weixin', timeout: 10000, success: resolve, fail: reject }); }); if (!loginRes.code) { throw new Error('获取登录凭证失败'); } console.log('获取到登录code:', loginRes.code.substring(0, 20) + '...'); // 2. 获取本地token(如果有的话) const localToken = Cache.get(TOKEN); // 3. 调用后端统一登录接口,同时传递登录code和手机号code const requestData = { jsCode: loginRes.code, // 微信登录code,用于获取openid code: phoneCode, // 手机号授权code timestamp: Date.now() // 防止缓存 }; // 如果有本地token,也带上(用于关联已有账号) if (localToken) { requestData.oldToken = localToken; } console.log('发送登录请求,数据:', { ...requestData, phoneCode: '已隐藏' }); // 4. 调用后端接口 const res = await wxLogin(requestData); console.log('登录接口返回:', res); // 5. 处理后端响应 if (res.code === 200) { const appStore = useAppStore(); // 保存token appStore.UPDATE_TOKEN(res.data.access_token || res.data); // 获取用户信息 await appStore.USERINFO(); // 移除了Toast,只在页面显示 // 触发登录成功事件 uni.$emit('loginSuccess'); // 登录成功后的处理(例如跳转) await handleAfterLoginSuccess(options); return { success: true, data: res.data, message: res.msg || '登录成功', userInfo: appStore.userInfo }; } else { // 处理业务错误 const error = { code: res.code, message: res.msg || res.data?.message || '登录失败', data: res.data }; throw error; } } /** * 登录成功后的处理 */ async function handleAfterLoginSuccess(options) { try { // 如果有回调URL,则跳转 if (options.redirectUrl) { setTimeout(() => { if (options.redirectUrl.startsWith('/')) { uni.redirectTo({ url: options.redirectUrl }); } else { uni.switchTab({ url: options.redirectUrl }); } }, 1500); return; } // 默认跳转逻辑 const pages = getCurrentPages(); const currentRoute = pages[pages.length - 1]?.route || ''; // 如果当前在个人中心页面,刷新页面即可 if (currentRoute.includes('mine')) { // 可以触发页面刷新 const eventChannel = pages[pages.length - 1]; if (eventChannel && eventChannel.onLoad) { eventChannel.onLoad(); } } else { // 其他页面跳转到个人中心 setTimeout(() => { uni.switchTab({ url: '/pages/mine/mine' }); }, 1500); } } catch (error) { console.error('登录后处理失败:', error); } } /** * 快捷登录方法(适用于页面直接调用) * @param {Object} e - 微信获取手机号事件对象 * @param {Object} customOptions - 自定义配置选项 * @returns {Promise} */ export function quickLogin(e, customOptions = {}) { const defaultOptions = { onSuccess: (result) => { console.log('登录成功,用户数据:', result); }, onFail: (error) => { console.log('登录失败:', error); }, redirectUrl: '/pages/mine/mine' }; const options = { ...defaultOptions, ...customOptions }; return getPhoneNumber(e, options); } //model是ture,属于重新定位,直接更新位置 export function getLocation(model=true,callback) { uni.getLocation({ type: 'wgs84', geocode: true, success: function (res) { // console.log('当前位置的经度:' + res.longitude); // console.log('当前位置的纬度:' + res.latitude); reverseGeocoder(res.latitude, res.longitude, model,callback); }, complete: function (res) { // console.log('getLocation-complete:' ,res); // const appStore = useAppStore(); // appStore.UPDATE_CITY("洛阳市") } }); } // 逆地理编码示例(使用腾讯地图) export function reverseGeocoder(latitude, longitude, model,callback) { getCityInfo({latitude,longitude}).then((res)=>{ console.log('reverseGeocoder-res:' ,res); if (res.code === 200){ const appStore = useAppStore(); if(model){ appStore.UPDATE_CITYINFO(res.data); callback(); }else if(res.data?.name && res.data.name != appStore.cityInfo?.name){ uni.showModal({ title: '提示', content: '你当前的城市有更新,是否切换到'+ res.data.name, success: function (e) { if (e.confirm) { appStore.UPDATE_CITYINFO(res.data) } else if (e.cancel) { console.log('用户点击取消'); } } }); } } }) } export function getNavbarHeight() { const appStore = useAppStore(); // 获取系统信息 const sysInfo = uni.getSystemInfoSync() // 状态栏高度(不同设备不一致) appStore.UPDATE_statusBarHeight(sysInfo.statusBarHeight) // 导航栏总高度 = 状态栏高度 + 自定义导航内容高度(通常 44px) // navbarHeight.value = statusBarHeight.value + 44; } export function setClipboardData(data="") { uni.setClipboardData({ data, success: function () { Toast({ title: "复制成功" }); } }); } export function checkLoginShowModal() { return new Promise((resolve, reject) => { if (!checkLogin()) { uni.showModal({ title: '温馨提示', content: '登录后将享受更多优质权益,请先登录', // showCancel:false, success: function (res) { if (res.confirm) { //跳转到我的页 uni.switchTab({ url: "/pages/mine/mine", }); } else if (res.cancel) { console.log('用户点击取消'); } resolve(false); } }); }else { resolve(true); } }) } // 检测今日免费提问次数 export function checkAiQuotaDailyModal() { return new Promise((resolve, reject) => { const appStore = useAppStore(); if (appStore.userInfo?.aiQuotaDaily <= 0) { let content = ''; if(appStore.userInfo?.rechargeBalance <= 0){ content = ' AI调用次数不足且余额不足,请充值或明日再试'; }else if(appStore.userInfo?.rechargeBalance > 0){ if(appStore.useBalance){ resolve(true); return; } content = '当日免费ai调用次数已用完,是否使用晓豆进行支付'; } uni.showModal({ title: '温馨提示', content, // showCancel:false, success: function (res) { if (res.confirm) { if(appStore.userInfo?.rechargeBalance <= 0){ //跳转到充值页 uni.navigateTo({ url: "/pages/recharge/recharge", }); }else{ appStore.useBalance = true; resolve(true); } } else if (res.cancel) { console.log('用户点击取消'); } resolve(false); } }); }else { resolve(true); } }) } // 方法:按指定key将一维对象数组转为二维数组 export function groupByKey(arr, key) { // 创建一个临时对象用于分组 const groupObj = {}; // 遍历原数组 arr.forEach(item => { // 获取当前项的key值作为分组标识 const groupKey = item[key]; // 如果该分组不存在,则初始化一个空数组 if (!groupObj[groupKey]) { groupObj[groupKey] = []; } // 将当前项添加到对应的分组中 groupObj[groupKey].push(item); }); // 将对象的值转换为数组,得到二维数组 return Object.values(groupObj); } // 支付 export function wxPay({timeStamp,nonceStr,packageVal,signType,paySign},callback) { uni.requestPayment({ provider: 'wxpay', timeStamp, nonceStr, package:packageVal, signType, paySign, success: function (res) { console.log('wxPay-success:' + JSON.stringify(res)); callback({isSuccess:1}); }, fail: function (err) { console.log('wxPay-fail:' + JSON.stringify(err)); Toast({ title: "支付失败" }); callback({isSuccess:0}); } }); } /** * 计算两点之间的直线距离(Haversine公式) * @param {Number} lat1 起点纬度 * @param {Number} lon1 起点经度 * @param {Number} lat2 终点纬度 * @param {Number} lon2 终点经度 * @returns {Number} 距离(千米) */ export function calculateDistance (lat1, lon1, lat2, lon2) { // 将角度转换为弧度 const radLat1 = (lat1 * Math.PI) / 180; const radLat2 = (lat2 * Math.PI) / 180; const a = radLat1 - radLat2; const b = (lon1 * Math.PI) / 180 - (lon2 * Math.PI) / 180; // Haversine公式计算距离(地球半径取6371千米) let s = 2 * Math.asin( Math.sqrt( Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2) ) ); s = s * 6371; // 地球半径(千米) return s.toFixed(2); }; // 获取邀请码 export function getSceneInfo (e) { console.log("getSceneInfo",e) if(e.scene){ const decodedScene = decodeURIComponent(e.scene); // 2. 分割参数(格式如:key1=value1&key2=value2) const params = {}; if (decodedScene) { decodedScene.split('&').forEach(item => { const [key, value] = item.split('='); if (key && value) { params[key] = value; } }); } console.log("getSceneInfo-params",params) // 邀请码 const appStore = useAppStore(); if(params.inviteCode)appStore.UPDATE_inviteCode(params.inviteCode); return params; } }; //获取客户信息 export function getUserInfo (e) { const appStore = useAppStore(); if(Cache.get(TOKEN))appStore.USERINFO(); }; export async function chooseImageOne(apiUrl="chat/file/imageUpload") { return new Promise((resolve, reject) => { uni.chooseImage({ count: 1, success: (chooseImageRes) => { const tempFilePaths = chooseImageRes.tempFilePaths; console.log("chooseImageRes",chooseImageRes); const appStore = useAppStore(); uni.uploadFile({ url: `${HTTP_REQUEST_URL}/mini/${apiUrl}`, //仅为示例,非真实的接口地址 filePath: tempFilePaths[0], name: 'file', header: { [TOKENNAME]: appStore.token, }, success: (uploadFileRes) => { console.log('uni.uploadFile',uploadFileRes); const data = JSON.parse(uploadFileRes.data); if(data.code==200){ resolve(data); }else{ reject(data); Toast({ title: "图片上传失败" }); } } }); } }); }) } export async function uploadFile(filePath,apiUrl="chat/file/imageUpload") { return new Promise((resolve, reject) => { const appStore = useAppStore(); uni.uploadFile({ url: `${HTTP_REQUEST_URL}/mini/${apiUrl}`, //仅为示例,非真实的接口地址 filePath, name: 'file', header: { [TOKENNAME]: appStore.token, }, success: (uploadFileRes) => { console.log('uni.uploadFile',uploadFileRes); const data = JSON.parse(uploadFileRes.data); if(data.code==200){ resolve(data); }else{ reject(data); Toast({ title: "图片上传失败" }); } } }); }) }