mirror of
https://github.com/Cccc-owo/CheckInApp.git
synced 2026-06-17 05:56:29 +00:00
feat(frontend): show QR refresh in dialog
This commit is contained in:
@@ -27,6 +27,7 @@ import { useRouter } from '@/app/router'
|
|||||||
import StateBlock from '@/components/StateBlock.vue'
|
import StateBlock from '@/components/StateBlock.vue'
|
||||||
import { alertClass, cardClass, inputClass, sectionHeaderClass, toneClass } from '@/components/ui'
|
import { alertClass, cardClass, inputClass, sectionHeaderClass, toneClass } from '@/components/ui'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
import {
|
import {
|
||||||
cronLabel,
|
cronLabel,
|
||||||
@@ -58,6 +59,7 @@ const qrRefreshError = ref('')
|
|||||||
const qrRefreshImage = ref('')
|
const qrRefreshImage = ref('')
|
||||||
const qrRefreshSessionId = ref('')
|
const qrRefreshSessionId = ref('')
|
||||||
const qrRefreshSucceeded = ref(false)
|
const qrRefreshSucceeded = ref(false)
|
||||||
|
const qrRefreshDialogOpen = ref(false)
|
||||||
let pollTimer: number | undefined
|
let pollTimer: number | undefined
|
||||||
let qrRefreshPollTimer: number | undefined
|
let qrRefreshPollTimer: number | undefined
|
||||||
|
|
||||||
@@ -179,9 +181,24 @@ async function cancelQrRefresh(clearFeedback = true) {
|
|||||||
if (sessionId) await authApi.cancelQRCodeSession(sessionId).catch(() => undefined)
|
if (sessionId) await authApi.cancelQRCodeSession(sessionId).catch(() => undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function closeQrRefreshDialog() {
|
||||||
|
qrRefreshDialogOpen.value = false
|
||||||
|
await cancelQrRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleQrRefreshDialogOpenChange(open: boolean) {
|
||||||
|
if (open) {
|
||||||
|
qrRefreshDialogOpen.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await closeQrRefreshDialog()
|
||||||
|
}
|
||||||
|
|
||||||
async function requestQrRefresh() {
|
async function requestQrRefresh() {
|
||||||
if (!canRefreshToken.value || qrRefreshLoading.value) return
|
if (!canRefreshToken.value || qrRefreshLoading.value) return
|
||||||
|
|
||||||
|
qrRefreshDialogOpen.value = true
|
||||||
const alias = auth.state.user?.alias?.trim()
|
const alias = auth.state.user?.alias?.trim()
|
||||||
if (!alias) {
|
if (!alias) {
|
||||||
qrRefreshError.value = '当前用户缺少用户名,无法创建扫码刷新会话。'
|
qrRefreshError.value = '当前用户缺少用户名,无法创建扫码刷新会话。'
|
||||||
@@ -443,63 +460,53 @@ onBeforeUnmount(() => {
|
|||||||
<QrCode class="size-4" />
|
<QrCode class="size-4" />
|
||||||
{{ qrRefreshLoading ? '创建中' : '扫码刷新' }}
|
{{ qrRefreshLoading ? '创建中' : '扫码刷新' }}
|
||||||
</Button>
|
</Button>
|
||||||
<div
|
|
||||||
v-if="qrRefreshImage || qrRefreshInfo || qrRefreshError"
|
|
||||||
class="grid gap-3 rounded-lg border border-border bg-muted p-3"
|
|
||||||
>
|
|
||||||
<div class="flex items-start justify-between gap-3">
|
|
||||||
<div>
|
|
||||||
<div class="font-medium text-foreground">扫码刷新授权</div>
|
|
||||||
<div v-if="qrRefreshInfo" class="mt-1 text-muted-foreground">
|
|
||||||
{{ qrRefreshInfo }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
v-if="qrRefreshSessionId || qrRefreshImage"
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
type="button"
|
|
||||||
aria-label="关闭扫码刷新"
|
|
||||||
@click="cancelQrRefresh"
|
|
||||||
>
|
|
||||||
<X class="size-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div v-if="qrRefreshError" :class="alertClass.danger">
|
|
||||||
{{ qrRefreshError }}
|
|
||||||
</div>
|
|
||||||
<div v-if="qrRefreshSucceeded" :class="alertClass.success">授权刷新成功</div>
|
|
||||||
<div v-if="qrRefreshImage" class="rounded-lg border border-border bg-background p-3">
|
|
||||||
<img
|
|
||||||
:src="qrRefreshImageSrc"
|
|
||||||
alt="QQ 授权刷新二维码"
|
|
||||||
class="mx-auto size-44 rounded-md bg-background object-contain"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-wrap gap-2">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
type="button"
|
|
||||||
:disabled="qrRefreshLoading || !canRefreshToken"
|
|
||||||
@click="requestQrRefresh"
|
|
||||||
>
|
|
||||||
<RotateCw class="size-4" />
|
|
||||||
重新获取
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
v-if="qrRefreshSessionId || qrRefreshImage"
|
|
||||||
variant="ghost"
|
|
||||||
type="button"
|
|
||||||
@click="cancelQrRefresh"
|
|
||||||
>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<Dialog :open="qrRefreshDialogOpen" @update:open="handleQrRefreshDialogOpenChange">
|
||||||
|
<DialogContent class="gap-0 overflow-hidden p-0 sm:max-w-[420px]">
|
||||||
|
<DialogHeader class="border-b border-border bg-muted/55 px-5 py-4">
|
||||||
|
<DialogTitle class="flex items-center gap-2">
|
||||||
|
<QrCode class="size-5" />
|
||||||
|
扫码刷新授权
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div class="grid gap-4 p-5 text-sm">
|
||||||
|
<div v-if="qrRefreshInfo" class="text-muted-foreground">
|
||||||
|
{{ qrRefreshInfo }}
|
||||||
|
</div>
|
||||||
|
<div v-if="qrRefreshError" :class="alertClass.danger">
|
||||||
|
{{ qrRefreshError }}
|
||||||
|
</div>
|
||||||
|
<div v-if="qrRefreshSucceeded" :class="alertClass.success">授权刷新成功</div>
|
||||||
|
<div v-if="qrRefreshImage" class="rounded-lg border border-border bg-background p-4">
|
||||||
|
<img
|
||||||
|
:src="qrRefreshImageSrc"
|
||||||
|
alt="QQ 授权刷新二维码"
|
||||||
|
class="mx-auto size-56 max-w-full rounded-md bg-background object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<StateBlock v-else-if="qrRefreshLoading" title="正在创建二维码" type="loading" />
|
||||||
|
<div class="flex flex-wrap justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
type="button"
|
||||||
|
:disabled="qrRefreshLoading || !canRefreshToken"
|
||||||
|
@click="requestQrRefresh"
|
||||||
|
>
|
||||||
|
<RotateCw class="size-4" />
|
||||||
|
重新获取
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" type="button" @click="closeQrRefreshDialog">
|
||||||
|
<X class="size-4" />
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
<section :class="[cardClass, 'overflow-hidden']">
|
<section :class="[cardClass, 'overflow-hidden']">
|
||||||
<div :class="sectionHeaderClass">
|
<div :class="sectionHeaderClass">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|||||||
@@ -101,3 +101,12 @@ def test_dashboard_refresh_uses_qr_api_instead_of_login_redirect() -> None:
|
|||||||
assert "authApi.getQRCodeStatus" in dashboard
|
assert "authApi.getQRCodeStatus" in dashboard
|
||||||
assert "authApi.cancelQRCodeSession" in dashboard
|
assert "authApi.cancelQRCodeSession" in dashboard
|
||||||
assert "router.navigate('/login')" not in dashboard
|
assert "router.navigate('/login')" not in dashboard
|
||||||
|
|
||||||
|
|
||||||
|
def test_dashboard_qr_refresh_uses_dialog() -> None:
|
||||||
|
dashboard = (SRC_ROOT / "views" / "DashboardView.vue").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "@/components/ui/dialog" in dashboard
|
||||||
|
assert "qrRefreshDialogOpen" in dashboard
|
||||||
|
assert "<Dialog" in dashboard
|
||||||
|
assert "<DialogContent" in dashboard
|
||||||
|
|||||||
Reference in New Issue
Block a user