722 lines
21 KiB
TypeScript
722 lines
21 KiB
TypeScript
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
|
import { showConfirm, showSuccess } from '@/lib/feedback'
|
|
import {
|
|
closeAdminManualRedeemTask,
|
|
confirmAdminManualRedeemRole,
|
|
createAdminManualRedeemSession,
|
|
createAdminManualRedeemTask,
|
|
fetchAdminManualRedeemDetail,
|
|
fetchAdminManualRedeemSessionSummary,
|
|
fetchAdminManualRedeemSkuSuggestions,
|
|
fetchAdminTaskScreenshot,
|
|
refreshAdminManualRedeemSession,
|
|
redeemAdminManualRedeem,
|
|
removeAdminManualRedeemSession,
|
|
} from '@/services/admin'
|
|
import type { AdminInventorySkuSuggestion, AdminManualRedeemDetail } from '@/types/admin'
|
|
import type { TencentLoginType } from '@/types/tencent/session'
|
|
|
|
import { notifyTencentActionError } from './tencent/session-errors'
|
|
import {
|
|
useSessionPolling,
|
|
useSessionQrDisplay,
|
|
useSessionRoleFacts,
|
|
useSessionResultFacts,
|
|
buildRedeemBlockedReason,
|
|
resolveTaskStatusLabel,
|
|
syncLoginTypeFromDetail,
|
|
mergeSessionDetail,
|
|
shouldRefreshQrImage,
|
|
shouldKeepPolling,
|
|
DEFAULT_LOGIN_TYPE,
|
|
} from './shared'
|
|
|
|
export function useAdminManualRedeemPage() {
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
|
|
// ── Form state (admin-only) ────────────────────────────────────────
|
|
const form = reactive({
|
|
proofValue: '',
|
|
skuCode: '',
|
|
remark: '',
|
|
})
|
|
const suggestions = ref<AdminInventorySkuSuggestion[]>([])
|
|
|
|
// ── Core state ────────────────────────────────────────────────────
|
|
const detail = ref<AdminManualRedeemDetail | null>(null)
|
|
const detailLoading = ref(false)
|
|
const createLoading = ref(false)
|
|
const sessionLoading = ref(false)
|
|
const roleConfirmLoading = ref(false)
|
|
const redeemLoading = ref(false)
|
|
const closeLoading = ref(false)
|
|
const loginType = ref<TencentLoginType>(DEFAULT_LOGIN_TYPE)
|
|
|
|
// ── Screenshot state (admin-only) ──────────────────────────────────
|
|
const screenshotBlob = ref<Blob | null>(null)
|
|
const screenshotUrl = ref('')
|
|
const screenshotLoading = ref(false)
|
|
|
|
// ── Derived state ──────────────────────────────────────────────────
|
|
const session = computed(() => detail.value?.session || null)
|
|
const task = computed(() => detail.value?.task || null)
|
|
const order = computed(() => detail.value?.order || null)
|
|
const orderItem = computed(() => detail.value?.orderItem || null)
|
|
const result = computed(() => detail.value?.result || null)
|
|
const manualRequest = computed(() => detail.value?.manualRequest || null)
|
|
const activityInfo = computed(() => session.value?.activityInfo || null)
|
|
const roleReady = computed(() => Boolean(activityInfo.value?.role?.ready))
|
|
const roleConfirmed = computed(() =>
|
|
Boolean(task.value && ['role_confirmed', 'redeeming', 'redeemed'].includes(task.value.status)),
|
|
)
|
|
const hasSession = computed(() => Boolean(session.value?.sessionId))
|
|
const loginTypeLabel = computed(() => (loginType.value === 'wx' ? '微信' : 'QQ'))
|
|
const currentTaskId = computed(() => Number(task.value?.taskId || route.query.taskId || 0) || 0)
|
|
const reviewReady = computed(
|
|
() => Boolean(session.value?.review?.capturedAt) && task.value?.status !== 'redeemed',
|
|
)
|
|
const resultReady = computed(
|
|
() => Boolean(result.value?.screenshotReady) && task.value?.status === 'redeemed',
|
|
)
|
|
|
|
// ── Polling ────────────────────────────────────────────────────────
|
|
const {
|
|
pollWarningMessage,
|
|
startPolling,
|
|
resetPolling,
|
|
resetPollingWarning,
|
|
handleSilentPollingError,
|
|
} = useSessionPolling({
|
|
hasSession: computed(() => Boolean(task.value?.taskId && hasSession.value)),
|
|
shouldKeepPolling: computed(() => shouldKeepPolling(detail.value)),
|
|
onPollTick: () => refreshSessionSummary({ silent: true }),
|
|
pollWarningPrefix: '人工兑换状态刷新失败',
|
|
})
|
|
|
|
const sessionNotice = computed(
|
|
() => pollWarningMessage.value || session.value?.notice || task.value?.lastError || '',
|
|
)
|
|
|
|
// ── QR display ─────────────────────────────────────────────────────
|
|
const {
|
|
qrImage,
|
|
qrFigureStyle,
|
|
qrPreviewWidth,
|
|
handleQrImageLoad,
|
|
} = useSessionQrDisplay({ session })
|
|
|
|
// ── Presentation ───────────────────────────────────────────────────
|
|
const loginTabs = [
|
|
{ value: 'qq' as const, label: 'QQ账号登录' },
|
|
{ value: 'wx' as const, label: '微信账号登录' },
|
|
]
|
|
|
|
const statusLabel = computed(() =>
|
|
resolveTaskStatusLabel(task.value?.status, session.value?.status, 'admin'),
|
|
)
|
|
const initButtonLabel = computed(() => `生成${loginTypeLabel.value}二维码`)
|
|
const scanInstruction = computed(
|
|
() => `请让客户使用${loginTypeLabel.value}扫码,并在手机上确认登录`,
|
|
)
|
|
|
|
useSessionRoleFacts({
|
|
activityInfo,
|
|
statusLabel,
|
|
roleFactDefaults: { nickname: '等待客户登录' },
|
|
})
|
|
|
|
const proofValue = computed(() => manualRequest.value?.proofValue || order.value?.platformOrderId || '-')
|
|
|
|
const { resultFacts } = useSessionResultFacts({
|
|
order,
|
|
orderItem,
|
|
result,
|
|
resultFactOverrides: {
|
|
firstLabel: '唯一凭据',
|
|
firstValue: proofValue,
|
|
},
|
|
})
|
|
|
|
// Admin overrides roleFacts to include "所在大区" and exclude "任务状态"
|
|
const adminRoleFacts = computed(() => [
|
|
{
|
|
label: '登录昵称',
|
|
value: activityInfo.value?.nickname || '等待客户登录',
|
|
},
|
|
{
|
|
label: '当前角色',
|
|
value: activityInfo.value?.role?.roleName || '后端浏览器同步中',
|
|
accent: true,
|
|
},
|
|
{
|
|
label: '角色 ID',
|
|
value: activityInfo.value?.role?.roleId || '未识别',
|
|
},
|
|
{
|
|
label: '所在大区',
|
|
value: activityInfo.value?.role?.area || '未识别',
|
|
},
|
|
])
|
|
|
|
const canConfirmRole = computed(() =>
|
|
Boolean(
|
|
task.value &&
|
|
task.value.status === 'claimed' &&
|
|
hasSession.value &&
|
|
roleReady.value &&
|
|
!roleConfirmLoading.value,
|
|
),
|
|
)
|
|
const canRedeem = computed(() =>
|
|
Boolean(
|
|
task.value &&
|
|
['role_confirmed', 'redeeming'].includes(task.value.status) &&
|
|
!redeemLoading.value,
|
|
),
|
|
)
|
|
const redeemBlockedReason = computed(() =>
|
|
buildRedeemBlockedReason({
|
|
detail,
|
|
hasSession,
|
|
loginTypeLabel,
|
|
redeemLoading,
|
|
roleReady,
|
|
roleConfirmed,
|
|
sessionNotice,
|
|
context: 'admin',
|
|
}),
|
|
)
|
|
const redeemButtonLabel = computed(() => (redeemLoading.value ? '兑换中...' : '开始兑换'))
|
|
const screenshotEmptyTitle = computed(() =>
|
|
task.value?.status === 'redeemed' ? '未获取到结果图' : '等待角色截图',
|
|
)
|
|
const screenshotEmptyMessage = computed(() =>
|
|
task.value?.status === 'redeemed'
|
|
? '本次兑换可能未生成最终截图,或截图仍在同步中。'
|
|
: '客户登录并识别到角色后,这里会展示给客服复核的角色截图。',
|
|
)
|
|
const canCreateTask = computed(
|
|
() => Boolean(form.proofValue.trim() && form.skuCode.trim()) && !createLoading.value,
|
|
)
|
|
const hasActiveTask = computed(() => Boolean(task.value?.taskId))
|
|
|
|
// ── Watchers ───────────────────────────────────────────────────────
|
|
watch(qrImage, () => {
|
|
// QR image natural width reset handled by useSessionQrDisplay
|
|
})
|
|
|
|
watch(
|
|
() => currentTaskId.value,
|
|
(taskId, previousTaskId) => {
|
|
if (!taskId || taskId === previousTaskId) {
|
|
return
|
|
}
|
|
|
|
void loadDetail(taskId)
|
|
},
|
|
{ immediate: true },
|
|
)
|
|
|
|
onMounted(() => {
|
|
void loadSkuSuggestions()
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
resetPolling()
|
|
clearScreenshotPreview()
|
|
})
|
|
|
|
// ── Helpers ────────────────────────────────────────────────────────
|
|
function restartPollingIfNeeded(nextDetail = detail.value) {
|
|
resetPolling()
|
|
|
|
if (shouldKeepPolling(nextDetail)) {
|
|
startPolling()
|
|
}
|
|
}
|
|
|
|
function resolveSkuName(skuCode: string) {
|
|
const normalized = String(skuCode || '').trim()
|
|
const matched = suggestions.value.find(
|
|
(item) => String(item.skuCode || '').trim() === normalized,
|
|
)
|
|
return matched?.skuCode || normalized
|
|
}
|
|
|
|
// ── Screenshot helpers (admin-only) ───────────────────────────────
|
|
async function loadScreenshotPreview(nextDetail = detail.value) {
|
|
const taskId = Number(nextDetail?.task?.taskId || 0)
|
|
const shouldFetch = Boolean(
|
|
taskId && (nextDetail?.session?.review?.capturedAt || nextDetail?.result?.screenshotReady),
|
|
)
|
|
|
|
if (!shouldFetch) {
|
|
clearScreenshotPreview()
|
|
return
|
|
}
|
|
|
|
screenshotLoading.value = true
|
|
|
|
try {
|
|
const blob = await fetchAdminTaskScreenshot(taskId)
|
|
clearScreenshotPreview()
|
|
screenshotBlob.value = blob
|
|
screenshotUrl.value = URL.createObjectURL(blob)
|
|
} catch {
|
|
clearScreenshotPreview()
|
|
} finally {
|
|
screenshotLoading.value = false
|
|
}
|
|
}
|
|
|
|
function clearScreenshotPreview() {
|
|
screenshotBlob.value = null
|
|
|
|
if (screenshotUrl.value) {
|
|
URL.revokeObjectURL(screenshotUrl.value)
|
|
screenshotUrl.value = ''
|
|
}
|
|
}
|
|
|
|
async function writeImageBlobToClipboard(blob: Blob, fallbackMessage: string) {
|
|
if (
|
|
typeof navigator === 'undefined' ||
|
|
!navigator.clipboard ||
|
|
typeof window === 'undefined' ||
|
|
!('ClipboardItem' in window)
|
|
) {
|
|
throw new Error('当前浏览器不支持直接复制图片,请改用截图发送或下载')
|
|
}
|
|
|
|
try {
|
|
const ClipboardItemCtor = window.ClipboardItem as typeof ClipboardItem
|
|
await navigator.clipboard.write([
|
|
new ClipboardItemCtor({
|
|
[blob.type || 'image/png']: blob,
|
|
}),
|
|
])
|
|
} catch (error) {
|
|
throw error instanceof Error ? error : new Error(fallbackMessage)
|
|
}
|
|
}
|
|
|
|
// ── API flows ─────────────────────────────────────────────────────
|
|
async function loadSkuSuggestions(keyword = '') {
|
|
try {
|
|
const response = await fetchAdminManualRedeemSkuSuggestions({
|
|
credentialType: 'tencent_code',
|
|
keyword: keyword.trim(),
|
|
})
|
|
suggestions.value = response.data.items
|
|
} catch (error) {
|
|
notifyTencentActionError(error, '读取人工兑换 SKU 建议失败')
|
|
}
|
|
}
|
|
|
|
async function loadDetail(taskId = currentTaskId.value) {
|
|
if (!taskId) {
|
|
detail.value = null
|
|
clearScreenshotPreview()
|
|
return false
|
|
}
|
|
|
|
detailLoading.value = true
|
|
|
|
try {
|
|
const response = await fetchAdminManualRedeemDetail(taskId)
|
|
detail.value = response.data
|
|
loginType.value = syncLoginTypeFromDetail(response.data, loginType.value)
|
|
await loadScreenshotPreview(response.data)
|
|
restartPollingIfNeeded()
|
|
return true
|
|
} catch (error) {
|
|
notifyTencentActionError(error, '读取人工兑换详情失败')
|
|
return false
|
|
} finally {
|
|
detailLoading.value = false
|
|
}
|
|
}
|
|
|
|
async function createTaskNow() {
|
|
if (!canCreateTask.value) {
|
|
return
|
|
}
|
|
|
|
createLoading.value = true
|
|
|
|
try {
|
|
const response = await createAdminManualRedeemTask({
|
|
proofValue: form.proofValue.trim(),
|
|
skuCode: form.skuCode.trim(),
|
|
skuName: resolveSkuName(form.skuCode),
|
|
remark: form.remark.trim(),
|
|
})
|
|
|
|
detail.value = response.data
|
|
loginType.value = syncLoginTypeFromDetail(response.data, loginType.value)
|
|
await loadScreenshotPreview(response.data)
|
|
await router.replace({
|
|
path: route.path,
|
|
query: {
|
|
taskId: String(response.data.task.taskId),
|
|
},
|
|
})
|
|
showSuccess(response.msg || '人工兑换任务已创建')
|
|
} catch (error) {
|
|
notifyTencentActionError(error, '创建人工兑换任务失败')
|
|
} finally {
|
|
createLoading.value = false
|
|
}
|
|
}
|
|
|
|
async function createSessionFlow(nextLoginType = loginType.value) {
|
|
if (!task.value?.taskId) {
|
|
return
|
|
}
|
|
|
|
sessionLoading.value = true
|
|
resetPolling()
|
|
resetPollingWarning()
|
|
|
|
try {
|
|
loginType.value = nextLoginType
|
|
const response = await createAdminManualRedeemSession(task.value.taskId, {
|
|
loginType: nextLoginType,
|
|
forceRecreate: hasSession.value,
|
|
})
|
|
|
|
detail.value = response.data
|
|
loginType.value = syncLoginTypeFromDetail(response.data, nextLoginType)
|
|
await loadScreenshotPreview(response.data)
|
|
startPolling()
|
|
} catch (error) {
|
|
notifyTencentActionError(error, '创建人工兑换登录会话失败')
|
|
} finally {
|
|
sessionLoading.value = false
|
|
}
|
|
}
|
|
|
|
async function refreshSessionFlow() {
|
|
if (!task.value?.taskId) {
|
|
return false
|
|
}
|
|
|
|
sessionLoading.value = true
|
|
resetPolling()
|
|
resetPollingWarning()
|
|
|
|
try {
|
|
const response = await refreshAdminManualRedeemSession(task.value.taskId)
|
|
detail.value = response.data
|
|
loginType.value = syncLoginTypeFromDetail(response.data, loginType.value)
|
|
await loadScreenshotPreview(response.data)
|
|
restartPollingIfNeeded(response.data)
|
|
return true
|
|
} catch (error) {
|
|
notifyTencentActionError(error, '刷新人工兑换会话失败')
|
|
await refreshSessionSummary({ silent: true })
|
|
restartPollingIfNeeded()
|
|
return false
|
|
} finally {
|
|
sessionLoading.value = false
|
|
}
|
|
}
|
|
|
|
async function closeSessionFlow({ silent = false } = {}) {
|
|
if (!task.value?.taskId) {
|
|
return false
|
|
}
|
|
|
|
if (!silent) {
|
|
sessionLoading.value = true
|
|
}
|
|
|
|
resetPolling()
|
|
resetPollingWarning()
|
|
|
|
try {
|
|
const response = await removeAdminManualRedeemSession(task.value.taskId)
|
|
detail.value = response.data
|
|
loginType.value = syncLoginTypeFromDetail(response.data, loginType.value)
|
|
await loadScreenshotPreview(response.data)
|
|
|
|
if (!silent) {
|
|
showSuccess(response.msg || '人工兑换会话已关闭')
|
|
}
|
|
|
|
return true
|
|
} catch (error) {
|
|
if (!silent) {
|
|
notifyTencentActionError(error, '关闭人工兑换会话失败')
|
|
}
|
|
return false
|
|
} finally {
|
|
if (!silent) {
|
|
sessionLoading.value = false
|
|
}
|
|
}
|
|
}
|
|
|
|
async function refreshSessionSummary({ silent = false } = {}) {
|
|
if (!task.value?.taskId) {
|
|
return false
|
|
}
|
|
|
|
if (!silent) {
|
|
sessionLoading.value = true
|
|
resetPollingWarning()
|
|
}
|
|
|
|
try {
|
|
const currentSession = detail.value?.session || null
|
|
const response = await fetchAdminManualRedeemSessionSummary(task.value.taskId)
|
|
detail.value = mergeSessionDetail(
|
|
detail.value,
|
|
response.data,
|
|
(currentSession, nextSession) => ({
|
|
review: nextSession.review ?? currentSession.review ?? null,
|
|
}),
|
|
)
|
|
loginType.value = syncLoginTypeFromDetail(response.data, loginType.value)
|
|
|
|
if (shouldRefreshQrImage(currentSession, response.data.session)) {
|
|
const fullResponse = await fetchAdminManualRedeemDetail(task.value.taskId!)
|
|
detail.value = mergeSessionDetail(
|
|
detail.value,
|
|
fullResponse.data,
|
|
(currentSession, nextSession) => ({
|
|
review: nextSession.review ?? currentSession.review ?? null,
|
|
}),
|
|
)
|
|
loginType.value = syncLoginTypeFromDetail(fullResponse.data, loginType.value)
|
|
}
|
|
|
|
await loadScreenshotPreview(detail.value)
|
|
|
|
if (silent) {
|
|
resetPollingWarning()
|
|
}
|
|
|
|
return true
|
|
} catch (error) {
|
|
if (silent) {
|
|
handleSilentPollingError(error)
|
|
} else {
|
|
notifyTencentActionError(error, '刷新人工兑换会话状态失败')
|
|
}
|
|
|
|
return false
|
|
} finally {
|
|
if (!silent) {
|
|
sessionLoading.value = false
|
|
}
|
|
}
|
|
}
|
|
|
|
async function confirmRoleNow() {
|
|
if (!task.value?.taskId || !canConfirmRole.value) {
|
|
return
|
|
}
|
|
|
|
roleConfirmLoading.value = true
|
|
|
|
try {
|
|
const response = await confirmAdminManualRedeemRole(task.value.taskId)
|
|
detail.value = response.data
|
|
await loadScreenshotPreview(response.data)
|
|
showSuccess(response.msg || '角色已确认')
|
|
restartPollingIfNeeded(response.data)
|
|
} catch (error) {
|
|
notifyTencentActionError(error, '确认角色失败')
|
|
await refreshSessionSummary({ silent: true })
|
|
} finally {
|
|
roleConfirmLoading.value = false
|
|
}
|
|
}
|
|
|
|
async function redeemNow() {
|
|
if (!task.value?.taskId || !canRedeem.value) {
|
|
return
|
|
}
|
|
|
|
redeemLoading.value = true
|
|
resetPolling()
|
|
resetPollingWarning()
|
|
|
|
try {
|
|
const response = await redeemAdminManualRedeem(task.value.taskId)
|
|
detail.value = response.data
|
|
await loadScreenshotPreview(response.data)
|
|
showSuccess(response.msg || '兑换完成')
|
|
restartPollingIfNeeded(response.data)
|
|
} catch (error) {
|
|
notifyTencentActionError(error, '人工兑换失败')
|
|
await refreshSessionSummary({ silent: true })
|
|
restartPollingIfNeeded()
|
|
} finally {
|
|
redeemLoading.value = false
|
|
}
|
|
}
|
|
|
|
async function closeTaskNow() {
|
|
if (!task.value?.taskId) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
await showConfirm(
|
|
`确认关闭任务 ${task.value.taskNo} 吗?关闭后会释放未消耗的资源,并结束当前人工兑换流程。`,
|
|
'关闭人工兑换',
|
|
)
|
|
} catch {
|
|
return
|
|
}
|
|
|
|
closeLoading.value = true
|
|
|
|
try {
|
|
const response = await closeAdminManualRedeemTask(task.value.taskId)
|
|
detail.value = response.data
|
|
await loadScreenshotPreview(response.data)
|
|
resetPolling()
|
|
showSuccess(response.msg || '人工兑换任务已关闭')
|
|
} catch (error) {
|
|
notifyTencentActionError(error, '关闭人工兑换任务失败')
|
|
} finally {
|
|
closeLoading.value = false
|
|
}
|
|
}
|
|
|
|
async function switchLoginType(nextLoginType: TencentLoginType) {
|
|
if (nextLoginType === loginType.value) {
|
|
return
|
|
}
|
|
|
|
if (hasSession.value) {
|
|
await createSessionFlow(nextLoginType)
|
|
return
|
|
}
|
|
|
|
loginType.value = nextLoginType
|
|
}
|
|
|
|
function resetCurrentTask() {
|
|
resetPolling()
|
|
detail.value = null
|
|
clearScreenshotPreview()
|
|
void router.replace({
|
|
path: route.path,
|
|
query: {},
|
|
})
|
|
}
|
|
|
|
async function copyQrImage() {
|
|
if (!qrImage.value) {
|
|
return false
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(qrImage.value)
|
|
const blob = await response.blob()
|
|
await writeImageBlobToClipboard(blob, '复制二维码失败')
|
|
showSuccess('二维码已复制到剪贴板')
|
|
return true
|
|
} catch (error) {
|
|
notifyTencentActionError(error, '复制二维码失败')
|
|
return false
|
|
}
|
|
}
|
|
|
|
async function copyScreenshot() {
|
|
if (!screenshotBlob.value) {
|
|
return false
|
|
}
|
|
|
|
try {
|
|
await writeImageBlobToClipboard(screenshotBlob.value, '复制截图失败')
|
|
showSuccess('截图已复制到剪贴板')
|
|
return true
|
|
} catch (error) {
|
|
notifyTencentActionError(error, '复制截图失败')
|
|
return false
|
|
}
|
|
}
|
|
|
|
function downloadScreenshot() {
|
|
if (!screenshotBlob.value || !screenshotUrl.value) {
|
|
return false
|
|
}
|
|
|
|
const anchor = document.createElement('a')
|
|
anchor.href = screenshotUrl.value
|
|
anchor.download =
|
|
task.value?.status === 'redeemed'
|
|
? `manual-redeem-result-${task.value?.taskNo || 'task'}.png`
|
|
: `manual-redeem-review-${task.value?.taskNo || 'task'}.png`
|
|
anchor.click()
|
|
return true
|
|
}
|
|
|
|
return {
|
|
form,
|
|
suggestions,
|
|
detail,
|
|
detailLoading,
|
|
createLoading,
|
|
sessionLoading,
|
|
roleConfirmLoading,
|
|
redeemLoading,
|
|
closeLoading,
|
|
loginType,
|
|
loginTypeLabel,
|
|
loginTabs,
|
|
session,
|
|
task,
|
|
order,
|
|
orderItem,
|
|
manualRequest,
|
|
activityInfo,
|
|
roleReady,
|
|
roleConfirmed,
|
|
hasSession,
|
|
qrImage,
|
|
qrFigureStyle,
|
|
qrPreviewWidth,
|
|
statusLabel,
|
|
sessionNotice,
|
|
roleFacts: adminRoleFacts,
|
|
resultFacts,
|
|
canConfirmRole,
|
|
canRedeem,
|
|
redeemBlockedReason,
|
|
redeemButtonLabel,
|
|
initButtonLabel,
|
|
scanInstruction,
|
|
screenshotEmptyTitle,
|
|
screenshotEmptyMessage,
|
|
screenshotUrl,
|
|
screenshotLoading,
|
|
reviewReady,
|
|
resultReady,
|
|
canCreateTask,
|
|
hasActiveTask,
|
|
createTaskNow,
|
|
loadSkuSuggestions,
|
|
createSessionFlow,
|
|
refreshSessionFlow,
|
|
closeSessionFlow,
|
|
switchLoginType,
|
|
confirmRoleNow,
|
|
redeemNow,
|
|
closeTaskNow,
|
|
resetCurrentTask,
|
|
handleQrImageLoad,
|
|
copyQrImage,
|
|
copyScreenshot,
|
|
downloadScreenshot,
|
|
}
|
|
}
|