feat: 更新微信小程序登录流程和演示环境配置

This commit is contained in:
Ray.Hao
2026-03-05 16:57:54 +08:00
parent 67ada6263b
commit 5bf7f0561f
11 changed files with 137 additions and 64 deletions

View File

@@ -7,8 +7,8 @@ VITE_APP_PORT = 4096
VITE_APP_BASE_API = '/dev-api' VITE_APP_BASE_API = '/dev-api'
# API 服务器的 URL # API 服务器的 URL
#VITE_APP_API_URL = http://localhost:8989 VITE_APP_API_URL = http://localhost:8000
VITE_APP_API_URL = https://api.youlai.tech # VITE_APP_API_URL = https://api.youlai.tech
# WebSocket 服务器的 URL不配置默认关闭 WebSocket # WebSocket 服务器的 URL不配置默认关闭 WebSocket
#VITE_APP_WS_ENDPOINT= ws://localhost:4096/ws #VITE_APP_WS_ENDPOINT= ws://localhost:4096/ws

2
components.d.ts vendored
View File

@@ -1,4 +1,4 @@
/* eslint-disable */ /* eslint-disable */
/* prettier-ignore */ /* prettier-ignore */
// @ts-nocheck // @ts-nocheck
// Generated by vite-plugin-uni-components // Generated by vite-plugin-uni-components

View File

