feat: 完善微信登录
This commit is contained in:
@@ -85,7 +85,8 @@
|
|||||||
"Bash(taskkill //F //PID 1716)",
|
"Bash(taskkill //F //PID 1716)",
|
||||||
"Bash(mysql -u root -p123456 rent_platform -e \"SELECT id, name, type, enabled, config FROM mkt_activities WHERE type='invite_cashback' LIMIT 1\")",
|
"Bash(mysql -u root -p123456 rent_platform -e \"SELECT id, name, type, enabled, config FROM mkt_activities WHERE type='invite_cashback' LIMIT 1\")",
|
||||||
"Bash(node check-config.js)",
|
"Bash(node check-config.js)",
|
||||||
"Bash(node verify-fix.js)"
|
"Bash(node verify-fix.js)",
|
||||||
|
"Bash(ipconfig)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# H5 开发环境接口
|
# H5 开发环境接口
|
||||||
VITE_H5_API_BASE_URL=http://localhost:3000
|
VITE_H5_API_BASE_URL=http://localhost:3000
|
||||||
|
|
||||||
# 小程序开发环境接口
|
# 小程序开发环境接口(真机调试使用局域网IP)
|
||||||
VITE_MP_API_BASE_URL=http://localhost:3000
|
VITE_MP_API_BASE_URL=http://192.168.0.111:3000
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ function handleTap(key: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tool-label {
|
.tool-label {
|
||||||
font-size: $font-xs;
|
font-size: $font-base;
|
||||||
color: $text-secondary;
|
color: $text-secondary;
|
||||||
font-weight: $font-medium;
|
font-weight: $font-medium;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"versionCode": "100",
|
"versionCode": "100",
|
||||||
"transformPx": false,
|
"transformPx": false,
|
||||||
"mp-weixin": {
|
"mp-weixin": {
|
||||||
"appid": "",
|
"appid": "wx6b2d69c900f8f93a",
|
||||||
"setting": {
|
"setting": {
|
||||||
"urlCheck": false,
|
"urlCheck": false,
|
||||||
"es6": true,
|
"es6": true,
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page-cashbacks">
|
<view class="page-cashbacks">
|
||||||
<!-- 自定义导航栏 -->
|
|
||||||
<view class="custom-navbar">
|
|
||||||
<view class="navbar-content">
|
|
||||||
<view class="navbar-left" @tap="goBack">
|
|
||||||
<u-icon name="arrow-left" :size="20" color="#1A1A1A" />
|
|
||||||
</view>
|
|
||||||
<text class="navbar-title">返现明细</text>
|
|
||||||
<view class="navbar-right"></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 顶部统计卡片 -->
|
<!-- 顶部统计卡片 -->
|
||||||
<view class="stats-header">
|
<view class="stats-header">
|
||||||
<view class="stats-main">
|
<view class="stats-main">
|
||||||
@@ -319,46 +308,6 @@ function goBack() {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========== 自定义导航栏 ========== */
|
|
||||||
.custom-navbar {
|
|
||||||
background: #ffffff;
|
|
||||||
padding: 0 24rpx;
|
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-content {
|
|
||||||
height: 88rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-left,
|
|
||||||
.navbar-right {
|
|
||||||
width: 80rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-left {
|
|
||||||
justify-content: flex-start;
|
|
||||||
padding: 12rpx;
|
|
||||||
margin-left: -12rpx;
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-title {
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1A1A1A;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========== 顶部统计卡片 ========== */
|
/* ========== 顶部统计卡片 ========== */
|
||||||
.stats-header {
|
.stats-header {
|
||||||
margin: 24rpx 24rpx 16rpx;
|
margin: 24rpx 24rpx 16rpx;
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page-records">
|
<view class="page-records">
|
||||||
<!-- 自定义导航栏 -->
|
|
||||||
<view class="custom-navbar">
|
|
||||||
<view class="navbar-content">
|
|
||||||
<view class="navbar-left" @tap="goBack">
|
|
||||||
<u-icon name="arrow-left" :size="20" color="#1A1A1A" />
|
|
||||||
</view>
|
|
||||||
<text class="navbar-title">我的邀请</text>
|
|
||||||
<view class="navbar-right"></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 顶部统计卡片 -->
|
<!-- 顶部统计卡片 -->
|
||||||
<view class="stats-banner">
|
<view class="stats-banner">
|
||||||
<view class="stats-item">
|
<view class="stats-item">
|
||||||
@@ -279,46 +268,6 @@ function goBack() {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========== 自定义导航栏 ========== */
|
|
||||||
.custom-navbar {
|
|
||||||
background: #ffffff;
|
|
||||||
padding: 0 24rpx;
|
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-content {
|
|
||||||
height: 88rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-left,
|
|
||||||
.navbar-right {
|
|
||||||
width: 80rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-left {
|
|
||||||
justify-content: flex-start;
|
|
||||||
padding: 12rpx;
|
|
||||||
margin-left: -12rpx;
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-title {
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1A1A1A;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========== 顶部统计横幅 ========== */
|
/* ========== 顶部统计横幅 ========== */
|
||||||
.stats-banner {
|
.stats-banner {
|
||||||
margin: 24rpx 24rpx 16rpx;
|
margin: 24rpx 24rpx 16rpx;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
<!-- 一键登录按钮(微信获取手机号) -->
|
<!-- 一键登录按钮(微信获取手机号) -->
|
||||||
<button
|
<button
|
||||||
v-if="isWechatMiniProgram"
|
v-if="isWechatMiniProgram && agreed"
|
||||||
class="quick-login-button"
|
class="quick-login-button"
|
||||||
open-type="getPhoneNumber"
|
open-type="getPhoneNumber"
|
||||||
@getphonenumber="handleQuickLogin"
|
@getphonenumber="handleQuickLogin"
|
||||||
@@ -31,6 +31,16 @@
|
|||||||
一键登录
|
一键登录
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- 未同意协议时显示的普通按钮 -->
|
||||||
|
<button
|
||||||
|
v-if="isWechatMiniProgram && !agreed"
|
||||||
|
class="quick-login-button"
|
||||||
|
@tap="handleQuickLoginCheck"
|
||||||
|
>
|
||||||
|
<u-icon name="phone-fill" :size="24" color="#fff" style="margin-right: 12rpx;" />
|
||||||
|
一键登录
|
||||||
|
</button>
|
||||||
|
|
||||||
<!-- 手机号安全登录按钮 -->
|
<!-- 手机号安全登录按钮 -->
|
||||||
<button class="phone-login-button" @tap="handleShowPhoneModal">
|
<button class="phone-login-button" @tap="handleShowPhoneModal">
|
||||||
<u-icon name="lock-fill" :size="24" color="#fff" style="margin-right: 12rpx;" />
|
<u-icon name="lock-fill" :size="24" color="#fff" style="margin-right: 12rpx;" />
|
||||||
@@ -76,7 +86,23 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="modal-footer">
|
<view class="modal-footer">
|
||||||
<button class="modal-button cancel" @tap="closeAgreementModal">取消</button>
|
<button class="modal-button cancel" @tap="closeAgreementModal">取消</button>
|
||||||
<button class="modal-button confirm" @tap="confirmAgreement">确定</button>
|
<!-- 如果是微信一键登录触发的弹窗,确定按钮带微信授权 -->
|
||||||
|
<button
|
||||||
|
v-if="isWechatMiniProgram && pendingLoginType === 'wechat'"
|
||||||
|
class="modal-button confirm"
|
||||||
|
open-type="getPhoneNumber"
|
||||||
|
@getphonenumber="handleQuickLoginAfterAgreement"
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</button>
|
||||||
|
<!-- 其他情况的普通确定按钮 -->
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
class="modal-button confirm"
|
||||||
|
@tap="confirmAgreement"
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -147,6 +173,7 @@ const agreed = ref(false);
|
|||||||
const isWechatMiniProgram = ref(false);
|
const isWechatMiniProgram = ref(false);
|
||||||
const showAgreementModal = ref(false);
|
const showAgreementModal = ref(false);
|
||||||
const showPhoneModal = ref(false);
|
const showPhoneModal = ref(false);
|
||||||
|
const pendingLoginType = ref<'wechat' | 'phone' | null>(null);
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
let timer: ReturnType<typeof setInterval> | null = null;
|
let timer: ReturnType<typeof setInterval> | null = null;
|
||||||
@@ -176,6 +203,7 @@ function checkAgreement(action: () => void) {
|
|||||||
function closeAgreementModal() {
|
function closeAgreementModal() {
|
||||||
showAgreementModal.value = false;
|
showAgreementModal.value = false;
|
||||||
pendingAction = null;
|
pendingAction = null;
|
||||||
|
pendingLoginType.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确认协议
|
// 确认协议
|
||||||
@@ -186,54 +214,82 @@ function confirmAgreement() {
|
|||||||
pendingAction();
|
pendingAction();
|
||||||
pendingAction = null;
|
pendingAction = null;
|
||||||
}
|
}
|
||||||
|
pendingLoginType.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 一键登录检查协议
|
||||||
|
function handleQuickLoginCheck() {
|
||||||
|
if (!agreed.value) {
|
||||||
|
pendingLoginType.value = 'wechat';
|
||||||
|
showAgreementModal.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同意协议后的微信一键登录
|
||||||
|
async function handleQuickLoginAfterAgreement(e: any) {
|
||||||
|
agreed.value = true;
|
||||||
|
showAgreementModal.value = false;
|
||||||
|
pendingLoginType.value = null;
|
||||||
|
await handleQuickLogin(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 一键登录(微信获取手机号)
|
// 一键登录(微信获取手机号)
|
||||||
async function handleQuickLogin(e: any) {
|
async function handleQuickLogin(e: any) {
|
||||||
const action = async () => {
|
try {
|
||||||
try {
|
const { code: phoneCode, encryptedData, iv } = e.detail;
|
||||||
const { code, encryptedData, iv } = e.detail;
|
|
||||||
|
|
||||||
if (!code) {
|
console.log('微信手机号授权返回:', { phoneCode, encryptedData, iv });
|
||||||
uni.showToast({ title: '获取手机号失败', icon: 'none' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showLoading({ title: '登录中...' });
|
if (!phoneCode) {
|
||||||
|
uni.showToast({ title: '获取手机号失败', icon: 'none' });
|
||||||
// 获取待绑定的邀请码
|
return;
|
||||||
const inviteCode = uni.getStorageSync('pendingInviteCode');
|
|
||||||
|
|
||||||
// 调用后端接口,使用微信手机号登录
|
|
||||||
const res = await loginByWechatPhone(code, encryptedData, iv, inviteCode);
|
|
||||||
|
|
||||||
// 登录成功后清除邀请码
|
|
||||||
if (inviteCode) {
|
|
||||||
uni.removeStorageSync('pendingInviteCode');
|
|
||||||
}
|
|
||||||
|
|
||||||
userStore.setLogin(res.data);
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({ title: '登录成功', icon: 'success' });
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
uni.switchTab({ url: '/pages/index/index' });
|
|
||||||
}, 500);
|
|
||||||
} catch (error: any) {
|
|
||||||
uni.hideLoading();
|
|
||||||
console.error('一键登录失败', error);
|
|
||||||
uni.showToast({
|
|
||||||
title: error.message || '登录失败,请重试',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if (!checkAgreement(action)) {
|
uni.showLoading({ title: '登录中...' });
|
||||||
return;
|
|
||||||
|
// 先调用 wx.login 获取登录 code
|
||||||
|
const loginRes = await new Promise<any>((resolve, reject) => {
|
||||||
|
uni.login({
|
||||||
|
provider: 'weixin',
|
||||||
|
success: resolve,
|
||||||
|
fail: reject,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!loginRes.code) {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({ title: '获取登录凭证失败', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('wx.login 返回的 code:', loginRes.code);
|
||||||
|
|
||||||
|
// 获取待绑定的邀请码
|
||||||
|
const inviteCode = uni.getStorageSync('pendingInviteCode');
|
||||||
|
|
||||||
|
// 调用后端接口,使用微信手机号登录
|
||||||
|
const res = await loginByWechatPhone(loginRes.code, encryptedData, iv, inviteCode);
|
||||||
|
|
||||||
|
// 登录成功后清除邀请码
|
||||||
|
if (inviteCode) {
|
||||||
|
uni.removeStorageSync('pendingInviteCode');
|
||||||
|
}
|
||||||
|
|
||||||
|
userStore.setLogin(res.data);
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({ title: '登录成功', icon: 'success' });
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.switchTab({ url: '/pages/index/index' });
|
||||||
|
}, 500);
|
||||||
|
} catch (error: any) {
|
||||||
|
uni.hideLoading();
|
||||||
|
console.error('一键登录失败', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || '登录失败,请重试',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await action();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示手机号登录弹窗
|
// 显示手机号登录弹窗
|
||||||
@@ -243,6 +299,7 @@ function handleShowPhoneModal() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!checkAgreement(action)) {
|
if (!checkAgreement(action)) {
|
||||||
|
pendingLoginType.value = 'phone';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,32 +687,37 @@ async function handlePhoneLogin() {
|
|||||||
|
|
||||||
.modal-footer {
|
.modal-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-top: 1rpx solid #f0f0f0;
|
gap: 24rpx;
|
||||||
|
padding: 0 32rpx 32rpx;
|
||||||
|
|
||||||
.modal-button {
|
.modal-button {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 96rpx;
|
height: 88rpx;
|
||||||
line-height: 96rpx;
|
line-height: 88rpx;
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
border-radius: 16rpx;
|
||||||
transition: all 0.2s;
|
transition: all 0.3s;
|
||||||
|
|
||||||
&.cancel {
|
&.cancel {
|
||||||
color: #999;
|
background: #f7f8fa;
|
||||||
border-right: 1rpx solid #f0f0f0;
|
color: #666;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background: #f7f8fa;
|
background: #e8e9eb;
|
||||||
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.confirm {
|
&.confirm {
|
||||||
color: #667eea;
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(102, 126, 234, 0.3);
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background: rgba(102, 126, 234, 0.1);
|
box-shadow: 0 2rpx 8rpx rgba(102, 126, 234, 0.4);
|
||||||
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,6 @@
|
|||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- 商家服务 -->
|
<!-- 商家服务 -->
|
||||||
<view class="menu-section">
|
<view class="menu-section">
|
||||||
<menu-list
|
<menu-list
|
||||||
|
|||||||
@@ -3,9 +3,6 @@
|
|||||||
<!-- 搜索头部 -->
|
<!-- 搜索头部 -->
|
||||||
<view class="search-header">
|
<view class="search-header">
|
||||||
<view class="search-bar">
|
<view class="search-bar">
|
||||||
<view class="back-icon" @tap="goBack">
|
|
||||||
<u-icon name="arrow-left" size="20" color="#333" />
|
|
||||||
</view>
|
|
||||||
<view class="input-wrapper">
|
<view class="input-wrapper">
|
||||||
<u-icon name="search" size="18" color="#999" />
|
<u-icon name="search" size="18" color="#999" />
|
||||||
<input
|
<input
|
||||||
@@ -238,15 +235,6 @@ function formatMerchant(merchant: any) {
|
|||||||
margin-bottom: 16rpx;
|
margin-bottom: 16rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-icon {
|
|
||||||
width: 48rpx;
|
|
||||||
height: 48rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-wrapper {
|
.input-wrapper {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page-withdrawals">
|
<view class="page-withdrawals">
|
||||||
<!-- 导航栏 -->
|
|
||||||
<view class="navbar">
|
|
||||||
<view class="navbar-left" @tap="goBack">
|
|
||||||
<u-icon name="arrow-left" :size="20" color="#1A1A1A" />
|
|
||||||
</view>
|
|
||||||
<text class="navbar-title">提现记录</text>
|
|
||||||
<view class="navbar-right"></view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 筛选栏 -->
|
<!-- 筛选栏 -->
|
||||||
<view class="filter-bar">
|
<view class="filter-bar">
|
||||||
<view
|
<view
|
||||||
@@ -212,43 +203,6 @@ function goBack() {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 导航栏 */
|
|
||||||
.navbar {
|
|
||||||
background: #ffffff;
|
|
||||||
padding: 0 24rpx;
|
|
||||||
border-bottom: 1rpx solid #F0F0F0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
height: 88rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-left,
|
|
||||||
.navbar-right {
|
|
||||||
width: 80rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-left {
|
|
||||||
justify-content: flex-start;
|
|
||||||
padding: 12rpx;
|
|
||||||
margin-left: -12rpx;
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-title {
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1A1A1A;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 筛选栏 */
|
/* 筛选栏 */
|
||||||
.filter-bar {
|
.filter-bar {
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
|
|||||||
@@ -2,21 +2,17 @@
|
|||||||
function getBaseURL(): string {
|
function getBaseURL(): string {
|
||||||
// 判断当前运行平台
|
// 判断当前运行平台
|
||||||
// #ifdef H5
|
// #ifdef H5
|
||||||
return import.meta.env.VITE_H5_API_BASE_URL || 'http://localhost:3000';
|
return 'http://localhost:3000';
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
return import.meta.env.VITE_MP_API_BASE_URL || 'http://localhost:3000';
|
return 'http://192.168.0.111:3000';
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// 其他平台默认值
|
// 其他平台默认值
|
||||||
// #ifndef H5 || MP-WEIXIN
|
return 'http://192.168.0.111:3000';
|
||||||
return import.meta.env.VITE_MP_API_BASE_URL || 'http://localhost:3000';
|
|
||||||
// #endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const BASE_URL = getBaseURL();
|
|
||||||
|
|
||||||
interface RequestOptions {
|
interface RequestOptions {
|
||||||
url: string;
|
url: string;
|
||||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
||||||
@@ -54,6 +50,9 @@ export function request<T = any>(options: RequestOptions): Promise<ApiResponse<T
|
|||||||
header['Authorization'] = `Bearer ${token}`;
|
header['Authorization'] = `Bearer ${token}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 动态获取 BASE_URL
|
||||||
|
const BASE_URL = getBaseURL();
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
uni.request({
|
uni.request({
|
||||||
url: `${BASE_URL}${url}`,
|
url: `${BASE_URL}${url}`,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ScheduleModule } from '@nestjs/schedule';
|
|||||||
import databaseConfig from './config/database.config';
|
import databaseConfig from './config/database.config';
|
||||||
import jwtConfig from './config/jwt.config';
|
import jwtConfig from './config/jwt.config';
|
||||||
import redisConfig from './config/redis.config';
|
import redisConfig from './config/redis.config';
|
||||||
|
import wechatConfig from './config/wechat.config';
|
||||||
import { ScheduleModule as TaskScheduleModule } from './schedule/schedule.module';
|
import { ScheduleModule as TaskScheduleModule } from './schedule/schedule.module';
|
||||||
|
|
||||||
// 新的模块结构 - 按端分类
|
// 新的模块结构 - 按端分类
|
||||||
@@ -20,7 +21,7 @@ import { WebsiteModule } from './modules/website/website.module';
|
|||||||
TaskScheduleModule,
|
TaskScheduleModule,
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
load: [databaseConfig, jwtConfig, redisConfig],
|
load: [databaseConfig, jwtConfig, redisConfig, wechatConfig],
|
||||||
envFilePath: ['.env.local', '.env'],
|
envFilePath: ['.env.local', '.env'],
|
||||||
}),
|
}),
|
||||||
TypeOrmModule.forRootAsync({
|
TypeOrmModule.forRootAsync({
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('wechat', () => ({
|
||||||
|
appId: process.env.WECHAT_APPID,
|
||||||
|
appSecret: process.env.WECHAT_SECRET,
|
||||||
|
}));
|
||||||
@@ -239,18 +239,7 @@ export class AuthService {
|
|||||||
session_key: string;
|
session_key: string;
|
||||||
unionid?: string;
|
unionid?: string;
|
||||||
}> {
|
}> {
|
||||||
// 开发模式:返回模拟数据
|
// 调用微信API
|
||||||
const isDev = process.env.NODE_ENV === 'development';
|
|
||||||
if (isDev) {
|
|
||||||
this.logger.log(`[DEV] 模拟微信登录: code=${code}`);
|
|
||||||
return {
|
|
||||||
openid: `dev_openid_${Date.now()}`,
|
|
||||||
session_key: 'dev_session_key_1234567890abcdef',
|
|
||||||
unionid: `dev_unionid_${Date.now()}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生产模式:调用微信API
|
|
||||||
const appId = this.configService.get<string>('wechat.appId');
|
const appId = this.configService.get<string>('wechat.appId');
|
||||||
const appSecret = this.configService.get<string>('wechat.appSecret');
|
const appSecret = this.configService.get<string>('wechat.appSecret');
|
||||||
|
|
||||||
@@ -261,12 +250,17 @@ export class AuthService {
|
|||||||
const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appId}&secret=${appSecret}&js_code=${code}&grant_type=authorization_code`;
|
const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appId}&secret=${appSecret}&js_code=${code}&grant_type=authorization_code`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
this.logger.log(`调用微信API: appId=${appId}, code=${code.substring(0, 10)}...`);
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
|
this.logger.log(`微信API响应: ${JSON.stringify(data)}`);
|
||||||
|
|
||||||
if (data.errcode) {
|
if (data.errcode) {
|
||||||
this.logger.error(`微信API错误: ${data.errmsg}`);
|
const errorMsg = this.getWechatErrorMessage(data.errcode, data.errmsg);
|
||||||
throw new BadRequestException('微信授权失败');
|
this.logger.error(`微信API错误 [${data.errcode}]: ${errorMsg}`);
|
||||||
|
this.logger.error(`微信API错误 data : ${data}`);
|
||||||
|
throw new BadRequestException(errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -275,29 +269,35 @@ export class AuthService {
|
|||||||
unionid: data.unionid,
|
unionid: data.unionid,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof BadRequestException) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
this.logger.error('调用微信API失败', error);
|
this.logger.error('调用微信API失败', error);
|
||||||
throw new BadRequestException('微信授权失败');
|
throw new BadRequestException('微信授权失败,请重试');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getWechatErrorMessage(errcode: number, errmsg: string): string {
|
||||||
|
const errorMap: Record<number, string> = {
|
||||||
|
40029: '微信登录code无效,请重新授权',
|
||||||
|
45011: '微信登录频率限制,请稍后再试',
|
||||||
|
40163: 'code已被使用,请重新授权',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (errcode === -1) {
|
||||||
|
return '微信系统繁忙,请稍后再试';
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMap[errcode] || `微信授权失败: ${errmsg}`;
|
||||||
|
}
|
||||||
|
|
||||||
private decryptWechatData(
|
private decryptWechatData(
|
||||||
encryptedData: string,
|
encryptedData: string,
|
||||||
iv: string,
|
iv: string,
|
||||||
sessionKey: string,
|
sessionKey: string,
|
||||||
): any {
|
): any {
|
||||||
try {
|
try {
|
||||||
// 开发模式:返回模拟数据
|
// 解密数据
|
||||||
const isDev = process.env.NODE_ENV === 'development';
|
|
||||||
if (isDev) {
|
|
||||||
this.logger.log('[DEV] 模拟解密微信手机号');
|
|
||||||
return {
|
|
||||||
phoneNumber: '13800138000',
|
|
||||||
purePhoneNumber: '13800138000',
|
|
||||||
countryCode: '86',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生产模式:解密数据
|
|
||||||
const sessionKeyBuffer = Buffer.from(sessionKey, 'base64');
|
const sessionKeyBuffer = Buffer.from(sessionKey, 'base64');
|
||||||
const encryptedDataBuffer = Buffer.from(encryptedData, 'base64');
|
const encryptedDataBuffer = Buffer.from(encryptedData, 'base64');
|
||||||
const ivBuffer = Buffer.from(iv, 'base64');
|
const ivBuffer = Buffer.from(iv, 'base64');
|
||||||
|
|||||||
Reference in New Issue
Block a user