mirror of
https://github.com/Cccc-owo/CheckInApp.git
synced 2026-06-17 14:06:28 +00:00
frontend: use composables
This commit is contained in:
@@ -50,6 +50,7 @@
|
||||
import { ref, computed, watch, onBeforeUnmount } from 'vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useBreakpoint } from '@/composables/useBreakpoint'
|
||||
import { usePollStatus } from '@/composables/usePollStatus'
|
||||
import { message } from 'ant-design-vue'
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
@@ -73,6 +74,13 @@ const emit = defineEmits(['update:visible', 'success', 'error'])
|
||||
const authStore = useAuthStore()
|
||||
const { isMobile } = useBreakpoint()
|
||||
|
||||
// 使用轮询 composable
|
||||
const { startPolling: startQRPolling, stopPolling } = usePollStatus({
|
||||
interval: 2000,
|
||||
maxRetries: 90, // 3分钟 = 180秒 / 2秒间隔 = 90次
|
||||
backoff: false
|
||||
})
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (val) => emit('update:visible', val),
|
||||
@@ -85,7 +93,6 @@ const errorMessage = ref('')
|
||||
const countdown = ref(180) // 倒计时 3 分钟
|
||||
const progress = ref(100)
|
||||
|
||||
let pollingTimer = null
|
||||
let countdownTimer = null
|
||||
|
||||
// 获取二维码
|
||||
@@ -97,8 +104,48 @@ const fetchQRCode = async () => {
|
||||
qrcodeUrl.value = `data:image/png;base64,${result.qrcode_base64}`
|
||||
status.value = 'pending'
|
||||
|
||||
// 开始轮询扫码状态
|
||||
startPolling()
|
||||
// 开始轮询扫码状态(使用 composable)
|
||||
startQRPolling(
|
||||
async () => {
|
||||
const result = await authStore.checkQRCodeStatus(sessionId.value)
|
||||
|
||||
// 检查是否完成(成功、过期或失败)
|
||||
const completed = result.status === 'expired' || result.status === 'failed' || result.success
|
||||
|
||||
return {
|
||||
completed,
|
||||
success: result.success === true,
|
||||
data: result
|
||||
}
|
||||
},
|
||||
{
|
||||
onSuccess: (result) => {
|
||||
status.value = 'success'
|
||||
stopCountdown()
|
||||
message.success('登录成功!')
|
||||
|
||||
// 延迟关闭对话框
|
||||
setTimeout(() => {
|
||||
emit('success', result.user)
|
||||
handleClose()
|
||||
}, 1500)
|
||||
},
|
||||
onFailure: (result) => {
|
||||
if (result.status === 'expired') {
|
||||
status.value = 'expired'
|
||||
} else {
|
||||
status.value = 'failed'
|
||||
errorMessage.value = result.message || '扫码失败'
|
||||
}
|
||||
stopCountdown()
|
||||
},
|
||||
onTimeout: () => {
|
||||
status.value = 'expired'
|
||||
stopCountdown()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
startCountdown()
|
||||
} catch (error) {
|
||||
status.value = 'failed'
|
||||
@@ -107,57 +154,6 @@ const fetchQRCode = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 开始轮询扫码状态
|
||||
const startPolling = () => {
|
||||
if (pollingTimer) {
|
||||
clearInterval(pollingTimer)
|
||||
}
|
||||
|
||||
pollingTimer = setInterval(async () => {
|
||||
try {
|
||||
const result = await authStore.checkQRCodeStatus(sessionId.value)
|
||||
|
||||
if (result.success) {
|
||||
// 扫码成功
|
||||
status.value = 'success'
|
||||
stopPolling()
|
||||
stopCountdown()
|
||||
|
||||
message.success('登录成功!')
|
||||
|
||||
// 延迟关闭对话框
|
||||
setTimeout(() => {
|
||||
emit('success', result.user)
|
||||
handleClose()
|
||||
}, 1500)
|
||||
} else if (result.status === 'expired') {
|
||||
// 二维码过期
|
||||
status.value = 'expired'
|
||||
stopPolling()
|
||||
stopCountdown()
|
||||
} else if (result.status === 'failed') {
|
||||
// 扫码失败
|
||||
status.value = 'failed'
|
||||
errorMessage.value = result.message || '扫码失败'
|
||||
stopPolling()
|
||||
stopCountdown()
|
||||
}
|
||||
// 否则继续轮询(pending 状态)
|
||||
} catch (error) {
|
||||
console.error('轮询扫码状态失败:', error)
|
||||
// 继续轮询,不中断
|
||||
}
|
||||
}, 2000) // 每 2 秒轮询一次
|
||||
}
|
||||
|
||||
// 停止轮询
|
||||
const stopPolling = () => {
|
||||
if (pollingTimer) {
|
||||
clearInterval(pollingTimer)
|
||||
pollingTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
// 开始倒计时
|
||||
const startCountdown = () => {
|
||||
countdown.value = 180
|
||||
@@ -172,7 +168,7 @@ const startCountdown = () => {
|
||||
|
||||
if (countdown.value <= 0) {
|
||||
status.value = 'expired'
|
||||
stopPolling()
|
||||
stopPolling() // 停止轮询
|
||||
stopCountdown()
|
||||
}
|
||||
}, 1000)
|
||||
@@ -193,7 +189,7 @@ const refreshQRCode = () => {
|
||||
|
||||
// 关闭对话框
|
||||
const handleClose = () => {
|
||||
stopPolling()
|
||||
stopPolling() // 停止轮询
|
||||
stopCountdown()
|
||||
|
||||
// 如果有未完成的会话,取消它
|
||||
@@ -250,30 +246,50 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
color: #52c41a;
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.dark .success-icon {
|
||||
color: #81c784;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
color: #faad14;
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
.dark .warning-icon {
|
||||
color: #ffb74d;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
color: #ff4d4f;
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.dark .error-icon {
|
||||
color: #ef5350;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
margin-top: 20px;
|
||||
font-size: 16px;
|
||||
color: #606266;
|
||||
color: var(--md-sys-color-on-surface-variant);
|
||||
}
|
||||
|
||||
.status-text.success {
|
||||
color: #52c41a;
|
||||
color: #4caf50;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dark .status-text.success {
|
||||
color: #81c784;
|
||||
}
|
||||
|
||||
.status-text.error {
|
||||
color: #ff4d4f;
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.dark .status-text.error {
|
||||
color: #ef5350;
|
||||
}
|
||||
|
||||
.qrcode-wrapper {
|
||||
@@ -286,22 +302,22 @@ onBeforeUnmount(() => {
|
||||
.qrcode-image {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border: 1px solid var(--md-sys-color-outline-variant);
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
background-color: var(--md-sys-color-surface);
|
||||
}
|
||||
|
||||
.hint-text {
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
color: #8c8c8c;
|
||||
color: var(--md-sys-color-on-surface-variant);
|
||||
}
|
||||
|
||||
.countdown-text {
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
color: var(--md-sys-color-on-surface-variant);
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
|
||||
@@ -175,12 +175,20 @@ import { useUserStore } from '@/stores/user'
|
||||
import { useTaskStore } from '@/stores/task'
|
||||
import { useCheckInStore } from '@/stores/checkIn'
|
||||
import { formatDateTime } from '@/utils/helpers'
|
||||
import { usePollStatus } from '@/composables/usePollStatus'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const userStore = useUserStore()
|
||||
const taskStore = useTaskStore()
|
||||
const checkInStore = useCheckInStore()
|
||||
|
||||
// 使用轮询 composable
|
||||
const { startPolling } = usePollStatus({
|
||||
interval: 2000, // 每 2 秒轮询一次
|
||||
maxRetries: 15, // 最多 15 次 (30 秒)
|
||||
backoff: false // 不使用指数退避
|
||||
})
|
||||
|
||||
const tokenStatusLoading = ref(false)
|
||||
const checkInLoading = ref(false)
|
||||
const selectedTaskId = ref(null)
|
||||
@@ -260,48 +268,34 @@ const handleCheckIn = async () => {
|
||||
// 显示提示消息
|
||||
message.info('打卡任务已启动,正在后台处理...')
|
||||
|
||||
// 用于存储 interval ID,以便在超时时清理
|
||||
let pollIntervalId = null
|
||||
|
||||
// 开始轮询检查打卡状态
|
||||
pollIntervalId = setInterval(async () => {
|
||||
try {
|
||||
// 使用轮询 composable 检查打卡状态
|
||||
startPolling(
|
||||
async () => {
|
||||
const status = await taskStore.getCheckInRecordStatus(recordId)
|
||||
|
||||
// 只要状态不是 pending,说明打卡请求已经处理完成
|
||||
if (status.status !== 'pending') {
|
||||
clearInterval(pollIntervalId)
|
||||
checkInLoading.value = false
|
||||
|
||||
if (status.status === 'success') {
|
||||
// 打卡成功
|
||||
message.success('打卡成功!')
|
||||
checkInStore.fetchMyRecords({ limit: 1 })
|
||||
} else {
|
||||
// 打卡失败或其他状态 (failure, out_of_time, unknown 等)
|
||||
const errorMsg = status.error_message || status.response_text || '打卡失败'
|
||||
message.error(errorMsg)
|
||||
checkInStore.fetchMyRecords({ limit: 1 })
|
||||
}
|
||||
return {
|
||||
completed: status.status !== 'pending',
|
||||
success: status.status === 'success',
|
||||
data: status
|
||||
}
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
checkInLoading.value = false
|
||||
message.success('打卡成功!')
|
||||
checkInStore.fetchMyRecords({ limit: 1 })
|
||||
},
|
||||
onFailure: (statusData) => {
|
||||
checkInLoading.value = false
|
||||
const errorMsg = statusData.error_message || statusData.response_text || '打卡失败'
|
||||
message.error(errorMsg)
|
||||
checkInStore.fetchMyRecords({ limit: 1 })
|
||||
},
|
||||
onTimeout: () => {
|
||||
checkInLoading.value = false
|
||||
message.warning('打卡处理时间较长,请稍后查看打卡记录')
|
||||
}
|
||||
// status === 'pending' 时继续轮询
|
||||
} catch (error) {
|
||||
// 查询状态失败,停止轮询
|
||||
console.error('轮询状态失败:', error)
|
||||
clearInterval(pollIntervalId)
|
||||
checkInLoading.value = false
|
||||
message.error('查询打卡状态失败')
|
||||
}
|
||||
}, 2000) // 每 2 秒查询一次
|
||||
|
||||
// 设置超时保护(30 秒后停止轮询)
|
||||
setTimeout(() => {
|
||||
if (checkInLoading.value) {
|
||||
clearInterval(pollIntervalId)
|
||||
checkInLoading.value = false
|
||||
message.warning('打卡处理时间较长,请稍后查看打卡记录')
|
||||
}
|
||||
}, 30000)
|
||||
)
|
||||
|
||||
} catch (error) {
|
||||
console.error('启动打卡失败:', error)
|
||||
|
||||
@@ -216,7 +216,7 @@ const handleQRCodeLogin = async () => {
|
||||
// 显示 QR 码弹窗
|
||||
qrcodeVisible.value = true
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error)
|
||||
// 表单验证失败,不需要打印错误(由 Ant Design 自动显示错误提示)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -370,12 +370,20 @@ import { useBreakpoint } from '@/composables/useBreakpoint'
|
||||
import { useTaskStore } from '@/stores/task'
|
||||
import { useTemplateStore } from '@/stores/template'
|
||||
import { copyToClipboard, formatDateTime } from '@/utils/helpers'
|
||||
import { usePollStatus } from '@/composables/usePollStatus'
|
||||
|
||||
const router = useRouter()
|
||||
const taskStore = useTaskStore()
|
||||
const templateStore = useTemplateStore()
|
||||
const { isMobile } = useBreakpoint()
|
||||
|
||||
// 使用轮询 composable
|
||||
const { startPolling } = usePollStatus({
|
||||
interval: 2000,
|
||||
maxRetries: 15,
|
||||
backoff: false
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const showCreateDialog = ref(false)
|
||||
const submitting = ref(false)
|
||||
@@ -675,48 +683,34 @@ const handleCheckIn = async (taskId) => {
|
||||
// 显示提示消息
|
||||
message.info('打卡任务已启动,正在后台处理...')
|
||||
|
||||
// 用于存储 interval ID,以便在超时时清理
|
||||
let pollIntervalId = null
|
||||
|
||||
// 开始轮询检查打卡状态
|
||||
pollIntervalId = setInterval(async () => {
|
||||
try {
|
||||
// 使用轮询 composable 检查打卡状态
|
||||
startPolling(
|
||||
async () => {
|
||||
const status = await taskStore.getCheckInRecordStatus(recordId)
|
||||
|
||||
// 只要状态不是 pending,说明打卡请求已经处理完成
|
||||
if (status.status !== 'pending') {
|
||||
clearInterval(pollIntervalId)
|
||||
checkInLoading.value[taskId] = false
|
||||
|
||||
if (status.status === 'success') {
|
||||
// 打卡成功
|
||||
message.success('打卡成功!')
|
||||
await fetchTasks()
|
||||
} else {
|
||||
// 打卡失败或其他状态 (failure, out_of_time, unknown 等)
|
||||
const errorMsg = status.error_message || status.response_text || '打卡失败'
|
||||
message.error(errorMsg)
|
||||
await fetchTasks()
|
||||
}
|
||||
return {
|
||||
completed: status.status !== 'pending',
|
||||
success: status.status === 'success',
|
||||
data: status
|
||||
}
|
||||
},
|
||||
{
|
||||
onSuccess: async () => {
|
||||
checkInLoading.value[taskId] = false
|
||||
message.success('打卡成功!')
|
||||
await fetchTasks()
|
||||
},
|
||||
onFailure: async (statusData) => {
|
||||
checkInLoading.value[taskId] = false
|
||||
const errorMsg = statusData.error_message || statusData.response_text || '打卡失败'
|
||||
message.error(errorMsg)
|
||||
await fetchTasks()
|
||||
},
|
||||
onTimeout: () => {
|
||||
checkInLoading.value[taskId] = false
|
||||
message.warning('打卡处理时间较长,请稍后查看打卡记录')
|
||||
}
|
||||
// status === 'pending' 时继续轮询
|
||||
} catch (error) {
|
||||
// 查询状态失败,停止轮询
|
||||
console.error('轮询状态失败:', error)
|
||||
clearInterval(pollIntervalId)
|
||||
checkInLoading.value[taskId] = false
|
||||
message.error('查询打卡状态失败')
|
||||
}
|
||||
}, 2000) // 每 2 秒查询一次
|
||||
|
||||
// 设置超时保护(30 秒后停止轮询)
|
||||
setTimeout(() => {
|
||||
if (checkInLoading.value[taskId]) {
|
||||
clearInterval(pollIntervalId)
|
||||
checkInLoading.value[taskId] = false
|
||||
message.warning('打卡处理时间较长,请稍后查看打卡记录')
|
||||
}
|
||||
}, 30000)
|
||||
)
|
||||
|
||||
} catch (error) {
|
||||
console.error('启动打卡失败:', error)
|
||||
|
||||
Reference in New Issue
Block a user