@@ -1,5 +1,5 @@
{ {
"name": "vue-uniapp-template", "name": "youlai-uniapp-template",
"version": "0.0.0", "version": "0.0.0",
"scripts": { "scripts": {
"dev:app": "uni -p app", "dev:app": "uni -p app",

View File

@@ -1,6 +1,7 @@
import request, { publicRequest } from "@/utils/request"; import request, { publicRequest } from "@/utils/request";
const AUTH_BASE_URL = "/api/v1/auth"; const AUTH_BASE_URL = "/api/v1/auth";
const WECHAT_MINIAPP_AUTH_BASE_URL = "/api/v1/wechat/miniapp/auth";
export interface LoginData { export interface LoginData {
username: string; username: string;
@@ -40,7 +41,7 @@ export interface WechatMiniappPhoneLoginData {
export interface WechatMiniappBindMobileData { export interface WechatMiniappBindMobileData {
openid: string; openid: string;
mobile: string; mobile: string;
code: string; smsCode: string;
} }
const AuthAPI = { const AuthAPI = {
@@ -57,6 +58,8 @@ const AuthAPI = {
/** /**
* 发送短信验证码 * 发送短信验证码
*
* 演示环境说明:短信服务未配置,验证码固定为 123456
*/ */
sendSmsLoginCode(mobile: string): Promise<void> { sendSmsLoginCode(mobile: string): Promise<void> {
const mobileSafe = encodeURIComponent(mobile); const mobileSafe = encodeURIComponent(mobile);
@@ -79,34 +82,46 @@ const AuthAPI = {
}, },
/** /**
* 微信小程序登录(个人小程序) * 微信小程序静默登录
*
* 适用场景:个人小程序
* - 已绑定手机号的用户:直接返回 token登录成功
* - 未绑定手机号的用户:返回 openid需调用绑定手机号接口
*/ */
wechatMiniappLogin(code: string): Promise<WechatMiniappLoginResult> { wechatMiniappSilentLogin(code: string): Promise<WechatMiniappLoginResult> {
return publicRequest<WechatMiniappLoginResult>({ return publicRequest<WechatMiniappLoginResult>({
url: `${AUTH_BASE_URL}/wechat-miniapp/login?code=${encodeURIComponent(code)}`, url: `${WECHAT_MINIAPP_AUTH_BASE_URL}/silent-login?code=${encodeURIComponent(code)}`,
method: "POST", method: "POST",
}); });
}, },
/** /**
* 微信小程序一键登录(企业小程序) * 微信小程序手机号快捷登录
*
* 适用场景:企业认证小程序(已开通手机号快捷登录权限)
* 一步完成登录,无需绑定流程,自动创建新用户
*/ */
wechatMiniappPhoneLogin(data: WechatMiniappPhoneLoginData): Promise<LoginResult> { wechatMiniappPhoneLogin(data: WechatMiniappPhoneLoginData): Promise<LoginResult> {
const loginCodeSafe = encodeURIComponent(data.loginCode);
const phoneCodeSafe = encodeURIComponent(data.phoneCode);
return publicRequest<LoginResult>({ return publicRequest<LoginResult>({
url: `${AUTH_BASE_URL}/wechat-miniapp/phone-login`, url: `${WECHAT_MINIAPP_AUTH_BASE_URL}/phone-login?loginCode=${loginCodeSafe}&phoneCode=${phoneCodeSafe}`,
method: "POST", method: "POST",
data,
}); });
}, },
/** /**
* 微信小程序绑定手机号 * 微信小程序绑定手机号
*
* 演示环境说明:短信服务未配置,验证码固定为 123456
*/ */
wechatMiniappBindMobile(data: WechatMiniappBindMobileData): Promise<LoginResult> { wechatMiniappBindMobile(data: WechatMiniappBindMobileData): Promise<LoginResult> {
const openidSafe = encodeURIComponent(data.openid);
const mobileSafe = encodeURIComponent(data.mobile);
const smsCodeSafe = encodeURIComponent(data.smsCode);
return publicRequest<LoginResult>({ return publicRequest<LoginResult>({
url: `${AUTH_BASE_URL}/wechat-miniapp/bind-mobile`, url: `${WECHAT_MINIAPP_AUTH_BASE_URL}/bind-mobile?openid=${openidSafe}&mobile=${mobileSafe}&smsCode=${smsCodeSafe}`,
method: "POST", method: "POST",
data,
}); });
}, },

View File

@@ -93,6 +93,14 @@ const UserAPI = {
}); });
}, },
/** 解绑第三方账号 */
unbindSocial(platform: string) {
return request({
url: `${USER_BASE_URL}/social?platform=${encodeURIComponent(platform)}`,
method: "DELETE",
});
},
/** /**
* 发送手机/邮箱验证码 * 发送手机/邮箱验证码
* *

View File

@@ -152,15 +152,10 @@
"backgroundColorTop": "@bgColorTop", "backgroundColorTop": "@bgColorTop",
"backgroundColorBottom": "@bgColorBottom", "backgroundColorBottom": "@bgColorBottom",
"enablePullDownRefresh": false, "enablePullDownRefresh": false,
"onReachBottomDistance": 50, "onReachBottomDistance": 50
"animationType": "pop-in",
"animationDuration": 300
}, },
"tabBar": { "tabBar": {
"custom": true, "custom": true,
"customize": true,
"overlay": true,
"height": "0",
"color": "@tabColor", "color": "@tabColor",
"selectedColor": "@tabSelectedColor", "selectedColor": "@tabSelectedColor",
"backgroundColor": "@tabBgColor", "backgroundColor": "@tabBgColor",
@@ -179,4 +174,4 @@
}, },
"__esModule": true, "__esModule": true,
"subPackages": [] "subPackages": []
} }

View File

@@ -71,6 +71,11 @@
</view> </view>
</view> </view>
<!-- 演示环境提示 -->
<view v-if="loginMode === 'SMS'" class="demo-tip">
<text class="demo-tip-text">演示环境验证码123456</text>
</view>
<!-- 登录按钮 --> <!-- 登录按钮 -->
<button class="btn-primary" :disabled="loading || !canSubmit" @click="handleLogin"> <button class="btn-primary" :disabled="loading || !canSubmit" @click="handleLogin">
{{ loading ? "登录中..." : " " }} {{ loading ? "登录中..." : " " }}
@@ -87,7 +92,7 @@
</view> </view>
</view> </view>
<!-- #ifdef MP-WECHAT --> <!-- #ifdef MP-WEIXIN -->
<!-- 微信登录区域 --> <!-- 微信登录区域 -->
<view v-else class="form-area"> <view v-else class="form-area">
<!-- 企业一键登录 --> <!-- 企业一键登录 -->
@@ -96,7 +101,7 @@
open-type="getPhoneNumber" open-type="getPhoneNumber"
@getphonenumber="handleWechatPhoneLogin" @getphonenumber="handleWechatPhoneLogin"
> >
<wd-icon name="wechat" size="20" color="#fff" class="mr-2" /> <image class="btn-wechat-icon" src="/static/icons/weixin.png" mode="aspectFit" />
微信一键登录 微信一键登录
</button> </button>
@@ -108,7 +113,7 @@
</view> </view>
<!-- #endif --> <!-- #endif -->
<!-- #ifdef MP-WECHAT --> <!-- #ifdef MP-WEIXIN -->
<!-- 分割线 --> <!-- 分割线 -->
<view v-if="loginMode !== 'WECHAT'" class="divider"> <view v-if="loginMode !== 'WECHAT'" class="divider">
<view class="divider-line" /> <view class="divider-line" />
@@ -118,9 +123,12 @@
<!-- 微信登录入口 --> <!-- 微信登录入口 -->
<view v-if="loginMode !== 'WECHAT'" class="wechat-entry"> <view v-if="loginMode !== 'WECHAT'" class="wechat-entry">
<view class="wechat-icon" @click="loginMode = 'WECHAT'"> <image
<wd-icon name="wechat" size="24" color="#fff" /> class="wechat-icon-img"
</view> src="/static/icons/weixin.png"
mode="aspectFit"
@click="loginMode = 'WECHAT'"
/>
</view> </view>
<!-- #endif --> <!-- #endif -->
@@ -183,6 +191,11 @@
</view> </view>
</view> </view>
<!-- 演示环境提示 -->
<view class="demo-tip">
<text class="demo-tip-text">演示环境验证码123456</text>
</view>
<button class="btn-bind" :disabled="bindLoading" @click="handleBindMobile"> <button class="btn-bind" :disabled="bindLoading" @click="handleBindMobile">
{{ bindLoading ? "绑定中..." : "确认绑定" }} {{ bindLoading ? "绑定中..." : "确认绑定" }}
</button> </button>
@@ -221,7 +234,7 @@ const smsTimer = ref<ReturnType<typeof setInterval> | null>(null);
const formData = ref({ const formData = ref({
username: "admin", username: "admin",
password: "123456", password: "123456",
code: "1234", code: "123456",
}); });
const redirect = ref("/pages/index/index"); const redirect = ref("/pages/index/index");
@@ -459,7 +472,7 @@ const handleBindMobile = async () => {
await userStore.bindMobileForWechatMiniapp({ await userStore.bindMobileForWechatMiniapp({
openid: wechatOpenid.value, openid: wechatOpenid.value,
mobile, mobile,
code, smsCode: code,
}); });
await userStore.getInfo(); await userStore.getInfo();
showBindMobilePopup.value = false; showBindMobilePopup.value = false;
@@ -502,8 +515,8 @@ const navigateToAgreement = (type: string) => {
.bg-circle { .bg-circle {
position: absolute; position: absolute;
border-radius: 50%;
filter: blur(64px); filter: blur(64px);
border-radius: 50%;
} }
.bg-circle-1 { .bg-circle-1 {
@@ -515,8 +528,8 @@ const navigateToAgreement = (type: string) => {
} }
.bg-circle-2 { .bg-circle-2 {
bottom: -80px;
right: -80px; right: -80px;
bottom: -80px;
width: 320px; width: 320px;
height: 320px; height: 320px;
background-color: rgba(59, 130, 246, 0.15); background-color: rgba(59, 130, 246, 0.15);
@@ -562,9 +575,9 @@ const navigateToAgreement = (type: string) => {
width: 100%; width: 100%;
padding: 32px; padding: 32px;
background-color: rgba(255, 255, 255, 0.95); background-color: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(24px);
border-radius: 24px; border-radius: 24px;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(24px);
} }
:global(.dark) .login-card { :global(.dark) .login-card {
@@ -573,8 +586,8 @@ const navigateToAgreement = (type: string) => {
// 卡片头部 // 卡片头部
.card-header { .card-header {
text-align: center;
margin-bottom: 32px; margin-bottom: 32px;
text-align: center;
} }
.card-title { .card-title {
@@ -589,9 +602,9 @@ const navigateToAgreement = (type: string) => {
.card-desc { .card-desc {
display: block; display: block;
margin-top: 8px;
font-size: 14px; font-size: 14px;
color: #6b7280; color: #6b7280;
margin-top: 8px;
} }
:global(.dark) .card-desc { :global(.dark) .card-desc {
@@ -641,11 +654,11 @@ const navigateToAgreement = (type: string) => {
// 主按钮 // 主按钮
.btn-primary { .btn-primary {
display: flex;
align-items: center;
justify-content: center;
width: 100%; width: 100%;
height: 48px; height: 48px;
display: flex;
justify-content: center;
align-items: center;
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
color: #fff; color: #fff;
@@ -659,18 +672,18 @@ const navigateToAgreement = (type: string) => {
} }
&:disabled { &:disabled {
opacity: 0.5;
box-shadow: none; box-shadow: none;
opacity: 0.5;
} }
} }
// 微信按钮 // 微信按钮
.btn-wechat { .btn-wechat {
display: flex;
align-items: center;
justify-content: center;
width: 100%; width: 100%;
height: 48px; height: 48px;
display: flex;
justify-content: center;
align-items: center;
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
color: #fff; color: #fff;
@@ -683,13 +696,19 @@ const navigateToAgreement = (type: string) => {
} }
} }
.btn-wechat-icon {
width: 20px;
height: 20px;
margin-right: 8px;
}
// 绑定按钮 // 绑定按钮
.btn-bind { .btn-bind {
display: flex;
align-items: center;
justify-content: center;
width: 100%; width: 100%;
height: 48px; height: 48px;
display: flex;
justify-content: center;
align-items: center;
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
color: #fff; color: #fff;
@@ -703,11 +722,11 @@ const navigateToAgreement = (type: string) => {
// 验证码按钮 // 验证码按钮
.sms-btn { .sms-btn {
display: flex;
align-items: center;
justify-content: center;
height: 32px; height: 32px;
padding: 0 16px; padding: 0 16px;
display: flex;
justify-content: center;
align-items: center;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
border-radius: 8px; border-radius: 8px;
@@ -751,10 +770,10 @@ const navigateToAgreement = (type: string) => {
} }
.switch-link { .switch-link {
margin-left: 4px;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
color: #2563eb; color: #2563eb;
margin-left: 4px;
border-bottom: 1px solid #2563eb; border-bottom: 1px solid #2563eb;
} }
@@ -790,19 +809,13 @@ const navigateToAgreement = (type: string) => {
// 微信登录入口 // 微信登录入口
.wechat-entry { .wechat-entry {
display: flex; display: flex;
justify-content: center;
gap: 32px; gap: 32px;
justify-content: center;
} }
.wechat-icon { .wechat-icon-img {
width: 48px; width: 48px;
height: 48px; height: 48px;
display: flex;
align-items: center;
justify-content: center;
background-color: #22c55e;
border-radius: 50%;
box-shadow: 0 4px 6px -1px rgba(34, 197, 94, 0.3);
&:active { &:active {
transform: scale(0.95); transform: scale(0.95);
@@ -813,8 +826,8 @@ const navigateToAgreement = (type: string) => {
.agreement { .agreement {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
margin-top: 24px;
padding-top: 16px; padding-top: 16px;
margin-top: 24px;
border-top: 1px solid #f3f4f6; border-top: 1px solid #f3f4f6;
} }
@@ -824,8 +837,8 @@ const navigateToAgreement = (type: string) => {
.agreement-text { .agreement-text {
font-size: 12px; font-size: 12px;
color: #6b7280;
line-height: 1.5; line-height: 1.5;
color: #6b7280;
} }
:global(.dark) .agreement-text { :global(.dark) .agreement-text {
@@ -847,17 +860,37 @@ const navigateToAgreement = (type: string) => {
.bind-title { .bind-title {
display: block; display: block;
margin-bottom: 24px;
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
color: #111827; color: #111827;
text-align: center; text-align: center;
margin-bottom: 24px;
} }
:global(.dark) .bind-title { :global(.dark) .bind-title {
color: #fff; color: #fff;
} }
// 演示环境提示
.demo-tip {
display: flex;
justify-content: center;
margin-top: 12px;
}
.demo-tip-text {
padding: 4px 12px;
font-size: 12px;
color: #f59e0b;
background-color: #fef3c7;
border-radius: 4px;
}
:global(.dark) .demo-tip-text {
color: #fbbf24;
background-color: rgba(251, 191, 36, 0.1);
}
// 暗黑模式适配 // 暗黑模式适配
:deep(.wd-checkbox__label) { :deep(.wd-checkbox__label) {
display: flex; display: flex;
@@ -865,7 +898,7 @@ const navigateToAgreement = (type: string) => {
} }
:deep(.wd-popup__close) { :deep(.wd-popup__close) {
right: 16px;
top: 16px; top: 16px;
right: 16px;
} }
</style> </style>

View File

@@ -21,6 +21,9 @@
is-link is-link
@click="handleOpenDialog(DialogType.EMAIL)" @click="handleOpenDialog(DialogType.EMAIL)"
/> />
<!-- #ifdef MP-WEIXIN -->
<wd-cell title="微信" value="解绑" is-link @click="handleUnbindWechat" />
<!-- #endif -->
</wd-cell-group> </wd-cell-group>
</wd-card> </wd-card>
@@ -211,6 +214,25 @@ const mobileTimer = ref<ReturnType<typeof setInterval> | null>(null);
const emailCountdown = ref(0); const emailCountdown = ref(0);
const emailTimer = ref<ReturnType<typeof setTimeout> | null>(null); const emailTimer = ref<ReturnType<typeof setTimeout> | null>(null);
const handleUnbindWechat = async () => {
uni.showModal({
title: "提示",
content: "确定要解绑微信吗?解绑后将无法使用微信小程序登录",
success: async (res) => {
if (!res.confirm) return;
try {
await UserAPI.unbindSocial("WECHAT_MINI");
uni.showToast({ title: "解绑成功", icon: "success" });
loadUserProfile();
} catch (e) {
console.error("解绑微信失败", e);
uni.showToast({ title: "解绑失败", icon: "none" });
}
},
});
};
/** 加载用户信息 */ /** 加载用户信息 */
const loadUserProfile = async () => { const loadUserProfile = async () => {
userProfile.value = await UserAPI.getProfile(); userProfile.value = await UserAPI.getProfile();

View File

@@ -44,10 +44,10 @@ export const useUserStore = defineStore("user", () => {
}); });
}; };
// 微信小程序登录(个人小程序) // 微信小程序静默登录
const loginByWechatMiniapp = (code: string) => { const loginByWechatMiniapp = (code: string) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
AuthAPI.wechatMiniappLogin(code) AuthAPI.wechatMiniappSilentLogin(code)
.then((data) => { .then((data) => {
if (data.accessToken) { if (data.accessToken) {
setAccessToken(data.accessToken); setAccessToken(data.accessToken);

View File

@@ -1,4 +1,4 @@
/* eslint-disable */ /* eslint-disable */
/* prettier-ignore */ /* prettier-ignore */
// @ts-nocheck // @ts-nocheck
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
@@ -325,4 +325,4 @@ declare module 'vue' {
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']> readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']> readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
} }
} }

View File

@@ -1,4 +1,4 @@
/* eslint-disable */ /* eslint-disable */
/* prettier-ignore */ /* prettier-ignore */
// @ts-nocheck // @ts-nocheck
// Generated by vite-plugin-uni-pages // Generated by vite-plugin-uni-pages