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 }>(
|
return updateClient.post<{ data: AppVersion }>(
|
||||||
`/api/v1/updates/store/app/${versionId}/review`,
|
`/api/v1/updates/store/app/${versionId}/review`,
|
||||||
{ storeType, state },
|
{ storeType, state, ...(reason ? { reason } : {}) },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -537,7 +537,7 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<!-- Store Review Detail 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">
|
<div v-if="storeReviewDetailVersion">
|
||||||
<!-- Version summary bar -->
|
<!-- Version summary bar -->
|
||||||
<div class="review-detail-header">
|
<div class="review-detail-header">
|
||||||
@ -617,6 +617,7 @@
|
|||||||
@click="handleRetryStore(item.store)"
|
@click="handleRetryStore(item.store)"
|
||||||
>重新提交</el-button>
|
>重新提交</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -638,7 +639,7 @@
|
|||||||
plain
|
plain
|
||||||
@click="showPublishSchedule = true"
|
@click="showPublishSchedule = true"
|
||||||
>修改发布计划</el-button>
|
>修改发布计划</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@ -1049,6 +1050,8 @@ const operationLogs = ref<{
|
|||||||
}[]>([])
|
}[]>([])
|
||||||
const loadingOperationLogs = ref(false)
|
const loadingOperationLogs = ref(false)
|
||||||
let storeReviewReloadTimer: ReturnType<typeof setTimeout> | null = null
|
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 hasGraySelectCallback = computed(() => Boolean(publishConfigForm.value.graySelectCallbackUrl.trim()))
|
||||||
const hasGrayDirectorySyncCallback = computed(() => Boolean(publishConfigForm.value.grayDirectorySyncCallbackUrl.trim()))
|
const hasGrayDirectorySyncCallback = computed(() => Boolean(publishConfigForm.value.grayDirectorySyncCallbackUrl.trim()))
|
||||||
@ -1566,6 +1569,32 @@ function openStoreReviewDetail(row: AppVersion) {
|
|||||||
storeReviewDetailItems.value = parseStoreReview(row.storeReviewStatus)
|
storeReviewDetailItems.value = parseStoreReview(row.storeReviewStatus)
|
||||||
storeReviewDetailLive.value = false
|
storeReviewDetailLive.value = false
|
||||||
showStoreReviewDetail.value = true
|
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() {
|
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 state = (event.reviewState || '').toUpperCase()
|
||||||
|
const stage = (event.stage || '').toUpperCase()
|
||||||
if (state && state !== 'PENDING') {
|
if (state && state !== 'PENDING') {
|
||||||
|
// Use stage description for SUBMITTING transitions (排队→上传→审核中)
|
||||||
|
let stageHint = ''
|
||||||
|
if (state === 'SUBMITTING') {
|
||||||
|
stageHint = stage === 'QUEUED' ? '排队中' : stage === 'SUBMITTING' ? '正在上传' : ''
|
||||||
|
}
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: event.reviewReason
|
message: event.reviewReason
|
||||||
? `${storeName} → ${stateName}:${event.reviewReason}`
|
? `${storeName} → ${stageHint || stateName}:${event.reviewReason}`
|
||||||
: `${storeName} → ${stateName}`,
|
: `${storeName} → ${stageHint || stateName}`,
|
||||||
type: state === 'REJECTED' || state === 'WITHDRAWN' ? 'error'
|
type: state === 'REJECTED' || state === 'WITHDRAWN' ? 'error'
|
||||||
: state === 'APPROVED' ? 'success' : 'info',
|
: 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)
|
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(() => {
|
onBeforeUnmount(() => {
|
||||||
@ -2353,6 +2406,11 @@ onBeforeUnmount(() => {
|
|||||||
clearTimeout(storeReviewReloadTimer)
|
clearTimeout(storeReviewReloadTimer)
|
||||||
storeReviewReloadTimer = null
|
storeReviewReloadTimer = null
|
||||||
}
|
}
|
||||||
|
if (storeReviewAutoRefreshTimer) {
|
||||||
|
clearInterval(storeReviewAutoRefreshTimer)
|
||||||
|
storeReviewAutoRefreshTimer = null
|
||||||
|
}
|
||||||
|
stopDialogPoll()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户