2026-01-15 18:03:13 +08:00
|
|
|
|
<script setup>
|
|
|
|
|
|
import { RouterView, useRouter, useRoute } from "vue-router";
|
|
|
|
|
|
const { isWeChat } = useEnv();
|
|
|
|
|
|
import { useAgentStore } from "@/stores/agentStore";
|
|
|
|
|
|
import { useUserStore } from "@/stores/userStore";
|
|
|
|
|
|
import { useDialogStore } from "@/stores/dialogStore";
|
|
|
|
|
|
import { useAuthStore } from "@/stores/authStore";
|
|
|
|
|
|
import { useAppStore } from "@/stores/appStore";
|
|
|
|
|
|
import { useWeixinShare } from "@/composables/useWeixinShare";
|
|
|
|
|
|
import BindPhoneDialog from "@/components/BindPhoneDialog.vue";
|
|
|
|
|
|
import BindPhoneOnlyDialog from "@/components/BindPhoneOnlyDialog.vue";
|
2026-01-17 13:15:30 +08:00
|
|
|
|
import WechatOverlay from "@/components/WechatOverlay.vue";
|
2026-01-15 18:03:13 +08:00
|
|
|
|
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
|
const agentStore = useAgentStore();
|
|
|
|
|
|
const userStore = useUserStore();
|
|
|
|
|
|
const dialogStore = useDialogStore();
|
|
|
|
|
|
const authStore = useAuthStore();
|
|
|
|
|
|
const appStore = useAppStore();
|
|
|
|
|
|
const { setDynamicShare } = useWeixinShare();
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
// 初始化应用配置
|
|
|
|
|
|
await appStore.fetchAppConfig();
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复微信授权状态(页面刷新或回调时)
|
|
|
|
|
|
authStore.restoreFromStorage();
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否是微信授权回调
|
|
|
|
|
|
const url = new URL(window.location.href);
|
|
|
|
|
|
const code = url.searchParams.get("code");
|
|
|
|
|
|
const state = url.searchParams.get("state");
|
|
|
|
|
|
|
|
|
|
|
|
if (code && state) {
|
|
|
|
|
|
// 这是微信授权回调,处理授权结果
|
|
|
|
|
|
console.log("Handling WeChat auth callback");
|
|
|
|
|
|
await handleWeixinAuthCallback(code);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 正常初始化:加载用户信息等
|
|
|
|
|
|
await initializeApp();
|
|
|
|
|
|
}
|
|
|
|
|
|
getWeixinAuthUrl();
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟配置微信分享
|
|
|
|
|
|
setTimeout(async () => {
|
|
|
|
|
|
if (isWeChat.value && window.jWeixin) {
|
|
|
|
|
|
await setDynamicShare();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
|
|
|
|
|
|
// 监听路由变化更新分享配置
|
|
|
|
|
|
router.afterEach(async () => {
|
|
|
|
|
|
if (isWeChat.value && window.jWeixin && !authStore.isWeixinAuthing) {
|
|
|
|
|
|
await setDynamicShare();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理微信授权回调
|
|
|
|
|
|
*/
|
|
|
|
|
|
const handleWeixinAuthCallback = async (code) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log("🔄 WeChat auth callback: code=", code);
|
|
|
|
|
|
|
|
|
|
|
|
// 调用后端接口交换 token
|
|
|
|
|
|
const { data, error } = await useApiFetch("/user/wxh5Auth")
|
|
|
|
|
|
.post({ code })
|
|
|
|
|
|
.json();
|
|
|
|
|
|
|
|
|
|
|
|
console.log("📡 wxh5Auth response:", {
|
|
|
|
|
|
code: data.value?.code,
|
|
|
|
|
|
hasToken: !!data.value?.data?.accessToken,
|
|
|
|
|
|
error: error.value
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (data.value && !error.value && data.value.code === 200) {
|
|
|
|
|
|
// 保存 token
|
|
|
|
|
|
const token = data.value.data.accessToken;
|
|
|
|
|
|
localStorage.setItem("token", token);
|
|
|
|
|
|
localStorage.setItem("refreshAfter", data.value.data.refreshAfter);
|
|
|
|
|
|
localStorage.setItem("accessExpire", data.value.data.accessExpire);
|
|
|
|
|
|
// ⚠️ 重要:保存 token 后立即设置 tokenVersion,防止被 checkTokenVersion 清除
|
|
|
|
|
|
const tokenVersion = import.meta.env.VITE_TOKEN_VERSION || "1.1";
|
|
|
|
|
|
localStorage.setItem("tokenVersion", tokenVersion);
|
|
|
|
|
|
|
|
|
|
|
|
console.log("✅ Token saved successfully, token:", token.substring(0, 20) + "...");
|
|
|
|
|
|
console.log("✅ Token saved to localStorage, userId:", data.value.data.userId || "unknown");
|
|
|
|
|
|
console.log(`✅ TokenVersion set to ${tokenVersion}`);
|
|
|
|
|
|
|
|
|
|
|
|
// 验证 token 是否真的保存成功
|
|
|
|
|
|
const savedToken = localStorage.getItem("token");
|
|
|
|
|
|
if (savedToken === token) {
|
|
|
|
|
|
console.log("✅ Token verification: localStorage中的token与保存的token一致");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error("❌ Token verification failed: localStorage中的token与保存的token不一致");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清除 URL 中的 code 和 state 参数
|
|
|
|
|
|
const url = new URL(window.location.href);
|
|
|
|
|
|
const params = new URLSearchParams(url.search);
|
|
|
|
|
|
params.delete("code");
|
|
|
|
|
|
params.delete("state");
|
|
|
|
|
|
const newUrl = `${url.origin}${url.pathname}${params.toString() ? "?" + params.toString() : ""}`;
|
|
|
|
|
|
window.history.replaceState({}, "", newUrl);
|
|
|
|
|
|
console.log("✅ URL cleaned, removed code and state parameters");
|
|
|
|
|
|
|
|
|
|
|
|
// 获取用户信息
|
|
|
|
|
|
console.log("👤 Fetching user info...");
|
|
|
|
|
|
try {
|
|
|
|
|
|
await userStore.fetchUserInfo();
|
|
|
|
|
|
console.log("✅ User info fetched:", {
|
|
|
|
|
|
mobile: userStore.mobile,
|
|
|
|
|
|
isLoggedIn: userStore.isLoggedIn
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (userInfoErr) {
|
|
|
|
|
|
console.error("❌ Failed to fetch user info:", userInfoErr);
|
|
|
|
|
|
// 用户信息获取失败,清除 token,跳转到登录
|
|
|
|
|
|
authStore.resetAuthState();
|
|
|
|
|
|
await router.replace("/login");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取代理信息(已登录用户都尝试获取,后端会判断是否是代理)
|
|
|
|
|
|
try {
|
|
|
|
|
|
await agentStore.fetchAgentStatus();
|
|
|
|
|
|
} catch (agentErr) {
|
|
|
|
|
|
console.warn("Warning: Failed to fetch agent status:", agentErr);
|
|
|
|
|
|
// 不中断流程,只是警告
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 标记授权完成
|
|
|
|
|
|
authStore.completeWeixinAuth();
|
|
|
|
|
|
console.log("✅ WeChat auth marked as complete");
|
|
|
|
|
|
|
|
|
|
|
|
// 获取 pendingRoute 并跳转
|
|
|
|
|
|
const pendingRoute = authStore.pendingRoute
|
|
|
|
|
|
console.log("🎯 pendingRoute:", pendingRoute);
|
|
|
|
|
|
|
|
|
|
|
|
// if (pendingRoute) {
|
|
|
|
|
|
// // ⚠️ 重要:必须先跳转再清除,否则清除后 pendingRoute 为 null
|
|
|
|
|
|
// console.log("🚀 Navigating to pendingRoute:", pendingRoute);
|
|
|
|
|
|
// await router.replace(pendingRoute);
|
|
|
|
|
|
// authStore.clearPendingRoute();
|
|
|
|
|
|
// console.log("✅ Navigated to pendingRoute and cleared it");
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// // 默认跳转到首页
|
|
|
|
|
|
// console.log("📍 No pendingRoute found, navigating to home");
|
|
|
|
|
|
// await router.replace("/");
|
|
|
|
|
|
// }
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error("❌ WeChat auth failed:", {
|
|
|
|
|
|
code: data.value?.code,
|
|
|
|
|
|
message: data.value?.msg || data.value?.message,
|
|
|
|
|
|
error: error.value
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 授权失败,重置状态
|
|
|
|
|
|
authStore.resetAuthState();
|
|
|
|
|
|
// 跳转到登录页
|
|
|
|
|
|
await router.replace("/login");
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error("❌ Error handling WeChat auth callback:", err);
|
|
|
|
|
|
authStore.resetAuthState();
|
|
|
|
|
|
await router.replace("/login");
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 初始化应用:检查 token,加载用户信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
const initializeApp = async () => {
|
|
|
|
|
|
// 检查 token 版本
|
|
|
|
|
|
checkTokenVersion();
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试刷新 token
|
|
|
|
|
|
await refreshTokenIfNeeded();
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有 token,加载用户信息和代理信息
|
|
|
|
|
|
const token = localStorage.getItem("token");
|
|
|
|
|
|
if (token) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await userStore.fetchUserInfo();
|
|
|
|
|
|
// 已登录用户都尝试获取代理信息,后端会判断是否是代理
|
|
|
|
|
|
await agentStore.fetchAgentStatus();
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error("Error loading user info:", err);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查 token 版本,清除不兼容的旧 token
|
|
|
|
|
|
* ⚠️ 注意:只有在确实需要清除旧 token 时才清除,避免误清除新保存的 token
|
|
|
|
|
|
*/
|
|
|
|
|
|
const checkTokenVersion = () => {
|
|
|
|
|
|
const CURRENT_TOKEN_VERSION = import.meta.env.VITE_TOKEN_VERSION || "1.1";
|
|
|
|
|
|
const storedVersion = localStorage.getItem("tokenVersion");
|
|
|
|
|
|
const hasToken = !!localStorage.getItem("token");
|
|
|
|
|
|
|
|
|
|
|
|
// 如果 tokenVersion 不存在或版本不匹配
|
|
|
|
|
|
if (!storedVersion || storedVersion !== CURRENT_TOKEN_VERSION) {
|
|
|
|
|
|
// 只有在有 token 的情况下才清除(避免清除刚保存的新 token)
|
|
|
|
|
|
if (hasToken) {
|
|
|
|
|
|
console.log(`Token version mismatch: storedVersion=${storedVersion}, currentVersion=${CURRENT_TOKEN_VERSION}, clearing old auth data`);
|
|
|
|
|
|
// Token 版本不匹配,清除旧数据
|
|
|
|
|
|
localStorage.removeItem("token");
|
|
|
|
|
|
localStorage.removeItem("refreshAfter");
|
|
|
|
|
|
localStorage.removeItem("accessExpire");
|
|
|
|
|
|
localStorage.removeItem("userInfo");
|
|
|
|
|
|
localStorage.removeItem("agentInfo");
|
|
|
|
|
|
}
|
|
|
|
|
|
// 无论是否有 token,都设置新的 tokenVersion
|
|
|
|
|
|
localStorage.setItem("tokenVersion", CURRENT_TOKEN_VERSION);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log(`Token version check passed: storedVersion=${storedVersion}, currentVersion=${CURRENT_TOKEN_VERSION}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 在需要时刷新 token
|
|
|
|
|
|
*/
|
|
|
|
|
|
const refreshTokenIfNeeded = async () => {
|
|
|
|
|
|
const token = localStorage.getItem("token");
|
|
|
|
|
|
if (!token) return;
|
|
|
|
|
|
|
|
|
|
|
|
const accessExpire = localStorage.getItem("accessExpire");
|
|
|
|
|
|
const refreshAfter = localStorage.getItem("refreshAfter");
|
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
|
|
|
|
|
|
|
// 检查 token 是否已过期
|
|
|
|
|
|
if (accessExpire) {
|
|
|
|
|
|
const expireTime = parseInt(accessExpire) * 1000;
|
|
|
|
|
|
if (now > expireTime) {
|
|
|
|
|
|
console.log("Token expired");
|
|
|
|
|
|
return; // Token 已过期,不刷新,由路由守卫处理
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否需要刷新
|
|
|
|
|
|
if (refreshAfter) {
|
|
|
|
|
|
const refreshTime = parseInt(refreshAfter) * 1000;
|
|
|
|
|
|
if (now < refreshTime) {
|
|
|
|
|
|
return; // 还不需要刷新
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 执行 token 刷新
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data, error } = await useApiFetch("/user/getToken").post().json();
|
|
|
|
|
|
if (data.value && !error.value && data.value.code === 200) {
|
|
|
|
|
|
localStorage.setItem("token", data.value.data.accessToken);
|
|
|
|
|
|
localStorage.setItem("refreshAfter", data.value.data.refreshAfter);
|
|
|
|
|
|
localStorage.setItem("accessExpire", data.value.data.accessExpire);
|
|
|
|
|
|
console.log("Token refreshed successfully");
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error("Error refreshing token:", err);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 发起微信授权 URL
|
|
|
|
|
|
* 这个逻辑已经在路由守卫中实现了
|
|
|
|
|
|
* 这里保留这个函数的备份,以防需要其他地方调用
|
|
|
|
|
|
*/
|
2026-01-17 13:15:30 +08:00
|
|
|
|
// const getWeixinAuthUrl = () => {
|
|
|
|
|
|
// const isAuthenticated = localStorage.getItem("token");
|
|
|
|
|
|
|
|
|
|
|
|
// // 检查 token 是否过期
|
|
|
|
|
|
// const accessExpire = localStorage.getItem("accessExpire");
|
|
|
|
|
|
// const now = Date.now();
|
|
|
|
|
|
// let isTokenExpired = false;
|
|
|
|
|
|
// if (accessExpire) {
|
|
|
|
|
|
// isTokenExpired = now > parseInt(accessExpire) * 1000;
|
|
|
|
|
|
// }
|
|
|
|
|
|
// console.log("WeChat auth check:", {
|
|
|
|
|
|
// isWeChat: isWeChat.value,
|
|
|
|
|
|
// isAuthenticated,
|
|
|
|
|
|
// isTokenExpired
|
|
|
|
|
|
// });
|
|
|
|
|
|
// if (isWeChat.value && !isAuthenticated && !isTokenExpired) {
|
|
|
|
|
|
// console.log("🔄 Initiating WeChat auth flow");
|
|
|
|
|
|
// // 如果正在授权中或已完成授权,则阻止重复授权
|
|
|
|
|
|
// console.log("Auth store state:", {
|
|
|
|
|
|
// isWeixinAuthing: authStore.isWeixinAuthing,
|
|
|
|
|
|
// weixinAuthComplete: authStore.weixinAuthComplete
|
|
|
|
|
|
// });
|
|
|
|
|
|
// if (authStore.isWeixinAuthing || authStore.weixinAuthComplete) {
|
|
|
|
|
|
// return;
|
|
|
|
|
|
// }
|
|
|
|
|
|
// // 保存目标路由
|
|
|
|
|
|
// authStore.startWeixinAuth(route);
|
|
|
|
|
|
// console.log("🔖 Saved pendingRoute for WeChat auth:", route.fullPath);
|
|
|
|
|
|
// const appId = import.meta.env.VITE_WECHAT_APP_ID;
|
|
|
|
|
|
// const url = new URL(window.location.href);
|
|
|
|
|
|
// const params = new URLSearchParams(url.search);
|
|
|
|
|
|
// params.delete("code");
|
|
|
|
|
|
// params.delete("state");
|
2026-01-16 16:33:03 +08:00
|
|
|
|
|
2026-01-17 13:15:30 +08:00
|
|
|
|
// // 使用配置的固定域名,如果没有配置则使用当前域名
|
|
|
|
|
|
// const redirectDomain = import.meta.env.VITE_WECHAT_REDIRECT_DOMAIN || url.origin;
|
|
|
|
|
|
// const cleanUrl = `${redirectDomain}${url.pathname}${params.toString() ? "?" + params.toString() : ""
|
|
|
|
|
|
// }`;
|
|
|
|
|
|
// const redirectUri = encodeURIComponent(cleanUrl);
|
2026-01-16 16:33:03 +08:00
|
|
|
|
|
2026-01-17 13:15:30 +08:00
|
|
|
|
// console.log("🔗 WeChat redirectUri config:", {
|
|
|
|
|
|
// configuredDomain: import.meta.env.VITE_WECHAT_REDIRECT_DOMAIN || "未配置(使用当前域名)",
|
|
|
|
|
|
// currentOrigin: url.origin,
|
|
|
|
|
|
// finalRedirectDomain: redirectDomain,
|
|
|
|
|
|
// redirectUri: cleanUrl
|
|
|
|
|
|
// });
|
|
|
|
|
|
// const weixinAuthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_base&state=snsapi_base#wechat_redirect`;
|
|
|
|
|
|
|
|
|
|
|
|
// console.log(
|
|
|
|
|
|
// "🔄 Triggering WeChat auth from route guard, pendingRoute:",
|
|
|
|
|
|
// route.fullPath
|
|
|
|
|
|
// );
|
|
|
|
|
|
// window.location.href = weixinAuthUrl;
|
|
|
|
|
|
// }
|
|
|
|
|
|
// };
|
2026-01-15 18:03:13 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<RouterView />
|
|
|
|
|
|
<BindPhoneDialog />
|
2026-01-17 13:15:30 +08:00
|
|
|
|
<WechatOverlay />
|
2026-01-15 18:03:13 +08:00
|
|
|
|
<BindPhoneOnlyDialog />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped></style>
|