| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- 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: "图片上传失败" });
- }
- }
- });
- })
- }
|