2026-07-02 18:25:23 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view class="loading-page">
|
|
|
|
|
|
<view class="loading-spinner"></view>
|
|
|
|
|
|
<text class="loading-text">{{ statusText }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
import config from '@/config/env.js';
|
|
|
|
|
|
import { gatewayGet, gatewayPost } from '@/utils/request.js';
|
|
|
|
|
|
import { detectPlatform, getAppNo } from '@/utils/env.js';
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
statusText: '正在登录...',
|
|
|
|
|
|
deviceId: '',
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
onLoad(options) {
|
|
|
|
|
|
this.deviceId = options.device_id || '';
|
|
|
|
|
|
|
|
|
|
|
|
// #ifdef H5
|
2026-07-09 18:49:56 +08:00
|
|
|
|
// Mock 模式:跳过微信 OAuth,直接登录
|
|
|
|
|
|
if (config.mockWechatLogin) {
|
|
|
|
|
|
this.mockLogin();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-02 19:32:07 +08:00
|
|
|
|
// 优先从页面参数获取 code(App.vue 跳转时带过来)
|
|
|
|
|
|
// 兜底从 URL search 参数获取(微信直接回调时)
|
|
|
|
|
|
let code = options.code || '';
|
|
|
|
|
|
if (!code) {
|
|
|
|
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
|
|
|
|
code = urlParams.get('code') || '';
|
|
|
|
|
|
}
|
2026-07-02 18:25:23 +08:00
|
|
|
|
|
|
|
|
|
|
if (code) {
|
|
|
|
|
|
// 有 code → 调用后端登录
|
|
|
|
|
|
this.handleCallback(code);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 无 code → 发起 OAuth 授权
|
|
|
|
|
|
this.startAuth();
|
|
|
|
|
|
}
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
|
|
|
|
|
|
// #ifndef H5
|
|
|
|
|
|
// 小程序环境暂不支持,跳回扫码页
|
|
|
|
|
|
uni.redirectTo({ url: '/pages/index/scan' });
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 发起 OAuth 授权
|
|
|
|
|
|
*/
|
|
|
|
|
|
async startAuth() {
|
|
|
|
|
|
const platform = detectPlatform();
|
|
|
|
|
|
const appNo = getAppNo(platform);
|
|
|
|
|
|
|
|
|
|
|
|
if (!appNo) {
|
|
|
|
|
|
// 非微信环境,跳转注册页(手机号注册)
|
|
|
|
|
|
this.goToRegister();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.statusText = '正在获取授权...';
|
|
|
|
|
|
|
2026-07-02 19:04:08 +08:00
|
|
|
|
// 回调地址必须是 HTTPS,使用配置文件中的地址
|
|
|
|
|
|
const redirectUri = config.authRedirectUri || (
|
|
|
|
|
|
window.location.origin.replace(/^http:/, 'https:') + window.location.pathname
|
2026-07-02 18:25:23 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const res = await gatewayGet('/api/v1/user/auth/url', {
|
|
|
|
|
|
app_no: appNo,
|
|
|
|
|
|
scopes: 'snsapi_userinfo',
|
|
|
|
|
|
redirect_uri: redirectUri,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const data = res.data || {};
|
|
|
|
|
|
if (data.code === 0 && data.data && data.data.auth_url) {
|
|
|
|
|
|
// 跳转微信授权页
|
|
|
|
|
|
window.location.href = data.data.auth_url;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
uni.showToast({ title: data.message || '获取授权失败', icon: 'none' });
|
|
|
|
|
|
setTimeout(() => this.goToScan(), 1500);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('[Auth] startAuth error:', e);
|
|
|
|
|
|
uni.showToast({ title: '网络请求失败', icon: 'none' });
|
|
|
|
|
|
setTimeout(() => this.goToScan(), 1500);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理授权回调(从 URL 中获取 code 后调用后端 /login)
|
|
|
|
|
|
*/
|
|
|
|
|
|
async handleCallback(code) {
|
|
|
|
|
|
const appNo = uni.getStorageSync('app_no') || getAppNo(detectPlatform());
|
|
|
|
|
|
|
|
|
|
|
|
if (!appNo) {
|
|
|
|
|
|
uni.showToast({ title: '无法获取应用信息', icon: 'none' });
|
|
|
|
|
|
this.goToScan();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
this.statusText = '正在验证身份...';
|
|
|
|
|
|
|
|
|
|
|
|
const res = await gatewayPost('/api/v1/user/login', {
|
|
|
|
|
|
app_no: appNo,
|
|
|
|
|
|
code: code,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const data = res.data || {};
|
|
|
|
|
|
if (data.code === 0 && data.data) {
|
|
|
|
|
|
const result = data.data;
|
|
|
|
|
|
|
|
|
|
|
|
// 清除 URL 中的 code 参数,避免刷新重复处理
|
|
|
|
|
|
const url = new URL(window.location.href);
|
|
|
|
|
|
url.searchParams.delete('code');
|
|
|
|
|
|
url.searchParams.delete('state');
|
|
|
|
|
|
window.history.replaceState({}, '', url.toString());
|
|
|
|
|
|
|
|
|
|
|
|
if (result.register) {
|
|
|
|
|
|
// 已注册 → 缓存 JWT → 跳转首页
|
|
|
|
|
|
uni.setStorageSync('token', result.token);
|
|
|
|
|
|
uni.setStorageSync('user_info', result.user_info);
|
|
|
|
|
|
this.goToHome();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 未注册 → 缓存临时数据 → 跳转注册页
|
|
|
|
|
|
uni.setStorageSync('temp_token', result.temp_token);
|
|
|
|
|
|
uni.setStorageSync('auth_info', result.auth_info);
|
|
|
|
|
|
this.goToRegister();
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
uni.showToast({ title: data.message || '登录失败', icon: 'none' });
|
|
|
|
|
|
setTimeout(() => this.goToScan(), 1500);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('[Auth] login error:', e);
|
|
|
|
|
|
uni.showToast({ title: '网络请求失败', icon: 'none' });
|
|
|
|
|
|
setTimeout(() => this.goToScan(), 1500);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
goToHome() {
|
|
|
|
|
|
const url = this.deviceId
|
|
|
|
|
|
? `/pages/index/index?device_id=${this.deviceId}`
|
|
|
|
|
|
: '/pages/index/index';
|
|
|
|
|
|
uni.redirectTo({ url });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
goToRegister() {
|
|
|
|
|
|
const url = this.deviceId
|
|
|
|
|
|
? `/pages/register/register?device_id=${this.deviceId}`
|
|
|
|
|
|
: '/pages/register/register';
|
|
|
|
|
|
uni.redirectTo({ url });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
goToScan() {
|
|
|
|
|
|
uni.redirectTo({ url: '/pages/index/scan' });
|
|
|
|
|
|
},
|
2026-07-09 18:49:56 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Mock 登录(开发环境模拟不同用户状态)
|
|
|
|
|
|
*/
|
|
|
|
|
|
async mockLogin() {
|
|
|
|
|
|
const mode = config.mockWechatLogin;
|
|
|
|
|
|
const mock = config.mockUser;
|
|
|
|
|
|
|
|
|
|
|
|
if (mode === 'register' || mode === 'login') {
|
|
|
|
|
|
// 调用真实登录接口,用测试 code
|
|
|
|
|
|
this.statusText = '模拟:获取授权信息...';
|
|
|
|
|
|
const appNo = uni.getStorageSync('app_no') || getAppNo(detectPlatform()) || config.mockAppNo;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await gatewayPost('/api/v1/user/login', {
|
|
|
|
|
|
app_no: appNo,
|
|
|
|
|
|
code: 'test_code',
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const data = res.data || {};
|
|
|
|
|
|
if (data.code === 0 && data.data) {
|
|
|
|
|
|
const result = data.data;
|
|
|
|
|
|
|
|
|
|
|
|
if (result.register) {
|
|
|
|
|
|
// 已注册
|
|
|
|
|
|
uni.setStorageSync('token', result.token);
|
|
|
|
|
|
uni.setStorageSync('user_info', result.user_info);
|
|
|
|
|
|
console.log('[Auth] Mock: existing user → home');
|
|
|
|
|
|
this.goToHome();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 未注册 → 存真实的 temp_token,合并 mock 头像昵称
|
|
|
|
|
|
const authInfo = {
|
|
|
|
|
|
...mock.user_info,
|
|
|
|
|
|
...result.auth_info,
|
|
|
|
|
|
};
|
|
|
|
|
|
uni.setStorageSync('temp_token', result.temp_token);
|
|
|
|
|
|
uni.setStorageSync('auth_info', authInfo);
|
|
|
|
|
|
console.log('[Auth] Mock: new user → register, temp_token:', result.temp_token);
|
|
|
|
|
|
this.goToRegister();
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
uni.showToast({ title: data.message || '登录接口返回异常', icon: 'none' });
|
|
|
|
|
|
console.error('[Auth] Mock login failed:', data);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('[Auth] Mock login error:', e);
|
|
|
|
|
|
uni.showToast({ title: '网络请求失败', icon: 'none' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 'skip' → 直接登录
|
|
|
|
|
|
this.statusText = '模拟登录中...';
|
|
|
|
|
|
uni.setStorageSync('token', mock.token);
|
|
|
|
|
|
uni.setStorageSync('user_info', mock.user_info);
|
|
|
|
|
|
console.log('[Auth] Mock: skip → home');
|
|
|
|
|
|
setTimeout(() => this.goToHome(), 500);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-07-02 18:25:23 +08:00
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
page {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading-page {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
height: 100vh;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading-spinner {
|
|
|
|
|
|
width: 64rpx;
|
|
|
|
|
|
height: 64rpx;
|
|
|
|
|
|
border: 6rpx solid #e5e5e5;
|
|
|
|
|
|
border-top-color: #2979ff;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
animation: spin 0.8s linear infinite;
|
|
|
|
|
|
margin-bottom: 32rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes spin {
|
|
|
|
|
|
to {
|
|
|
|
|
|
transform: rotate(360deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading-text {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|