feat(tenant-platform): service activation realtime + store review retry
- Add IM real-time notification for service activation approval/rejection (LicenseManagementView, PushManagementView, AppDetailView) - Add retry button for FAILED and REJECTED stores in version review detail - Refactor storeReviewRealtime to single shared IM connection - Bump @xuqm/vue3-sdk to 0.2.3 (fixes sendSync crash when SDK uninitialized) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
父节点
ce05fa4fe3
当前提交
6b891bee92
@ -20,5 +20,8 @@
|
|||||||
"@vue/test-utils": "^2.4.9",
|
"@vue/test-utils": "^2.4.9",
|
||||||
"happy-dom": "^20.9.0",
|
"happy-dom": "^20.9.0",
|
||||||
"vitest": "^4.1.5"
|
"vitest": "^4.1.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@xuqm/vue3-sdk": "0.2.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
"@xuqm/vue3-sdk": "^0.2.2",
|
"@xuqm/vue3-sdk": "^0.2.3",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"element-plus": "^2.9.1",
|
"element-plus": "^2.9.1",
|
||||||
"pinia": "^3.0.1",
|
"pinia": "^3.0.1",
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { ImClient, type ImMessage } from '@xuqm/vue3-sdk'
|
import { ImClient, type ImMessage, init as initSdk } from '@xuqm/vue3-sdk'
|
||||||
import client from '@/api/client'
|
import client from '@/api/client'
|
||||||
|
|
||||||
|
// Initialize the SDK global so ImClient.sendSync() can send the /app/chat.sync frame.
|
||||||
|
// Only appKey is required; no secret is exposed here.
|
||||||
|
initSdk({ appKey: 'ak_409e217e4aa14254ad73ad3c' })
|
||||||
|
|
||||||
export interface StoreReviewRefreshEvent {
|
export interface StoreReviewRefreshEvent {
|
||||||
event: string
|
event: string
|
||||||
appKey: string
|
appKey: string
|
||||||
@ -16,25 +20,39 @@ export interface StoreReviewRefreshEvent {
|
|||||||
timestamp?: number
|
timestamp?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ServiceActivationRefreshEvent {
|
||||||
|
event: string
|
||||||
|
appKey: string
|
||||||
|
serviceType: string
|
||||||
|
status: string
|
||||||
|
reviewNote: string
|
||||||
|
timestamp?: number
|
||||||
|
}
|
||||||
|
|
||||||
interface PlatformEventTokenResponse {
|
interface PlatformEventTokenResponse {
|
||||||
userId: string
|
userId: string
|
||||||
token: string
|
token: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Single shared connection — all platform event types go through one WebSocket.
|
||||||
let imClient: ImClient | null = null
|
let imClient: ImClient | null = null
|
||||||
let activeAppKey = ''
|
let activeAppKey = ''
|
||||||
|
let storeReviewHandler: ((event: StoreReviewRefreshEvent) => void) | null = null
|
||||||
|
let serviceActivationHandler: ((event: ServiceActivationRefreshEvent) => void) | null = null
|
||||||
|
|
||||||
function sdkWsUrl() {
|
function sdkWsUrl() {
|
||||||
return import.meta.env.VITE_IM_WS_URL ?? ''
|
return import.meta.env.VITE_IM_WS_URL ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseEvent(message: ImMessage): StoreReviewRefreshEvent | null {
|
function parseAnyEvent(message: ImMessage): StoreReviewRefreshEvent | ServiceActivationRefreshEvent | null {
|
||||||
try {
|
try {
|
||||||
const raw = JSON.parse(message.content) as Record<string, unknown>
|
const raw = JSON.parse(message.content) as Record<string, unknown>
|
||||||
const payload = (raw?.payload && typeof raw.payload === 'object' ? raw.payload : raw) as Record<string, unknown>
|
const payload = (raw?.payload && typeof raw.payload === 'object' ? raw.payload : raw) as Record<string, unknown>
|
||||||
const event = String(payload.event ?? raw?.event ?? '')
|
const event = String(payload.event ?? raw?.event ?? '')
|
||||||
const appKey = String(payload.appKey ?? raw?.appKey ?? '')
|
const appKey = String(payload.appKey ?? raw?.appKey ?? '')
|
||||||
if (event !== 'store_review_update' || !appKey) return null
|
if (!appKey) return null
|
||||||
|
|
||||||
|
if (event === 'store_review_update') {
|
||||||
return {
|
return {
|
||||||
event,
|
event,
|
||||||
appKey,
|
appKey,
|
||||||
@ -48,14 +66,36 @@ function parseEvent(message: ImMessage): StoreReviewRefreshEvent | null {
|
|||||||
source: String(payload.source ?? raw?.source ?? ''),
|
source: String(payload.source ?? raw?.source ?? ''),
|
||||||
timestamp: Number(payload.timestamp ?? raw?.timestamp ?? Date.now()),
|
timestamp: Number(payload.timestamp ?? raw?.timestamp ?? Date.now()),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event === 'service_activation_update') {
|
||||||
|
return {
|
||||||
|
event,
|
||||||
|
appKey,
|
||||||
|
serviceType: String(payload.serviceType ?? raw?.serviceType ?? ''),
|
||||||
|
status: String(payload.status ?? raw?.status ?? ''),
|
||||||
|
reviewNote: String(payload.reviewNote ?? raw?.reviewNote ?? ''),
|
||||||
|
timestamp: Number(payload.timestamp ?? raw?.timestamp ?? Date.now()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
} catch {
|
} catch {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function connectStoreReviewRealtime(appKey: string, onEvent: (event: StoreReviewRefreshEvent) => void) {
|
function disconnectAll() {
|
||||||
if (!appKey) return
|
if (imClient) {
|
||||||
disconnectStoreReviewRealtime()
|
imClient.disconnect()
|
||||||
|
imClient = null
|
||||||
|
}
|
||||||
|
activeAppKey = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureConnection(appKey: string) {
|
||||||
|
if (imClient && activeAppKey === appKey) return
|
||||||
|
disconnectAll()
|
||||||
activeAppKey = appKey
|
activeAppKey = appKey
|
||||||
|
|
||||||
const res = await client.get<{ data: PlatformEventTokenResponse }>('/im/platform-events/token', {
|
const res = await client.get<{ data: PlatformEventTokenResponse }>('/im/platform-events/token', {
|
||||||
@ -65,7 +105,7 @@ export async function connectStoreReviewRealtime(appKey: string, onEvent: (event
|
|||||||
const platformToken = tokenData.token
|
const platformToken = tokenData.token
|
||||||
|
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
console.debug('[tenant-platform][IM] store review realtime token acquired', {
|
console.debug('[tenant-platform][IM] platform event connection established', {
|
||||||
appKey,
|
appKey,
|
||||||
userId: tokenData.userId,
|
userId: tokenData.userId,
|
||||||
})
|
})
|
||||||
@ -74,25 +114,32 @@ export async function connectStoreReviewRealtime(appKey: string, onEvent: (event
|
|||||||
const wsUrl = sdkWsUrl() || undefined
|
const wsUrl = sdkWsUrl() || undefined
|
||||||
const clientInstance = new ImClient({ tokenSupplier: () => platformToken, wsUrl })
|
const clientInstance = new ImClient({ tokenSupplier: () => platformToken, wsUrl })
|
||||||
clientInstance.on('message', (message) => {
|
clientInstance.on('message', (message) => {
|
||||||
const event = parseEvent(message)
|
const event = parseAnyEvent(message)
|
||||||
if (!event || event.appKey !== activeAppKey) return
|
if (!event || event.appKey !== activeAppKey) return
|
||||||
onEvent(event)
|
if (event.event === 'store_review_update' && storeReviewHandler) {
|
||||||
|
storeReviewHandler(event as StoreReviewRefreshEvent)
|
||||||
|
} else if (event.event === 'service_activation_update' && serviceActivationHandler) {
|
||||||
|
serviceActivationHandler(event as ServiceActivationRefreshEvent)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
clientInstance.on('error', (error) => {
|
clientInstance.on('error', (error) => {
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
console.warn('[tenant-platform][IM] store review realtime error', error)
|
console.warn('[tenant-platform][IM] platform event error', error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
clientInstance.connect()
|
clientInstance.connect()
|
||||||
imClient = clientInstance
|
imClient = clientInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function connectStoreReviewRealtime(appKey: string, onEvent: (event: StoreReviewRefreshEvent) => void) {
|
||||||
|
if (!appKey) return
|
||||||
|
storeReviewHandler = onEvent
|
||||||
|
await ensureConnection(appKey)
|
||||||
|
}
|
||||||
|
|
||||||
export function disconnectStoreReviewRealtime() {
|
export function disconnectStoreReviewRealtime() {
|
||||||
if (imClient) {
|
storeReviewHandler = null
|
||||||
imClient.disconnect()
|
if (!serviceActivationHandler) disconnectAll()
|
||||||
imClient = null
|
|
||||||
}
|
|
||||||
activeAppKey = ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function notifyStoreReviewRefresh(appKey: string) {
|
export function notifyStoreReviewRefresh(appKey: string) {
|
||||||
@ -100,3 +147,17 @@ export function notifyStoreReviewRefresh(appKey: string) {
|
|||||||
ElMessage.info('检测到审核状态更新,正在刷新...')
|
ElMessage.info('检测到审核状态更新,正在刷新...')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function connectServiceActivationRealtime(
|
||||||
|
appKey: string,
|
||||||
|
onEvent: (event: ServiceActivationRefreshEvent) => void,
|
||||||
|
) {
|
||||||
|
if (!appKey) return
|
||||||
|
serviceActivationHandler = onEvent
|
||||||
|
await ensureConnection(appKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function disconnectServiceActivationRealtime() {
|
||||||
|
serviceActivationHandler = null
|
||||||
|
if (!storeReviewHandler) disconnectAll()
|
||||||
|
}
|
||||||
|
|||||||
@ -215,6 +215,10 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
|||||||
import { View } from '@element-plus/icons-vue'
|
import { View } from '@element-plus/icons-vue'
|
||||||
import { appApi, type App, type FeatureService } from '@/api/app'
|
import { appApi, type App, type FeatureService } from '@/api/app'
|
||||||
import client from '@/api/client'
|
import client from '@/api/client'
|
||||||
|
import {
|
||||||
|
connectServiceActivationRealtime,
|
||||||
|
disconnectServiceActivationRealtime,
|
||||||
|
} from '@/services/storeReviewRealtime'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const app = ref<App | null>(null)
|
const app = ref<App | null>(null)
|
||||||
@ -383,13 +387,24 @@ function updateViewport() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
const appKey = route.params.appKey as string
|
||||||
loadData()
|
loadData()
|
||||||
updateViewport()
|
updateViewport()
|
||||||
window.addEventListener('resize', updateViewport)
|
window.addEventListener('resize', updateViewport)
|
||||||
|
void connectServiceActivationRealtime(appKey, (event) => {
|
||||||
|
if (event.status === 'APPROVED') {
|
||||||
|
ElMessage.success(`${serviceLabel(event.serviceType)} 已审核通过`)
|
||||||
|
loadData()
|
||||||
|
} else if (event.status === 'REJECTED') {
|
||||||
|
ElMessage.error(`${serviceLabel(event.serviceType)} 审核未通过:${event.reviewNote || '请联系运营'}`)
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('resize', updateViewport)
|
window.removeEventListener('resize', updateViewport)
|
||||||
|
disconnectServiceActivationRealtime()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -126,6 +126,11 @@ import { useRoute, useRouter } from 'vue-router'
|
|||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { licenseApi, type AppLicense, type LicenseDevice } from '@/api/license'
|
import { licenseApi, type AppLicense, type LicenseDevice } from '@/api/license'
|
||||||
import { appApi, type App } from '@/api/app'
|
import { appApi, type App } from '@/api/app'
|
||||||
|
import {
|
||||||
|
connectServiceActivationRealtime,
|
||||||
|
disconnectServiceActivationRealtime,
|
||||||
|
type ServiceActivationRefreshEvent,
|
||||||
|
} from '@/services/storeReviewRealtime'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -261,12 +266,26 @@ function updateViewport() {
|
|||||||
isMobile.value = window.innerWidth < 768
|
isMobile.value = window.innerWidth < 768
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function connectLicenseRealtime(key: string) {
|
||||||
|
void connectServiceActivationRealtime(key, (event: ServiceActivationRefreshEvent) => {
|
||||||
|
if (event.serviceType !== 'LICENSE') return
|
||||||
|
if (event.status === 'APPROVED') {
|
||||||
|
ElMessage.success('授权管理服务已审核通过')
|
||||||
|
checkServiceEnabled(key)
|
||||||
|
} else if (event.status === 'REJECTED') {
|
||||||
|
ElMessage.error(`授权管理服务审核未通过:${event.reviewNote || '请联系运营'}`)
|
||||||
|
checkServiceEnabled(key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
watch(appKey, (key) => {
|
watch(appKey, (key) => {
|
||||||
if (isServicesPortal.value) {
|
if (isServicesPortal.value) {
|
||||||
if (key) checkServiceEnabled(key)
|
if (key) checkServiceEnabled(key)
|
||||||
} else {
|
} else {
|
||||||
loadData()
|
loadData()
|
||||||
}
|
}
|
||||||
|
if (key) connectLicenseRealtime(key)
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -278,13 +297,15 @@ onMounted(() => {
|
|||||||
currentApp.value = portalApps.value.find(item => item.appKey === appKey.value) ?? currentApp.value
|
currentApp.value = portalApps.value.find(item => item.appKey === appKey.value) ?? currentApp.value
|
||||||
})
|
})
|
||||||
if (appKey.value) checkServiceEnabled(appKey.value)
|
if (appKey.value) checkServiceEnabled(appKey.value)
|
||||||
return
|
} else {
|
||||||
}
|
|
||||||
loadData()
|
loadData()
|
||||||
|
}
|
||||||
|
if (appKey.value) connectLicenseRealtime(appKey.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('resize', updateViewport)
|
window.removeEventListener('resize', updateViewport)
|
||||||
|
disconnectServiceActivationRealtime()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -173,6 +173,11 @@ import { useRoute, useRouter } from 'vue-router'
|
|||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { appApi, type App } from '@/api/app'
|
import { appApi, type App } from '@/api/app'
|
||||||
import { pushAdminApi, type DeviceLoginLog, type TestPushResult, type UserPushStatus } from '@/api/push'
|
import { pushAdminApi, type DeviceLoginLog, type TestPushResult, type UserPushStatus } from '@/api/push'
|
||||||
|
import {
|
||||||
|
connectServiceActivationRealtime,
|
||||||
|
disconnectServiceActivationRealtime,
|
||||||
|
type ServiceActivationRefreshEvent,
|
||||||
|
} from '@/services/storeReviewRealtime'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -308,8 +313,22 @@ function formatDateTime(iso: string): string {
|
|||||||
return new Date(iso).toLocaleString('zh-CN')
|
return new Date(iso).toLocaleString('zh-CN')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function connectPushRealtime(key: string) {
|
||||||
|
void connectServiceActivationRealtime(key, (event: ServiceActivationRefreshEvent) => {
|
||||||
|
if (event.serviceType !== 'PUSH') return
|
||||||
|
if (event.status === 'APPROVED') {
|
||||||
|
ElMessage.success('离线推送服务已审核通过')
|
||||||
|
checkServiceEnabled()
|
||||||
|
} else if (event.status === 'REJECTED') {
|
||||||
|
ElMessage.error(`离线推送服务审核未通过:${event.reviewNote || '请联系运营'}`)
|
||||||
|
checkServiceEnabled()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
watch(appKey, (key) => {
|
watch(appKey, (key) => {
|
||||||
if (isServicesPortal.value && key) checkServiceEnabled()
|
if (isServicesPortal.value && key) checkServiceEnabled()
|
||||||
|
if (key) connectPushRealtime(key)
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -319,9 +338,13 @@ onMounted(() => {
|
|||||||
checkServiceEnabled()
|
checkServiceEnabled()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (appKey.value) connectPushRealtime(appKey.value)
|
||||||
window.addEventListener('resize', updateViewport)
|
window.addEventListener('resize', updateViewport)
|
||||||
})
|
})
|
||||||
onBeforeUnmount(() => window.removeEventListener('resize', updateViewport))
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('resize', updateViewport)
|
||||||
|
disconnectServiceActivationRealtime()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -581,10 +581,20 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Rejection / withdrawal reason -->
|
<!-- Rejection / withdrawal reason -->
|
||||||
<div v-if="item.reason && (item.state === 'REJECTED' || item.state === 'WITHDRAWN')" class="review-card-reason">
|
<div v-if="item.reason && (item.state === 'REJECTED' || item.state === 'WITHDRAWN' || item.state === 'FAILED')" class="review-card-reason">
|
||||||
<el-icon><WarningFilled /></el-icon>
|
<el-icon><WarningFilled /></el-icon>
|
||||||
<span>{{ item.reason }}</span>
|
<span>{{ item.reason }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Retry button for failed or rejected submissions -->
|
||||||
|
<div v-if="item.state === 'FAILED' || item.state === 'REJECTED'" class="review-card-retry">
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
size="small"
|
||||||
|
:loading="retryingStores.has(item.store)"
|
||||||
|
@click="handleRetryStore(item.store)"
|
||||||
|
>重新提交</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -1397,6 +1407,7 @@ const storeReviewDetailVersion = ref<AppVersion | null>(null)
|
|||||||
const storeReviewDetailItems = ref<{ store: string; state: string; reason?: string; stage?: string; submittedAt?: string; updatedAt?: string; batchId?: string }[]>([])
|
const storeReviewDetailItems = ref<{ store: string; state: string; reason?: string; stage?: string; submittedAt?: string; updatedAt?: string; batchId?: string }[]>([])
|
||||||
const storeReviewDetailLive = ref(false)
|
const storeReviewDetailLive = ref(false)
|
||||||
const cancellingReview = ref(false)
|
const cancellingReview = ref(false)
|
||||||
|
const retryingStores = ref<Set<string>>(new Set())
|
||||||
const showPublishSchedule = ref(false)
|
const showPublishSchedule = ref(false)
|
||||||
const publishScheduleType = ref<'IMMEDIATE' | 'SCHEDULED'>('IMMEDIATE')
|
const publishScheduleType = ref<'IMMEDIATE' | 'SCHEDULED'>('IMMEDIATE')
|
||||||
const publishScheduleAt = ref('')
|
const publishScheduleAt = ref('')
|
||||||
@ -1425,6 +1436,26 @@ async function handleCancelReview(storeType?: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleRetryStore(storeType: string) {
|
||||||
|
if (!storeReviewDetailVersion.value) return
|
||||||
|
retryingStores.value = new Set([...retryingStores.value, storeType])
|
||||||
|
try {
|
||||||
|
await updateAdminApi.executeSubmitToStores(
|
||||||
|
storeReviewDetailVersion.value.id,
|
||||||
|
[storeType] as any,
|
||||||
|
storeReviewDetailVersion.value.storeSubmitMode ?? 'MANUAL',
|
||||||
|
)
|
||||||
|
ElMessage.success(`${storeLabel(storeType)} 重试已提交`)
|
||||||
|
const idx = storeReviewDetailItems.value.findIndex(i => i.store === storeType)
|
||||||
|
if (idx >= 0) storeReviewDetailItems.value[idx] = { ...storeReviewDetailItems.value[idx], state: 'PENDING', stage: 'QUEUED' }
|
||||||
|
await loadAppVersions()
|
||||||
|
} catch {
|
||||||
|
ElMessage.error(`${storeLabel(storeType)} 重试失败,请稍后再试`)
|
||||||
|
} finally {
|
||||||
|
retryingStores.value = new Set([...retryingStores.value].filter(s => s !== storeType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleUpdatePublishSchedule() {
|
async function handleUpdatePublishSchedule() {
|
||||||
if (!storeReviewDetailVersion.value) return
|
if (!storeReviewDetailVersion.value) return
|
||||||
updatingPublishSchedule.value = true
|
updatingPublishSchedule.value = true
|
||||||
@ -1996,14 +2027,14 @@ function storeLabel(type: string) {
|
|||||||
function reviewLabel(state: string): string {
|
function reviewLabel(state: string): string {
|
||||||
return {
|
return {
|
||||||
PENDING: '待提交', SUBMITTING: '提交中', UNDER_REVIEW: '审核中',
|
PENDING: '待提交', SUBMITTING: '提交中', UNDER_REVIEW: '审核中',
|
||||||
APPROVED: '已通过', REJECTED: '已拒绝', WITHDRAWN: '已撤回',
|
APPROVED: '已通过', REJECTED: '已拒绝', WITHDRAWN: '已撤回', FAILED: '提交失败',
|
||||||
}[state] ?? state
|
}[state] ?? state
|
||||||
}
|
}
|
||||||
|
|
||||||
function reviewTagType(state: string): string {
|
function reviewTagType(state: string): string {
|
||||||
return {
|
return {
|
||||||
PENDING: 'info', SUBMITTING: 'primary', UNDER_REVIEW: 'warning',
|
PENDING: 'info', SUBMITTING: 'primary', UNDER_REVIEW: 'warning',
|
||||||
APPROVED: 'success', REJECTED: 'danger', WITHDRAWN: '',
|
APPROVED: 'success', REJECTED: 'danger', WITHDRAWN: '', FAILED: 'danger',
|
||||||
}[state] ?? ''
|
}[state] ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2637,6 +2668,10 @@ onBeforeUnmount(() => {
|
|||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.review-store-card.review-card--failed {
|
||||||
|
border-color: var(--el-color-danger-light-5);
|
||||||
|
}
|
||||||
|
|
||||||
.review-card-head {
|
.review-card-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -2786,4 +2821,9 @@ onBeforeUnmount(() => {
|
|||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
.review-card-retry {
|
||||||
|
margin-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1429,10 +1429,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
vue "^3.5.13"
|
vue "^3.5.13"
|
||||||
|
|
||||||
"@xuqm/vue3-sdk@^0.2.2":
|
"@xuqm/vue3-sdk@0.2.3", "@xuqm/vue3-sdk@^0.2.3":
|
||||||
version "0.2.2"
|
version "0.2.3"
|
||||||
resolved "https://nexus.xuqinmin.com/repository/npm-hosted/@xuqm/vue3-sdk/-/vue3-sdk-0.2.2.tgz"
|
resolved "https://nexus.xuqinmin.com/repository/npm-hosted/@xuqm/vue3-sdk/-/vue3-sdk-0.2.3.tgz#b0100a309bf4179a43d98e21e24759b020221797"
|
||||||
integrity sha512-1fZrqEcPHf7E7LLIx2QhWG8s/yWxmNa2QNyvhvmsvyp2/LkO0uX++l/4aHZqvfiqlUDhcr892I4YdRoYuzAcQw==
|
integrity sha512-lUQSQNaYyrji8WwL/N4Xswdzyo4MrbwqSHsghC2Sg/zEGtuTAxo+D7t8pK1wTRLXwR5P2ZutYhE75OIFhXMFLg==
|
||||||
|
|
||||||
abbrev@^2.0.0:
|
abbrev@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户