feat(frontend): add P2 quality gates
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user