mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-04 23:18:44 +08:00
Merge pull request #440 from DDSRem-Dev/v2
This commit is contained in:
@@ -1,16 +1,22 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import QRCode from 'qrcode'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useDisplay } from 'vuetify'
|
import { useDisplay } from 'vuetify'
|
||||||
|
|
||||||
|
// 常量定义
|
||||||
|
const AUTH_WINDOW_WIDTH = 600
|
||||||
|
const AUTH_WINDOW_HEIGHT = 700
|
||||||
|
const POLL_INTERVAL = 2000
|
||||||
|
const AUTH_STATUS_SUCCESS = 2
|
||||||
|
const AUTH_STATUS_FAILED = -1
|
||||||
|
|
||||||
// 显示器宽度
|
// 显示器宽度
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
|
|
||||||
// 多语言支持
|
// 多语言支持
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
// 定义输入
|
// Props 定义
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
conf: {
|
conf: {
|
||||||
type: Object as PropType<{ [key: string]: any }>,
|
type: Object as PropType<{ [key: string]: any }>,
|
||||||
@@ -18,27 +24,40 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 定义事件
|
// Events 定义
|
||||||
const emit = defineEmits(['done', 'close'])
|
const emit = defineEmits(['done', 'close'])
|
||||||
|
|
||||||
// 二维码内容
|
// 响应式状态
|
||||||
const qrCodeContent = ref('')
|
const authUrl = ref('')
|
||||||
|
const authState = ref('')
|
||||||
|
const text = ref('')
|
||||||
|
const alertType = ref<'success' | 'info' | 'error' | 'warning'>('info')
|
||||||
|
|
||||||
// 二维码图片 base64
|
// 授权窗口引用
|
||||||
const qrCodeImage = ref('')
|
let authWindow: Window | null = null
|
||||||
|
let pollTimer: NodeJS.Timeout | undefined
|
||||||
|
|
||||||
// 下方的提示信息
|
// 清理资源
|
||||||
const text = ref(t('dialog.u115Auth.scanQrCode'))
|
function cleanup() {
|
||||||
|
if (pollTimer) {
|
||||||
|
clearTimeout(pollTimer)
|
||||||
|
pollTimer = undefined
|
||||||
|
}
|
||||||
|
if (authWindow && !authWindow.closed) {
|
||||||
|
authWindow.close()
|
||||||
|
authWindow = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 提醒类型
|
// 设置提示消息
|
||||||
const alertType = ref<'success' | 'info' | 'error' | 'warning' | undefined>('info')
|
function setMessage(type: typeof alertType.value, message: string) {
|
||||||
|
alertType.value = type
|
||||||
|
text.value = message
|
||||||
|
}
|
||||||
|
|
||||||
// timeout定时器
|
// 完成授权
|
||||||
let timeoutTimer: NodeJS.Timeout | undefined = undefined
|
function handleDone() {
|
||||||
|
cleanup()
|
||||||
// 完成
|
|
||||||
async function handleDone() {
|
|
||||||
clearTimeout(timeoutTimer)
|
|
||||||
emit('done')
|
emit('done')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,78 +66,118 @@ async function handleReset() {
|
|||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await api.get('/storage/reset/u115')
|
const result: { [key: string]: any } = await api.get('/storage/reset/u115')
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
// 重置成功
|
setMessage('success', t('dialog.u115Auth.authSuccess'))
|
||||||
alertType.value = 'success'
|
|
||||||
handleDone()
|
handleDone()
|
||||||
} else {
|
|
||||||
alertType.value = 'error'
|
|
||||||
text.value = result.message
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
else {
|
||||||
console.error(e)
|
setMessage('error', result.message || t('dialog.u115Auth.authFailed'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
catch (error) {
|
||||||
// 调用/u115/qrcode api生成二维码
|
console.error('Reset failed:', error)
|
||||||
async function getQrcode() {
|
setMessage('error', t('dialog.u115Auth.authFailed'))
|
||||||
try {
|
|
||||||
const result: { [key: string]: any } = await api.get('/storage/qrcode/u115')
|
|
||||||
if (result.success && result.data) {
|
|
||||||
qrCodeContent.value = result.data.codeContent
|
|
||||||
// 生成二维码图片
|
|
||||||
qrCodeImage.value = await QRCode.toDataURL(result.data.codeContent, {
|
|
||||||
width: 200,
|
|
||||||
margin: 1,
|
|
||||||
})
|
|
||||||
timeoutTimer = setTimeout(checkQrcode, 3000)
|
|
||||||
} else {
|
|
||||||
text.value = result.message
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用/aliyun/check api验证二维码
|
// 获取授权URL
|
||||||
async function checkQrcode() {
|
async function fetchAuthUrl() {
|
||||||
|
try {
|
||||||
|
const result: { [key: string]: any } = await api.get('/storage/auth_url/u115')
|
||||||
|
|
||||||
|
if (result.success && result.data) {
|
||||||
|
authUrl.value = result.data.authUrl
|
||||||
|
authState.value = result.data.state
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setMessage('error', result.message || t('dialog.u115Auth.urlFetchFailed'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('Fetch auth URL failed:', error)
|
||||||
|
setMessage('error', t('dialog.u115Auth.urlFetchFailed'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开授权窗口
|
||||||
|
function openAuthWindow() {
|
||||||
|
if (!authUrl.value) {
|
||||||
|
setMessage('error', t('dialog.u115Auth.urlEmpty'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const left = (window.screen.width - AUTH_WINDOW_WIDTH) / 2
|
||||||
|
const top = (window.screen.height - AUTH_WINDOW_HEIGHT) / 2
|
||||||
|
const features = [
|
||||||
|
`width=${AUTH_WINDOW_WIDTH}`,
|
||||||
|
`height=${AUTH_WINDOW_HEIGHT}`,
|
||||||
|
`left=${left}`,
|
||||||
|
`top=${top}`,
|
||||||
|
'toolbar=no',
|
||||||
|
'location=no',
|
||||||
|
'status=no',
|
||||||
|
'menubar=no',
|
||||||
|
'scrollbars=yes',
|
||||||
|
'resizable=yes',
|
||||||
|
].join(',')
|
||||||
|
|
||||||
|
authWindow = window.open(authUrl.value, '115授权', features)
|
||||||
|
|
||||||
|
if (authWindow) {
|
||||||
|
setMessage('info', t('dialog.u115Auth.authorizing'))
|
||||||
|
pollTimer = setTimeout(checkAuthStatus, POLL_INTERVAL)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setMessage('error', t('dialog.u115Auth.popupBlocked'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查授权状态
|
||||||
|
async function checkAuthStatus() {
|
||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await api.get('/storage/check/u115')
|
const result: { [key: string]: any } = await api.get('/storage/check/u115')
|
||||||
|
|
||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
const status = result.data.status
|
const { status, tip } = result.data
|
||||||
text.value = result.data.tip
|
|
||||||
if (status == 0) {
|
if (status === AUTH_STATUS_SUCCESS) {
|
||||||
alertType.value = 'info'
|
// 授权成功
|
||||||
// 新建、待扫码
|
setMessage('success', t('dialog.u115Auth.authSuccess'))
|
||||||
clearTimeout(timeoutTimer)
|
|
||||||
timeoutTimer = setTimeout(checkQrcode, 3000)
|
|
||||||
} else if (status == 1) {
|
|
||||||
// 已扫码
|
|
||||||
alertType.value = 'info'
|
|
||||||
text.value = t('dialog.u115Auth.scanned')
|
|
||||||
clearTimeout(timeoutTimer)
|
|
||||||
timeoutTimer = setTimeout(checkQrcode, 3000)
|
|
||||||
} else if (status == 2) {
|
|
||||||
// 已确认完成
|
|
||||||
alertType.value = 'success'
|
|
||||||
handleDone()
|
handleDone()
|
||||||
} else {
|
return
|
||||||
// 过期或者已取消
|
|
||||||
alertType.value = 'error'
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
alertType.value = 'error'
|
if (status === AUTH_STATUS_FAILED) {
|
||||||
text.value = result.message
|
// 授权失败或过期
|
||||||
|
setMessage('error', tip || t('dialog.u115Auth.authFailed'))
|
||||||
|
cleanup()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// status === 0 或 1,继续等待
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('Check auth status failed:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查窗口是否被用户关闭
|
||||||
|
if (authWindow?.closed) {
|
||||||
|
setMessage('warning', t('dialog.u115Auth.authCanceled'))
|
||||||
|
cleanup()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 继续轮询
|
||||||
|
pollTimer = setTimeout(checkAuthStatus, POLL_INTERVAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
// 生命周期钩子
|
||||||
await getQrcode()
|
onMounted(() => {
|
||||||
|
fetchAuthUrl()
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (timeoutTimer) clearTimeout(timeoutTimer)
|
cleanup()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -126,37 +185,63 @@ onUnmounted(() => {
|
|||||||
<VDialog width="40rem" scrollable :fullscreen="!display.mdAndUp.value">
|
<VDialog width="40rem" scrollable :fullscreen="!display.mdAndUp.value">
|
||||||
<VCard>
|
<VCard>
|
||||||
<VDialogCloseBtn @click="emit('close')" />
|
<VDialogCloseBtn @click="emit('close')" />
|
||||||
|
|
||||||
<VCardItem>
|
<VCardItem>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VIcon icon="mdi-qrcode" class="me-2" />
|
<VIcon icon="mdi-shield-key" class="me-2" />
|
||||||
</template>
|
</template>
|
||||||
<VCardTitle>
|
<VCardTitle>
|
||||||
{{ t('dialog.u115Auth.loginTitle') }}
|
{{ t('dialog.u115Auth.loginTitle') }}
|
||||||
</VCardTitle>
|
</VCardTitle>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
|
|
||||||
<VDivider />
|
<VDivider />
|
||||||
|
|
||||||
<VCardText class="pt-2 flex flex-col items-center justify-center">
|
<VCardText class="pt-2 flex flex-col items-center justify-center">
|
||||||
<div class="mt-6 rounded text-center p-3 border">
|
<!-- 授权按钮 -->
|
||||||
<VImg class="mx-auto" :src="qrCodeImage" width="200" height="200">
|
<div class="mt-6 mb-4 text-center">
|
||||||
<template #placeholder>
|
<VBtn
|
||||||
<div class="w-full h-full">
|
size="x-large"
|
||||||
<VSkeletonLoader class="object-cover aspect-w-1 aspect-h-1" />
|
color="primary"
|
||||||
</div>
|
prepend-icon="mdi-login"
|
||||||
</template>
|
:disabled="!authUrl"
|
||||||
</VImg>
|
class="px-8"
|
||||||
|
@click="openAuthWindow"
|
||||||
|
>
|
||||||
|
{{ t('dialog.u115Auth.openAuthWindow') }}
|
||||||
|
</VBtn>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<VAlert variant="tonal" :type="alertType" class="my-4 text-center" :text="text">
|
<!-- 状态提示 -->
|
||||||
|
<div v-if="text" class="w-full">
|
||||||
|
<VAlert
|
||||||
|
variant="tonal"
|
||||||
|
:type="alertType"
|
||||||
|
:text="text"
|
||||||
|
class="my-4 text-center"
|
||||||
|
>
|
||||||
<template #prepend />
|
<template #prepend />
|
||||||
</VAlert>
|
</VAlert>
|
||||||
</div>
|
</div>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
|
|
||||||
<VCardActions>
|
<VCardActions>
|
||||||
<VBtn color="error" @click="handleReset" prepend-icon="mdi-restore" class="px-5 me-3">
|
<VBtn
|
||||||
|
color="error"
|
||||||
|
prepend-icon="mdi-restore"
|
||||||
|
class="px-5 me-3"
|
||||||
|
@click="handleReset"
|
||||||
|
>
|
||||||
{{ t('dialog.u115Auth.reset') }}
|
{{ t('dialog.u115Auth.reset') }}
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
|
||||||
<VSpacer />
|
<VSpacer />
|
||||||
<VBtn @click="handleDone" prepend-icon="mdi-check" class="px-5 me-3">
|
|
||||||
|
<VBtn
|
||||||
|
prepend-icon="mdi-check"
|
||||||
|
class="px-5 me-3"
|
||||||
|
@click="handleDone"
|
||||||
|
>
|
||||||
{{ t('dialog.u115Auth.complete') }}
|
{{ t('dialog.u115Auth.complete') }}
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</VCardActions>
|
</VCardActions>
|
||||||
|
|||||||
@@ -2024,9 +2024,15 @@ export default {
|
|||||||
'Before sharing, please ensure the workflow does not contain sensitive information such as PassKey in RSS links to avoid information leakage.',
|
'Before sharing, please ensure the workflow does not contain sensitive information such as PassKey in RSS links to avoid information leakage.',
|
||||||
},
|
},
|
||||||
u115Auth: {
|
u115Auth: {
|
||||||
loginTitle: '115 Cloud Login',
|
loginTitle: '115 Cloud Authorization',
|
||||||
scanQrCode: 'Please scan with WeChat or 115 client',
|
openAuthWindow: 'Open Authorization Window',
|
||||||
scanned: 'Scanned, please confirm login',
|
authorizing: 'Please complete authorization in the new window...',
|
||||||
|
authSuccess: 'Authorization successful!',
|
||||||
|
authFailed: 'Authorization failed or expired',
|
||||||
|
authCanceled: 'Authorization canceled, please try again',
|
||||||
|
urlEmpty: 'Authorization URL is empty',
|
||||||
|
urlFetchFailed: 'Failed to fetch authorization URL',
|
||||||
|
popupBlocked: 'Unable to open authorization window, please check browser popup settings',
|
||||||
complete: 'Complete',
|
complete: 'Complete',
|
||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1995,9 +1995,15 @@ export default {
|
|||||||
securityWarningMessage: '分享前请确保工作流没有敏感信息,比如RSS链接中的PassKey等,避免产生信息泄露。',
|
securityWarningMessage: '分享前请确保工作流没有敏感信息,比如RSS链接中的PassKey等,避免产生信息泄露。',
|
||||||
},
|
},
|
||||||
u115Auth: {
|
u115Auth: {
|
||||||
loginTitle: '115网盘登录',
|
loginTitle: '115网盘授权',
|
||||||
scanQrCode: '请使用微信或115客户端扫码',
|
openAuthWindow: '打开授权窗口',
|
||||||
scanned: '已扫码,请确认登录',
|
authorizing: '请在新窗口中完成授权...',
|
||||||
|
authSuccess: '授权成功!',
|
||||||
|
authFailed: '授权失败或已过期',
|
||||||
|
authCanceled: '授权已取消,请重试',
|
||||||
|
urlEmpty: '授权URL为空',
|
||||||
|
urlFetchFailed: '获取授权URL失败',
|
||||||
|
popupBlocked: '无法打开授权窗口,请检查浏览器弹窗设置',
|
||||||
complete: '完成',
|
complete: '完成',
|
||||||
reset: '重置',
|
reset: '重置',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1996,9 +1996,15 @@ export default {
|
|||||||
securityWarningMessage: '分享前請確保工作流沒有敏感資訊,比如RSS連結中的PassKey等,避免產生資訊洩露。',
|
securityWarningMessage: '分享前請確保工作流沒有敏感資訊,比如RSS連結中的PassKey等,避免產生資訊洩露。',
|
||||||
},
|
},
|
||||||
u115Auth: {
|
u115Auth: {
|
||||||
loginTitle: '115網盤登錄',
|
loginTitle: '115網盤授權',
|
||||||
scanQrCode: '請使用微信或115客戶端掃碼',
|
openAuthWindow: '打開授權窗口',
|
||||||
scanned: '已掃碼,請確認登錄',
|
authorizing: '請在新窗口中完成授權...',
|
||||||
|
authSuccess: '授權成功!',
|
||||||
|
authFailed: '授權失敗或已過期',
|
||||||
|
authCanceled: '授權已取消,請重試',
|
||||||
|
urlEmpty: '授權URL為空',
|
||||||
|
urlFetchFailed: '獲取授權URL失敗',
|
||||||
|
popupBlocked: '無法打開授權窗口,請檢查瀏覽器彈窗設置',
|
||||||
complete: '完成',
|
complete: '完成',
|
||||||
reset: '重置',
|
reset: '重置',
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user