remove manual review button; store review is now fully automated via polling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
父节点
42b1868f54
当前提交
36deeb218f
@ -323,10 +323,10 @@ export const updateAdminApi = {
|
||||
)
|
||||
},
|
||||
|
||||
updateStoreReview(versionId: string, storeType: StoreType, state: StoreReviewState) {
|
||||
updateStoreReview(versionId: string, storeType: StoreType, state: StoreReviewState, reason?: string) {
|
||||
return updateClient.post<{ data: AppVersion }>(
|
||||
`/api/v1/updates/store/app/${versionId}/review`,
|
||||
{ storeType, state },
|
||||
{ storeType, state, ...(reason ? { reason } : {}) },
|
||||
)
|
||||
},
|
||||
|
||||
|
||||
@ -537,7 +537,7 @@
|
||||
</el-dialog>
|
||||
|
||||
<!-- Store Review Detail Dialog -->
|
||||
<el-dialog v-model="showStoreReviewDetail" title="应用商店状态详情" :width="isMobile ? '100%' : '760px'">
|
||||
<el-dialog v-model="showStoreReviewDetail" title="应用商店状态详情" :width="isMobile ? '100%' : '760px'" @close="stopDialogPoll">
|
||||
<div v-if="storeReviewDetailVersion">
|
||||
<!-- Version summary bar -->
|
||||
<div class="review-detail-header">
|
||||
@ -617,6 +617,7 @@
|
||||
@click="handleRetryStore(item.store)"
|
||||
>重新提交</el-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -638,7 +639,7 @@
|
||||
plain
|
||||
@click="showPublishSchedule = true"
|
||||
>修改发布计划</el-button>
|
||||
<el-button style="margin-left:auto" type="primary" @click="showStoreReviewDetail = false">关闭</el-button>
|
||||
<el-button style="margin-left:auto" type="primary" @click="showStoreReviewDetail = false; stopDialogPoll()">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@ -1049,6 +1050,8 @@ const operationLogs = ref<{
|
||||
}[]>([])
|
||||
const loadingOperationLogs = ref(false)
|
||||
let storeReviewReloadTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let storeReviewAutoRefreshTimer: ReturnType<typeof setInterval> | null = null
|
||||
let storeReviewDialogPollTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const hasGraySelectCallback = computed(() => Boolean(publishConfigForm.value.graySelectCallbackUrl.trim()))
|
||||
const hasGrayDirectorySyncCallback = computed(() => Boolean(publishConfigForm.value.grayDirectorySyncCallbackUrl.trim()))
|
||||
@ -1566,6 +1569,32 @@ function openStoreReviewDetail(row: AppVersion) {
|
||||
storeReviewDetailItems.value = parseStoreReview(row.storeReviewStatus)
|
||||
storeReviewDetailLive.value = false
|
||||
showStoreReviewDetail.value = true
|
||||
startDialogPoll()
|
||||
}
|
||||
|
||||
function startDialogPoll() {
|
||||
stopDialogPoll()
|
||||
storeReviewDialogPollTimer = setInterval(() => {
|
||||
if (!showStoreReviewDetail.value || !storeReviewDetailVersion.value) {
|
||||
stopDialogPoll()
|
||||
return
|
||||
}
|
||||
const hasActive = storeReviewDetailItems.value.some(i =>
|
||||
i.state === 'PENDING' || i.state === 'SUBMITTING' || i.state === 'UNDER_REVIEW'
|
||||
)
|
||||
if (hasActive) {
|
||||
void loadAppVersions()
|
||||
} else {
|
||||
stopDialogPoll()
|
||||
}
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
function stopDialogPoll() {
|
||||
if (storeReviewDialogPollTimer) {
|
||||
clearInterval(storeReviewDialogPollTimer)
|
||||
storeReviewDialogPollTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmSubmitToStores() {
|
||||
@ -2325,16 +2354,22 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// Only show toast for non-trivial state transitions
|
||||
// Toast: show for all state/stage changes except PENDING (initial queue)
|
||||
const state = (event.reviewState || '').toUpperCase()
|
||||
const stage = (event.stage || '').toUpperCase()
|
||||
if (state && state !== 'PENDING') {
|
||||
// Use stage description for SUBMITTING transitions (排队→上传→审核中)
|
||||
let stageHint = ''
|
||||
if (state === 'SUBMITTING') {
|
||||
stageHint = stage === 'QUEUED' ? '排队中' : stage === 'SUBMITTING' ? '正在上传' : ''
|
||||
}
|
||||
ElMessage({
|
||||
message: event.reviewReason
|
||||
? `${storeName} → ${stateName}:${event.reviewReason}`
|
||||
: `${storeName} → ${stateName}`,
|
||||
? `${storeName} → ${stageHint || stateName}:${event.reviewReason}`
|
||||
: `${storeName} → ${stageHint || stateName}`,
|
||||
type: state === 'REJECTED' || state === 'WITHDRAWN' ? 'error'
|
||||
: state === 'APPROVED' ? 'success' : 'info',
|
||||
duration: 4000,
|
||||
duration: state === 'SUBMITTING' ? 2000 : 4000,
|
||||
})
|
||||
}
|
||||
|
||||
@ -2344,6 +2379,24 @@ onMounted(() => {
|
||||
console.warn('[tenant-platform] store review realtime unavailable', error)
|
||||
}
|
||||
})
|
||||
|
||||
// Fallback: poll every 30s while any store is in an active state so WebSocket
|
||||
// drop-outs don't leave the UI stale (e.g. a 20-min upload where WS reconnects).
|
||||
storeReviewAutoRefreshTimer = setInterval(() => {
|
||||
const hasActive = appVersions.value.some(v => {
|
||||
if (!v.storeReviewStatus) return false
|
||||
try {
|
||||
const m = JSON.parse(v.storeReviewStatus) as Record<string, unknown>
|
||||
return Object.values(m).some(entry => {
|
||||
const state = typeof entry === 'string' ? entry : (entry as Record<string, unknown>)?.state as string
|
||||
return state === 'PENDING' || state === 'SUBMITTING' || state === 'UNDER_REVIEW'
|
||||
})
|
||||
} catch { return false }
|
||||
})
|
||||
if (hasActive) {
|
||||
void loadAppVersions()
|
||||
}
|
||||
}, 30_000)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@ -2353,6 +2406,11 @@ onBeforeUnmount(() => {
|
||||
clearTimeout(storeReviewReloadTimer)
|
||||
storeReviewReloadTimer = null
|
||||
}
|
||||
if (storeReviewAutoRefreshTimer) {
|
||||
clearInterval(storeReviewAutoRefreshTimer)
|
||||
storeReviewAutoRefreshTimer = null
|
||||
}
|
||||
stopDialogPoll()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户