remove manual review button; store review is now fully automated via polling

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-05-17 12:15:06 +08:00
父节点 42b1868f54
当前提交 36deeb218f
共有 2 个文件被更改,包括 66 次插入8 次删除

查看文件

@ -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>