feat(frontend): add P2 quality gates

This commit is contained in:
yml2213
2026-05-15 17:35:50 +08:00
parent a65c0713fb
commit 787b01749c
62 changed files with 4907 additions and 1355 deletions
+3 -1
View File
@@ -19,7 +19,9 @@ async function bootstrap() {
try {
const response = await fetchClaimDetail(token.value)
flowType.value = String(response.data.flowType || response.data.task.executorKey || 'tencent_claim').trim()
flowType.value = String(
response.data.flowType || response.data.task.executorKey || 'tencent_claim',
).trim()
} catch (error) {
errorMessage.value = error instanceof Error ? error.message : '读取领取信息失败'
} finally {
@@ -2,7 +2,14 @@
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import QRCode from 'qrcode'
import { ElMessageBox } from 'element-plus'
import { CircleCheck, CircleClose, Clock, Loading, RefreshRight, Warning } from '@element-plus/icons-vue'
import {
CircleCheck,
CircleClose,
Clock,
Loading,
RefreshRight,
Warning,
} from '@element-plus/icons-vue'
import { showError, showSuccess } from '@/lib/feedback'
import {
@@ -45,22 +52,35 @@ const isBindUrlExpired = computed(() => {
const expiresTime = Date.parse(expiresAt)
return Number.isFinite(expiresTime) && expiresTime <= Date.now()
})
const isBindingPrepared = computed(() => (
flow.value?.binding.prepareStatus === 'ready'
&& Boolean(String(flow.value?.binding.bindUrl || '').trim())
&& !isBindUrlExpired.value
))
const isBindingPrepared = computed(
() =>
flow.value?.binding.prepareStatus === 'ready' &&
Boolean(String(flow.value?.binding.bindUrl || '').trim()) &&
!isBindUrlExpired.value,
)
const isBindingPreparing = computed(() => flow.value?.binding.prepareStatus === 'pending')
const canEnterBindingStep = computed(() => isTicketVerified.value)
const isRoleReady = computed(() => Boolean(roleName.value || roleId.value))
const isRoleConfirmed = computed(() => String(task.value?.status || '').trim() === 'role_confirmed')
const isDispatched = computed(() => String(flow.value?.dispatch.status || '').trim() === 'success')
const isCompleted = computed(() => String(task.value?.status || '').trim() === 'completed' || String(flow.value?.consume.status || '').trim() === 'success')
const isCompleted = computed(
() =>
String(task.value?.status || '').trim() === 'completed' ||
String(flow.value?.consume.status || '').trim() === 'success',
)
const hasRedeemResult = computed(() => {
const status = String(task.value?.status || '').trim()
return isDispatched.value || ['dispatched_pending_return', 'completed', 'manual_review', 'failed'].includes(status)
return (
isDispatched.value ||
['dispatched_pending_return', 'completed', 'manual_review', 'failed'].includes(status)
)
})
const canSubmitTicket = computed(() => !['completed', 'manual_review', 'closed', 'expired'].includes(String(task.value?.status || '').trim()) && !submitting.value)
const canSubmitTicket = computed(
() =>
!['completed', 'manual_review', 'closed', 'expired'].includes(
String(task.value?.status || '').trim(),
) && !submitting.value,
)
const currentStep = computed(() => {
if (hasRedeemResult.value) {
return 4
@@ -187,7 +207,9 @@ async function refreshRole() {
if (isRoleReady.value) {
showSuccess('角色信息已刷新')
} else {
showError(flow.value?.role.errorMessage || '暂时还没有识别到角色信息,请完成绑定后稍等片刻再试')
showError(
flow.value?.role.errorMessage || '暂时还没有识别到角色信息,请完成绑定后稍等片刻再试',
)
}
} finally {
refreshingRole.value = false
@@ -261,9 +283,8 @@ function stopPolling() {
function syncPolling() {
const taskStatus = String(task.value?.status || '').trim()
const shouldPoll = Boolean(flow.value)
&& !hasRedeemResult.value
&& !['closed', 'expired'].includes(taskStatus)
const shouldPoll =
Boolean(flow.value) && !hasRedeemResult.value && !['closed', 'expired'].includes(taskStatus)
if (!shouldPoll) {
stopPolling()
@@ -316,7 +337,12 @@ onBeforeUnmount(() => {
<p>订单号{{ order?.platformOrderId || '-' }}</p>
</div>
<div v-if="!loading && !errorMessage" class="step-indicator">
<div v-for="step in 4" :key="step" class="step-dot" :class="{ active: currentStep >= step }">
<div
v-for="step in 4"
:key="step"
class="step-dot"
:class="{ active: currentStep >= step }"
>
{{ step }}
</div>
</div>
@@ -351,7 +377,8 @@ onBeforeUnmount(() => {
<section v-if="currentStep === 1" class="content-card">
<h2> 1 提交核销码</h2>
<p class="muted">
请先从快手小店复制核销码验证通过后系统会自动准备 Cloud 绑定资源不需要再让客服手工点准备绑定资源
请先从快手小店复制核销码验证通过后系统会自动准备 Cloud
绑定资源不需要再让客服手工点准备绑定资源
</p>
<el-input
@@ -374,14 +401,22 @@ onBeforeUnmount(() => {
<div class="status-banner info">
<el-icon><Clock /></el-icon>
<span>{{ isBindingPreparing ? '绑定资源会在核销通过后自动准备。' : '核销完成后会自动进入扫码绑定步骤。' }}</span>
<span>{{
isBindingPreparing
? '绑定资源会在核销通过后自动准备。'
: '核销完成后会自动进入扫码绑定步骤。'
}}</span>
</div>
<div v-if="flow.guideImages.length > 0" class="guide-section">
<el-collapse>
<el-collapse-item title="查看核销码图文指引" name="guide">
<div class="guide-grid">
<figure v-for="(imageUrl, index) in flow.guideImages" :key="imageUrl" class="guide-figure">
<figure
v-for="(imageUrl, index) in flow.guideImages"
:key="imageUrl"
class="guide-figure"
>
<img :src="imageUrl" :alt="`步骤 ${index + 1}`" />
<figcaption>步骤 {{ index + 1 }}</figcaption>
</figure>
@@ -403,9 +438,7 @@ onBeforeUnmount(() => {
<el-button type="primary" size="large" @click="openBindUrl()">
新窗口打开绑定页
</el-button>
<el-button size="large" plain @click="openBindUrl(true)">
当前页面打开
</el-button>
<el-button size="large" plain @click="openBindUrl(true)"> 当前页面打开 </el-button>
</div>
</div>
@@ -421,19 +454,16 @@ onBeforeUnmount(() => {
如果当前角色不正确请重新打开绑定页完成重新绑定然后刷新角色信息
</p>
<p class="muted meta-line">
最近刷新{{ formatDateTime(flow.role.refreshedAt) }}链接有效期{{ formatDateTime(flow.binding.bindExpiresAt) }}
最近刷新{{ formatDateTime(flow.role.refreshedAt) }}链接有效期{{
formatDateTime(flow.binding.bindExpiresAt)
}}
</p>
<p v-if="flow.binding.bindProbeMessage" class="muted meta-line">
链接检测{{ flow.binding.bindProbeStatus || '-' }} / {{ flow.binding.bindProbeMessage }}
</p>
<div class="action-row">
<el-button
size="large"
plain
:loading="refreshingRole"
@click="refreshRole"
>
<el-button size="large" plain :loading="refreshingRole" @click="refreshRole">
<el-icon><RefreshRight /></el-icon>
刷新角色信息
</el-button>
@@ -477,12 +507,7 @@ onBeforeUnmount(() => {
<span>兑换后不可取消也不可退货</span>
</div>
<el-button
type="primary"
size="large"
:loading="redeeming"
@click="confirmRedeem"
>
<el-button type="primary" size="large" :loading="redeeming" @click="confirmRedeem">
确认兑换
</el-button>
</section>