Merge pull request #440 from DDSRem-Dev/v2

This commit is contained in:
jxxghp
2026-01-29 22:22:03 +08:00
committed by GitHub
4 changed files with 196 additions and 93 deletions
+169 -84
View File
@@ -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>
+9 -3
View File
@@ -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',
}, },
+9 -3
View File
@@ -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: '重置',
}, },
+9 -3
View File
@@ -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: '重置',
}, },