chore: 移除微信授权登录

This commit is contained in:
Ray.Hao
2026-02-12 21:11:44 +08:00
parent 967accfd48
commit 7cbf29ff90
8 changed files with 119 additions and 521 deletions

View File

@@ -7,13 +7,6 @@ export interface LoginData {
password: string;
}
export interface WxLoginData {
code: string;
encryptedData?: string;
iv?: string;
phoneCode?: string;
}
export interface LoginResult {
accessToken: string;
refreshToken?: string;
@@ -23,6 +16,11 @@ export interface LoginResult {
isProfileComplete?: boolean;
}
export interface SmsLoginData {
mobile: string;
code: string;
}
const AuthAPI = {
/**
* 账号密码登录
@@ -37,42 +35,20 @@ const AuthAPI = {
});
},
/**
* 微信Web授权登录 (使用code进行授权登录)
* @param code 微信授权码
* @returns 登录结果
*/
loginByWechat(code: string): Promise<LoginResult> {
return publicRequest<LoginResult>({
url: `${AUTH_BASE_URL}/login/wechat`,
sendSmsLoginCode(mobile: string): Promise<void> {
const mobileSafe = encodeURIComponent(mobile);
return publicRequest<void>({
url: `${AUTH_BASE_URL}/sms/code?mobile=${mobileSafe}`,
method: "POST",
data: { code },
});
},
/**
* 微信小程序授权登录 (仅使用code获取OpenID)
* @param code 微信登录凭证
* @returns 登录结果
*/
loginByWxMiniAppCode(code: string): Promise<LoginResult> {
loginBySms(data: SmsLoginData): Promise<LoginResult> {
const mobileSafe = encodeURIComponent(data.mobile);
const codeSafe = encodeURIComponent(data.code);
return publicRequest<LoginResult>({
url: `${AUTH_BASE_URL}/wx/miniapp/code-login`,
url: `${AUTH_BASE_URL}/login/sms?mobile=${mobileSafe}&code=${codeSafe}`,
method: "POST",
data: { code },
});
},
/**
* 微信小程序手机号授权登录
* @param data 包含code、encryptedData、iv等手机号相关数据
* @returns 登录结果
*/
loginByWxMiniAppPhone(data: WxLoginData): Promise<LoginResult> {
return publicRequest<LoginResult>({
url: `${AUTH_BASE_URL}/wx/miniapp/phone-login`,
method: "POST",
data,
});
},

View File

@@ -25,10 +25,7 @@ const UserAPI = {
url: `${USER_BASE_URL}`,
method: "GET",
data: queryParams,
}).then((res: { data: UserPageVO[]; page?: { total?: number } }) => ({
list: res.data,
total: res.page?.total ?? 0,
}));
});
},
/**
* 添加用户
@@ -138,15 +135,6 @@ const UserAPI = {
method: "DELETE",
});
},
/** 获取微信手机号 */
getPhoneNumber(data: WechatPhoneData): Promise<PhoneNumberResult> {
return request<PhoneNumberResult>({
url: `${USER_BASE_URL}/wechat-phone`,
method: "POST",
data: data,
});
},
};
export default UserAPI;
@@ -326,23 +314,3 @@ export interface UserForm {
/** 用户名 */
username?: string;
}
/** 微信手机号授权数据 */
export interface WechatPhoneData {
/** 微信授权码 */
code: string;
/** 加密数据 */
encryptedData?: string;
/** 初始向量 */
iv?: string;
}
/** 手机号获取结果 */
export interface PhoneNumberResult {
/** 手机号 */
phoneNumber: string;
/** 纯手机号(去除+86 */
purePhoneNumber?: string;
/** 国家代码 */
countryCode?: string;
}

View File

@@ -1,159 +0,0 @@
/**
* 微信授权服务
* 处理微信登录授权、获取用户信息等功能
*/
import { ref } from "vue";
import { getAccessToken } from "@/utils/auth";
export function useWechat() {
// 定义微信授权状态
const authState = ref({
isLogining: false,
authDenied: false,
});
/**
* 获取微信登录凭证code
* @returns Promise 返回登录凭证code
*/
const getLoginCode = (): Promise<string> => {
return new Promise((resolve, reject) => {
// #ifdef MP-WEIXIN
uni.login({
provider: "weixin",
success: (res) => {
if (res.code) {
resolve(res.code);
} else {
reject(new Error("获取微信登录凭证失败"));
}
},
fail: (err) => {
reject(err);
},
});
// #endif
// #ifndef MP-WEIXIN
reject(new Error("当前环境不支持微信登录"));
// #endif
});
};
/**
* 获取微信用户手机号
* @param e 微信授权返回的事件对象
* @returns Promise 返回包含手机号加密数据的对象
*/
const getPhoneNumber = (
e: any
): Promise<{ code: string; encryptedData?: string; iv?: string }> => {
return new Promise((resolve, reject) => {
authState.value.isLogining = true;
// 判断授权是否成功
if (e.detail.errMsg !== "getPhoneNumber:ok") {
authState.value.isLogining = false;
authState.value.authDenied = true;
reject(new Error("用户拒绝授权"));
return;
}
// 获取登录凭证code
getLoginCode()
.then((code) => {
// 在微信小程序环境下可以获取encryptedData和iv
// #ifdef MP-WEIXIN
resolve({
code,
encryptedData: e.detail.encryptedData,
iv: e.detail.iv,
});
// #endif
// 其他环境或新版本接口
// #ifndef MP-WEIXIN
resolve({
code,
// 新版本接口在e.detail.code中包含手机号获取凭证
...(e.detail.code ? { phoneCode: e.detail.code } : {}),
});
// #endif
})
.catch((err) => {
reject(err);
})
.finally(() => {
authState.value.isLogining = false;
});
});
};
/**
* 检查会话有效性
* @returns Promise 返回会话是否有效
*/
const checkSession = (): Promise<boolean> => {
return new Promise((resolve) => {
const token = getAccessToken();
if (!token) {
resolve(false);
return;
}
// 调用后端接口验证token有效性
uni.request({
url: "/api/v1/auth/check-session",
method: "GET",
header: {
Authorization: `Bearer ${token}`,
},
success: (res: any) => {
if (res.statusCode === 200 && res.data.valid) {
resolve(true);
} else {
resolve(false);
}
},
fail: () => {
resolve(false);
},
});
});
};
/**
* 获取用户头像昵称
* 注意此接口已于2021年弃用仅作为兼容保留
* 推荐使用button组件的open-type="chooseAvatar"让用户选择头像
*/
const getUserProfile = (): Promise<any> => {
return new Promise((resolve, reject) => {
// #ifdef MP-WEIXIN
uni.getUserProfile({
desc: "用于完善用户资料",
success: (res) => {
resolve(res.userInfo);
},
fail: (err) => {
reject(err);
},
});
// #endif
// #ifndef MP-WEIXIN
reject(new Error("当前环境不支持获取用户信息"));
// #endif
});
};
return {
authState,
getLoginCode,
getPhoneNumber,
checkSession,
getUserProfile,
};
}

View File

@@ -6,7 +6,7 @@
<!-- 登录卡片 -->
<view class="login-card">
<!-- 账号密码登录表单 -->
<wd-form v-if="loginType === 'account'" ref="loginFormRef" :model="loginFormData">
<wd-form v-if="loginMode === 'ACCOUNT'" ref="loginFormRef" :model="loginFormData">
<!-- 用户名输入框 -->
<view class="login-form__item">
<wd-icon name="user" size="22" class="mr-20rpx" />
@@ -47,49 +47,51 @@
>
{{ loading ? "登录中..." : "账号登录" }}
</button>
<!-- 切换登录方式 -->
<view class="login-switch" @click="loginType = 'phone'">
<text>使用手机号一键登录</text>
<wd-icon name="arrow-right" size="12" />
</view>
</wd-form>
<!-- 手机号登录 -->
<view v-else class="phone-login">
<view class="phone-login__title">微信一键登录</view>
<view class="phone-login__subtitle">授权后将获取您的手机号</view>
<wd-form v-else ref="smsLoginFormRef" :model="smsLoginFormData">
<view class="login-form__item">
<wd-icon name="phone" size="22" class="mr-20rpx" />
<input
v-model="smsLoginFormData.mobile"
class="login-form__input"
placeholder="请输入手机号"
placeholder-class="login-form__placeholder"
/>
</view>
<view class="login-form__divider"></view>
<view class="login-form__item">
<wd-icon name="security" size="22" class="mr-20rpx" />
<input
v-model="smsLoginFormData.code"
class="login-form__input"
placeholder="请输入验证码"
placeholder-class="login-form__placeholder"
/>
<wd-button
size="small"
plain
:disabled="smsCountdown > 0"
@click="handleSendSmsLoginCode"
>
{{ smsCountdown > 0 ? `${smsCountdown}s后重试` : "发送验证码" }}
</wd-button>
</view>
<view class="login-form__divider"></view>
<button
class="wechat-btn"
class="login-btn"
:disabled="loading"
open-type="getPhoneNumber"
@getphonenumber="handleWechatPhoneLogin"
:class="{ 'login-btn--loading': loading }"
@click="handleSmsLogin"
>
<wd-icon name="weixin" size="24" color="#ffffff" />
<text class="ml-16rpx">微信一键登录</text>
{{ loading ? "登录中..." : "短信登录" }}
</button>
</wd-form>
<!-- 切换登录方式 -->
<view class="login-switch" @click="loginType = 'account'">
<text>使用账号密码登录</text>
<wd-icon name="arrow-right" size="12" />
</view>
</view>
<!-- 其他登录方式 -->
<view class="other-login">
<view class="other-login__title">
<view class="other-login__line"></view>
<text class="other-login__text">其他登录方式</text>
<view class="other-login__line"></view>
</view>
<view class="flex-center" @click="handleWechatLogin">
<view class="wechat-icon">
<image src="/static/icons/weixin.png" class="w-40rpx h-40rpx" />
</view>
</view>
<view class="login-switch" @click="loginMode = loginMode === 'ACCOUNT' ? 'SMS' : 'ACCOUNT'">
{{ loginMode === "ACCOUNT" ? "使用短信验证码登录" : "使用账号密码登录" }}
</view>
<!-- 底部协议 -->
@@ -120,19 +122,21 @@
<script lang="ts" setup>
import { onLoad } from "@dcloudio/uni-app";
import { type LoginData } from "@/api/auth";
import AuthAPI, { type LoginData } from "@/api/auth";
import { useUserStore } from "@/store/modules/user-store";
import { useToast } from "wot-design-uni";
import { useWechat } from "@/composables/useWechat";
const loginFormRef = ref();
const smsLoginFormRef = ref();
const toast = useToast();
const loading = ref(false);
const userStore = useUserStore();
const showPassword = ref(false);
const loginType = ref<"account" | "phone">("account");
const isAgreePolicy = ref(false); // 是否同意隐私协议
const { authState, getLoginCode, getPhoneNumber } = useWechat();
const loginMode = ref<"ACCOUNT" | "SMS">("ACCOUNT");
const smsCountdown = ref(0);
const smsTimer = ref<ReturnType<typeof setInterval> | null>(null);
// 登录表单数据
const loginFormData = ref<LoginData>({
@@ -140,9 +144,14 @@ const loginFormData = ref<LoginData>({
password: "123456",
});
const smsLoginFormData = ref({
mobile: "",
code: "",
});
// 仅使用 query 作为重定向来源
const redirect = ref("/pages/index/index");
onLoad((options) => {
onLoad((options: any) => {
const fromQuery = options && options.redirect ? decodeURIComponent(options.redirect) : "";
if (fromQuery && fromQuery !== "/pages/login/index") {
redirect.value = fromQuery;
@@ -184,7 +193,7 @@ const handleAccountLogin = () => {
uni.reLaunch({ url: redirect.value });
}, 1000);
})
.catch((error) => {
.catch((error: any) => {
toast.error(error?.message || "登录失败");
})
.finally(() => {
@@ -192,100 +201,63 @@ const handleAccountLogin = () => {
});
};
// 微信一键登录(通过手机号)
const handleWechatPhoneLogin = async (e: any) => {
if (loading.value || authState.value.isLogining) return;
// 检查是否同意隐私协议
if (!isAgreePolicy.value) {
toast.error("请先阅读并同意用户协议和隐私政策");
const handleSendSmsLoginCode = async () => {
if (smsCountdown.value > 0) return;
if (!smsLoginFormData.value.mobile) {
toast.error("请输入手机号");
return;
}
loading.value = true;
try {
// 获取手机号加密数据
const phoneData = await getPhoneNumber(e);
// 调用登录接口
const result: any = await userStore.loginWithWxPhone(phoneData);
// 获取用户信息
await userStore.getInfo();
toast.success("登录成功");
// 检查是否为新用户或信息不完整
if (result.isNewUser || !userStore.isUserInfoComplete()) {
// 跳转到完善信息页面
setTimeout(() => {
// 跳转完善资料redirect 保持在缓存中供目标页读取
uni.navigateTo({ url: "/pages/mine/profile/complete-profile" });
}, 1000);
} else {
// 跳转到重定向页面
setTimeout(() => {
uni.reLaunch({ url: redirect.value });
}, 1000);
}
await AuthAPI.sendSmsLoginCode(smsLoginFormData.value.mobile);
toast.success("验证码已发送");
smsCountdown.value = 60;
if (smsTimer.value) clearInterval(smsTimer.value);
smsTimer.value = setInterval(() => {
smsCountdown.value -= 1;
if (smsCountdown.value <= 0) {
smsCountdown.value = 0;
if (smsTimer.value) {
clearInterval(smsTimer.value);
smsTimer.value = null;
}
}
}, 1000);
} catch (error: any) {
if (error.message === "用户拒绝授权") {
toast.error("您已拒绝授权获取手机号");
} else {
toast.error(error?.message || "登录失败");
}
console.error("微信手机号登录失败:", error);
} finally {
loading.value = false;
toast.error(error?.message || "发送失败");
}
};
// 微信授权登录处理
const handleWechatLogin = async () => {
const handleSmsLogin = () => {
if (loading.value) return;
// 检查是否同意隐私协议
if (!isAgreePolicy.value) {
toast.error("请先阅读并同意用户协议和隐私政策");
return;
}
if (!smsLoginFormData.value.mobile) {
toast.error("请输入手机号");
return;
}
if (!smsLoginFormData.value.code) {
toast.error("请输入验证码");
return;
}
loading.value = true;
try {
// #ifdef MP-WEIXIN
// 获取微信登录的临时 code
const code = await getLoginCode();
// 在微信小程序环境下,使用小程序授权登录接口
const result: any = await userStore.loginWithWxMiniAppCode(code);
// 获取用户信息
await userStore.getInfo();
toast.success("登录成功");
// 检查用户信息是否完整
if (result.isNewUser || !userStore.isUserInfoComplete()) {
// 如果信息不完整,跳转到完善信息页面
setTimeout(() => {
uni.navigateTo({ url: "/pages/mine/profile/complete-profile" });
}, 1000);
} else {
// 否则直接跳转到重定向页面
userStore
.loginBySms({ mobile: smsLoginFormData.value.mobile, code: smsLoginFormData.value.code })
.then(() => userStore.getInfo())
.then(() => {
toast.success("登录成功");
setTimeout(() => {
uni.reLaunch({ url: redirect.value });
}, 1000);
}
// #endif
// #ifndef MP-WEIXIN
toast.error("当前环境不支持微信登录");
// #endif
} catch (error: any) {
toast.error(error?.message || "微信登录失败");
} finally {
loading.value = false;
}
})
.catch((error: any) => {
toast.error(error?.message || "登录失败");
})
.finally(() => {
loading.value = false;
});
};
// 跳转到用户协议页面
@@ -416,20 +388,6 @@ const navigateToPrivacy = () => {
}
}
// 微信按钮
.wechat-btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 88rpx;
font-size: 32rpx;
color: #ffffff;
background-color: #07c160;
border: none;
border-radius: 44rpx;
}
// 其他登录方式
.other-login {
margin-top: 60rpx;
@@ -454,17 +412,6 @@ const navigateToPrivacy = () => {
}
}
// 微信图标
.wechat-icon {
display: flex;
align-items: center;
justify-content: center;
width: 80rpx;
height: 80rpx;
background-color: #07c160;
border-radius: 50%;
}
// 登录协议
.login-agreement {
display: flex;

View File

@@ -102,22 +102,12 @@
手机号
<text class="required">*</text>
</view>
<view v-if="!profileForm.mobile" class="phone-auth">
<button
class="phone-auth-btn"
open-type="getPhoneNumber"
:disabled="phoneAuthLoading"
@getphonenumber="onGetPhoneNumber"
>
<wd-icon name="phone" size="20" color="#165DFF" />
<text class="auth-text">{{ phoneAuthLoading ? "授权中..." : "授权获取手机号" }}</text>
</button>
</view>
<view v-else class="phone-display">
<wd-icon name="phone" size="20" color="#52c41a" />
<text class="phone-number">{{ formatPhoneNumber(profileForm.mobile) }}</text>
<text class="change-phone" @click="changePhone">更换</text>
</view>
<wd-input
v-model="profileForm.mobile"
placeholder="请输入手机号"
prop="mobile"
custom-class="nickname-input"
/>
</view>
</wd-form>
</view>
@@ -183,7 +173,6 @@ const rules = {
// 状态管理
const loading = ref(false);
const phoneAuthLoading = ref(false);
const cropperVisible = ref(false);
const originalImageSrc = ref("");
const profileFormRef = ref();
@@ -280,45 +269,6 @@ const handleAvatarConfirm = async (event: any) => {
}
};
// 获取手机号授权
const onGetPhoneNumber = async (e: any) => {
console.log("手机号授权回调:", e);
if (e.detail.errMsg === "getPhoneNumber:ok") {
phoneAuthLoading.value = true;
try {
// 调用后端接口解析手机号
const phoneData = await UserAPI.getPhoneNumber({
code: e.detail.code,
encryptedData: e.detail.encryptedData,
iv: e.detail.iv,
});
profileForm.mobile = phoneData.phoneNumber;
toast.success("手机号授权成功");
} catch (error: any) {
console.error("手机号授权失败:", error);
toast.error(error?.message || "手机号授权失败");
} finally {
phoneAuthLoading.value = false;
}
} else {
toast.error("手机号授权失败");
}
};
// 更换手机号
const changePhone = () => {
profileForm.mobile = "";
};
// 格式化手机号显示
const formatPhoneNumber = (phone: string) => {
if (!phone) return "";
return phone.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
};
// 完成信息填写
const handleComplete = async () => {
try {
@@ -327,7 +277,7 @@ const handleComplete = async () => {
if (!valid) return;
if (!profileForm.mobile) {
toast.error("请先授权获取手机号");
toast.error("请输入手机号");
return;
}
@@ -477,51 +427,6 @@ const handleSkip = () => {
}
}
.phone-auth {
.phone-auth-btn {
display: flex;
gap: 12rpx;
align-items: center;
justify-content: center;
width: 100%;
height: 88rpx;
font-size: 28rpx;
color: #fff;
background: linear-gradient(90deg, #165dff, #4080ff);
border: none;
border-radius: 12rpx;
&[disabled] {
opacity: 0.6;
}
.auth-text {
font-size: 28rpx;
}
}
}
.phone-display {
display: flex;
gap: 12rpx;
align-items: center;
padding: 24rpx 20rpx;
background: #f0f9ff;
border: 1rpx solid #bae6fd;
border-radius: 12rpx;
.phone-number {
flex: 1;
font-size: 28rpx;
color: #333;
}
.change-phone {
font-size: 26rpx;
color: #165dff;
}
}
.footer {
.complete-btn {
margin-bottom: 30rpx;

View File

@@ -1,5 +1,5 @@
import { defineStore } from "pinia";
import AuthAPI, { type LoginData, type WxLoginData } from "@/api/auth";
import AuthAPI, { type LoginData, type SmsLoginData } from "@/api/auth";
import UserAPI, { type UserInfo } from "@/api/user";
import { setAccessToken, clearTokens } from "@/utils/auth";
import { getUserInfo, setUserInfo } from "@/utils/storage";
@@ -24,46 +24,15 @@ export const useUserStore = defineStore("user", () => {
});
};
// 微信Web授权登录
const loginWithWxCode = (code: string) => {
const loginBySms = (data: SmsLoginData) => {
return new Promise((resolve, reject) => {
AuthAPI.loginByWechat(code)
AuthAPI.loginBySms(data)
.then((data) => {
setAccessToken(data.accessToken);
resolve(data);
})
.catch((error: any) => {
console.error("微信授权登录失败", error);
reject(error);
});
});
};
// 微信小程序授权登录 (仅使用code获取OpenID)
const loginWithWxMiniAppCode = (code: string) => {
return new Promise((resolve, reject) => {
AuthAPI.loginByWxMiniAppCode(code)
.then((data) => {
setAccessToken(data.accessToken);
resolve(data);
})
.catch((error: any) => {
console.error("微信小程序授权登录失败", error);
reject(error);
});
});
};
// 微信手机号授权登录
const loginWithWxPhone = (data: WxLoginData): Promise<any> => {
return new Promise((resolve, reject) => {
AuthAPI.loginByWxMiniAppPhone(data)
.then((result: any) => {
setAccessToken(result.accessToken);
resolve(result);
})
.catch((error: any) => {
console.error("微信手机号登录失败", error);
.catch((error) => {
console.error("登录失败", error);
reject(error);
});
});
@@ -126,9 +95,7 @@ export const useUserStore = defineStore("user", () => {
return {
userInfo,
login,
loginWithWxCode,
loginWithWxMiniAppCode,
loginWithWxPhone,
loginBySms,
logout,
getInfo,
checkSession,

View File

@@ -179,7 +179,6 @@ declare global {
const useUploadFile: typeof import('@uni-helper/uni-use')['useUploadFile']
const useUserStore: typeof import('../store/modules/user-store')['useUserStore']
const useVisible: typeof import('@uni-helper/uni-use')['useVisible']
const useWechat: typeof import('../composables/useWechat')['useWechat']
const user: typeof import('../api/user')['default']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
@@ -320,7 +319,6 @@ declare module 'vue' {
readonly useThemeStore: UnwrapRef<typeof import('../store/modules/theme-store')['useThemeStore']>
readonly useToast: UnwrapRef<typeof import('wot-design-uni')['useToast']>
readonly useUserStore: UnwrapRef<typeof import('../store/modules/user-store')['useUserStore']>
readonly useWechat: UnwrapRef<typeof import('../composables/useWechat')['useWechat']>
readonly user: UnwrapRef<typeof import('../api/user')['default']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>

View File

@@ -54,11 +54,7 @@ function request<T = any>(options: RequestOptions): Promise<T> {
success: (res: any) => {
// 请求成功
if (res.statusCode >= 200 && res.statusCode < 300) {
if (res.data && typeof res.data === "object" && "page" in res.data && res.data.page != null) {
resolve({ data: res.data.data, page: res.data.page } as any);
} else {
resolve(res.data.data);
}
resolve(res.data.data);
}
// 未授权错误
else if (res.statusCode === 401) {