视频连线
This commit is contained in:
@@ -37,6 +37,10 @@ const login = (data) => {
|
||||
return post('/api/v1/auth/login', data, { needAuth: false })
|
||||
}
|
||||
|
||||
const getOAuthAuthorizeUrl = (provider) => {
|
||||
return get(`/api/v1/auth/oauth/${provider}/authorize`, {}, { needAuth: false })
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
* POST /api/v1/auth/logout
|
||||
@@ -107,6 +111,7 @@ const changePassword = (data) => {
|
||||
export default {
|
||||
register,
|
||||
login,
|
||||
getOAuthAuthorizeUrl,
|
||||
logout,
|
||||
refreshToken,
|
||||
getProfile,
|
||||
|
||||
@@ -31,6 +31,12 @@
|
||||
"navigationBarTitleText": "登录中"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/auth/oauth-callback",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录中"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/chat/index",
|
||||
"style": {
|
||||
|
||||
@@ -73,6 +73,18 @@
|
||||
<text class="btn-label">{{ loading ? '登录中…' : '登录' }}</text>
|
||||
</button>
|
||||
|
||||
<view class="oauth-section">
|
||||
<view class="oauth-divider">
|
||||
<view class="oauth-line"></view>
|
||||
<text class="oauth-text">其他登录方式</text>
|
||||
<view class="oauth-line"></view>
|
||||
</view>
|
||||
<view class="oauth-buttons">
|
||||
<button class="oauth-btn oauth-wechat" :disabled="oauthLoading" @tap="oauthLogin('wechat')">微信登录</button>
|
||||
<button class="oauth-btn oauth-qq" :disabled="oauthLoading" @tap="oauthLogin('qq')">QQ登录</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部链接 -->
|
||||
<view class="link-row">
|
||||
<text class="link-hint">还没有账号?</text>
|
||||
@@ -94,6 +106,7 @@ import { useWebSocketStore } from '@/store/websocket'
|
||||
import { useChatStore } from '@/store/chat'
|
||||
import { useContactStore } from '@/store/contact'
|
||||
import { useNotifyStore } from '@/store/notify'
|
||||
import authApi from '@/api/auth'
|
||||
|
||||
function readSsoQueryFromLocation() {
|
||||
try {
|
||||
@@ -125,6 +138,7 @@ export default {
|
||||
form: { account: '', password: '' },
|
||||
passwordVisible: false,
|
||||
loading: false,
|
||||
oauthLoading: false,
|
||||
focusState: { account: false, password: false },
|
||||
errors: { account: '', password: '' }
|
||||
}
|
||||
@@ -200,6 +214,29 @@ export default {
|
||||
|
||||
goRegister() {
|
||||
uni.navigateTo({ url: '/pages/auth/register' })
|
||||
},
|
||||
|
||||
async oauthLogin(provider) {
|
||||
if (this.oauthLoading) return
|
||||
this.oauthLoading = true
|
||||
try {
|
||||
const res = await authApi.getOAuthAuthorizeUrl(provider)
|
||||
const url = res && res.data && res.data.url
|
||||
if (!url) {
|
||||
uni.showToast({ title: '第三方登录未配置', icon: 'none' })
|
||||
return
|
||||
}
|
||||
// #ifdef H5
|
||||
window.location.href = url
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
uni.showToast({ title: '当前端暂不支持第三方登录', icon: 'none' })
|
||||
// #endif
|
||||
} catch (e) {
|
||||
console.error('第三方登录失败:', e)
|
||||
} finally {
|
||||
this.oauthLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,6 +399,54 @@ export default {
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.oauth-section {
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.oauth-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.oauth-line {
|
||||
flex: 1;
|
||||
height: 2rpx;
|
||||
background-color: #E2E8F0;
|
||||
}
|
||||
|
||||
.oauth-text {
|
||||
font-size: 24rpx;
|
||||
color: #94A3B8;
|
||||
}
|
||||
|
||||
.oauth-buttons {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
|
||||
.oauth-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
border-radius: 16rpx;
|
||||
font-size: 28rpx;
|
||||
color: #FFFFFF;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.oauth-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.oauth-wechat {
|
||||
background-color: #07C160;
|
||||
}
|
||||
|
||||
.oauth-qq {
|
||||
background-color: #12B7F5;
|
||||
}
|
||||
|
||||
/* ---- 底部链接 ---- */
|
||||
.link-row {
|
||||
display: flex;
|
||||
|
||||
131
frontend/src/pages/auth/oauth-callback.vue
Normal file
131
frontend/src/pages/auth/oauth-callback.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<view class="callback-page">
|
||||
<view class="callback-card">
|
||||
<view class="loading-dot"></view>
|
||||
<text class="callback-title">正在完成登录</text>
|
||||
<text class="callback-desc">请稍候,正在同步账号信息…</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { useWebSocketStore } from '@/store/websocket'
|
||||
import { useChatStore } from '@/store/chat'
|
||||
import { useContactStore } from '@/store/contact'
|
||||
import { useNotifyStore } from '@/store/notify'
|
||||
|
||||
function decodePayload(payload) {
|
||||
const normalized = String(payload || '').replace(/-/g, '+').replace(/_/g, '/')
|
||||
const padded = normalized + '='.repeat((4 - normalized.length % 4) % 4)
|
||||
const binary = atob(padded)
|
||||
return decodeURIComponent(Array.prototype.map.call(binary, c => {
|
||||
return `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`
|
||||
}).join(''))
|
||||
}
|
||||
|
||||
function readRawQueryParams() {
|
||||
const result = {}
|
||||
try {
|
||||
if (typeof window === 'undefined') return result
|
||||
const search = String(window.location.search || '').replace(/^\?/, '')
|
||||
const hash = String(window.location.hash || '')
|
||||
const hashQuery = hash.indexOf('?') >= 0 ? hash.slice(hash.indexOf('?') + 1) : ''
|
||||
;[search, hashQuery].forEach(raw => {
|
||||
if (!raw) return
|
||||
const params = new URLSearchParams(raw)
|
||||
params.forEach((value, key) => {
|
||||
if (result[key] === undefined) {
|
||||
result[key] = value
|
||||
}
|
||||
})
|
||||
})
|
||||
} catch (e) {}
|
||||
return result
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'OAuthCallbackPage',
|
||||
onLoad(query) {
|
||||
this.handleCallback({ ...readRawQueryParams(), ...(query || {}) })
|
||||
},
|
||||
methods: {
|
||||
async handleCallback(query) {
|
||||
try {
|
||||
if (query.status !== 'success') {
|
||||
const message = query.payload ? decodePayload(query.payload) : '第三方登录失败'
|
||||
uni.showToast({ title: message || '第三方登录失败', icon: 'none' })
|
||||
setTimeout(() => uni.redirectTo({ url: '/pages/auth/login' }), 1200)
|
||||
return
|
||||
}
|
||||
const loginData = JSON.parse(decodePayload(query.payload))
|
||||
const store = useUserStore()
|
||||
store.applyOAuthLogin(loginData)
|
||||
const wsStore = useWebSocketStore()
|
||||
wsStore.connect()
|
||||
useChatStore().initWsListeners()
|
||||
useContactStore().initWsListeners()
|
||||
const notifyStore = useNotifyStore()
|
||||
notifyStore.initWsListeners()
|
||||
notifyStore.fetchUnreadCount().catch(() => {})
|
||||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||
setTimeout(() => uni.reLaunch({ url: '/pages/index/index' }), 600)
|
||||
} catch (e) {
|
||||
console.error('OAuth 回调处理失败:', e)
|
||||
uni.showToast({ title: '登录失败', icon: 'none' })
|
||||
setTimeout(() => uni.redirectTo({ url: '/pages/auth/login' }), 1200)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.callback-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #F8FAFC;
|
||||
padding: 48rpx;
|
||||
}
|
||||
|
||||
.callback-card {
|
||||
width: 100%;
|
||||
max-width: 640rpx;
|
||||
padding: 64rpx 40rpx;
|
||||
border-radius: 24rpx;
|
||||
background-color: #FFFFFF;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.loading-dot {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border-radius: 50%;
|
||||
border: 6rpx solid #DBEAFE;
|
||||
border-top-color: #2563EB;
|
||||
animation: spin 800ms linear infinite;
|
||||
}
|
||||
|
||||
.callback-title {
|
||||
margin-top: 32rpx;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #1E293B;
|
||||
}
|
||||
|
||||
.callback-desc {
|
||||
margin-top: 12rpx;
|
||||
font-size: 26rpx;
|
||||
color: #64748B;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
@@ -106,6 +106,11 @@ export const useUserStore = defineStore('user', () => {
|
||||
return res.data
|
||||
}
|
||||
|
||||
const applyOAuthLogin = (data) => {
|
||||
_handleAuthSuccess(data)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
* 1. 调用后端 API 从 Redis 删除 Token(使 Token 立即失效)
|
||||
@@ -207,6 +212,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
// actions
|
||||
login,
|
||||
register,
|
||||
applyOAuthLogin,
|
||||
logout,
|
||||
getProfile,
|
||||
updateProfile,
|
||||
|
||||
Reference in New Issue
Block a user