@@ -13,15 +13,6 @@
< el-form-item label = "解析状态" > < el-select v-model = "query.parseStatus" clearable placeholder="全部" style="width: 110px"><el-option label="成功" value="success" /><el-option label="存疑" value="uncertain" /><el-option label="失败" value="failed" / > < / el-select > < / el-form-item >
< el-form-item class = "toolbar-actions" > < el-button type = "primary" :icon = "Search" @click ="search" > 查询 < / el -button > < el-button :icon = "RefreshLeft" @click ="reset" > 重置 < / el -button > < el-button v-if = "canPurchase" type="primary" :disabled="selectedProducts.length === 0" @click="openPurchaseBatch" > 创建采购 < / el -button > < / el-form-item >
< / el-form >
< div v-if = "activeRun" class="sync-banner" :class="`is-${activeRun.status}`" aria-live="polite" >
< div >
< strong > { { syncStatus ( activeRun . status ) . label } } < / strong >
< span > { { activeRun . dateFrom } } 至 { { activeRun . dateTo } } < / span >
< span v-if = "activeRun.status === 'running'" > 已处理 {{ activeRun.daysProcessed }} / {{ activeRun.daysTotal }} 天 , {{ activeRun.detailCount }} 条商品明细 < / span >
< span v-else-if = "activeRun.errorMessage" > {{ activeRun.errorMessage }} < / span >
< / div >
< el -button type = "primary" link @click ="$router.push({ path: '/syb-sync-runs/index', query: { runId: activeRun.id }})" > 查看详情 < / el -button >
< / div >
< el-alert v-if = "canPurchase" title="表头全选只选择当前页中可以创建采购任务的商品;不可创建的原因显示在“采购准备”列。" type="info" :closable="false" show-icon class="notice compact-notice" / >
< el-table ref = "productTable" v-loading = "loading" :data="products" row-key="id" border stripe empty-text="暂无 SYB 商品明细" @selection-change="handleSelectionChange" >
< el -table -column v-if = "canPurchase" type="selection" width="48" :selectable="rowSelectable" / >
@@ -127,7 +118,7 @@
< script >
import { ElMessage } from 'element-plus'
import { Search , RefreshLeft } from '@element-plus/icons-vue'
import { listSybProducts , getSybProduct , reparseSybProduct , correctSybProduct , listSybSyncRuns , getSybSyncRun } from '@/api/goauto/syb-products'
import { listSybProducts , getSybProduct , reparseSybProduct , correctSybProduct } from '@/api/goauto/syb-products'
import { listSybShops } from '@/api/goauto/syb-shops'
import { listDevices } from '@/api/goauto/devices'
import { createPurchaseTasksBatch , previewPurchaseTasks } from '@/api/goauto/purchase-tasks'
@@ -144,7 +135,6 @@ export default {
purchaseDialog : { open : false , loading : false , saving : false , ids : [ ] , deviceId : null , items : [ ] , eligibleCount : 0 , skippedCount : 0 } ,
purchaseResult : { open : false , items : [ ] , createdCount : 0 , failedCount : 0 } ,
query : { page : 1 , pageSize : 20 , shopName : '' , orderCodesText : '' , parseStatus : '' } ,
activeRun : null , syncPollTimer : null ,
detail : { open : false , loading : false , item : null } ,
correct : { open : false , saving : false , productId : null , targetColor : '' , targetSize : '' }
}
@@ -157,8 +147,7 @@ export default {
try { return JSON . stringify ( JSON . parse ( this . detail . item . rawJson ) , null , 2 ) } catch { return this . detail . item . rawJson }
}
} ,
created ( ) { this . load ( ) ; this . loadLatestSyncRun ( ) } ,
beforeUnmount ( ) { this . stopSyncPolling ( ) } ,
created ( ) { this . load ( ) } ,
methods : {
async load ( allowNetworkRetry = true ) {
const orderCodes = this . normalizedOrderCodes ( )
@@ -234,7 +223,6 @@ export default {
} ,
priceText ( row ) { if ( row . unitPriceCent === null || row . unitPriceCent === undefined ) return '—' ; return ` ¥ ${ ( row . unitPriceCent / 100 ) . toFixed ( 2 ) } ` } ,
parseMeta ( status ) { return { success : { label : '成功' , type : 'success' } , uncertain : { label : '存疑' , type : 'warning' } , failed : { label : '失败' , type : 'danger' } } [ status ] || { label : status || '-' , type : 'info' } } ,
syncStatus ( status ) { return { running : { label : '正在同步' } , succeeded : { label : '同步完成' } , failed : { label : '同步失败' } , interrupted : { label : '同步已中断' } } [ status ] || { label : status || '未知状态' } } ,
openShopeeDetail ( shopeeProductId , targetColor = '' ) {
if ( ! shopeeProductId ) { ElMessage . warning ( '该明细尚未关联虾皮商品' ) ; return }
const query = { productId : shopeeProductId }
@@ -286,36 +274,6 @@ export default {
} finally { this . purchaseDialog . saving = false }
} ,
// ---------------- SYB 自动同步状态 ----------------
async loadLatestSyncRun ( ) {
try {
const response = await listSybSyncRuns ( { page : 1 , pageSize : 1 } , { suppressNetworkError : true , suppressErrorMessage : true } )
const latest = Array . isArray ( response . data ? . items ) ? response . data . items [ 0 ] : null
if ( ! latest ) return
this . activeRun = latest
if ( latest . status === 'running' ) this . startSyncPolling ( latest . id )
} catch {
// 同步状态是辅助信息,失败不能阻断商品列表。
}
} ,
startSyncPolling ( runId ) {
this . stopSyncPolling ( )
const poll = async ( ) => {
try {
const r = await getSybSyncRun ( runId )
this . activeRun = r . data . item
if ( this . activeRun . status !== 'running' ) {
this . stopSyncPolling ( )
await this . load ( )
ElMessage [ this . activeRun . status === 'succeeded' ? 'success' : 'warning' ] ( this . syncStatus ( this . activeRun . status ) . label )
}
} catch { this . stopSyncPolling ( ) }
}
poll ( )
this . syncPollTimer = window . setInterval ( poll , 3000 )
} ,
stopSyncPolling ( ) { if ( this . syncPollTimer ) { window . clearInterval ( this . syncPollTimer ) ; this . syncPollTimer = null } } ,
// ---------------- 详情 ----------------
async openDetail ( id ) {
this . detail = { open : true , loading : true , item : null }
@@ -369,7 +327,6 @@ export default {
. toolbar - actions : deep ( . el - button + . el - button ) { margin - left : 0 }
. selection - count { display : inline - flex ; align - items : center ; min - height : 32 px ; white - space : nowrap ; color : # 606266 }
. selection - count strong { margin : 0 4 px ; font - variant - numeric : tabular - nums ; color : # 1 e40af }
. sync - banner { display : flex ; align - items : center ; justify - content : space - between ; gap : 16 px ; padding : 12 px 16 px ; margin - bottom : 12 px ; border : 1 px solid # bfdbfe ; border - radius : 8 px ; background : # eff6ff } . sync - banner > div { display : flex ; flex - wrap : wrap ; gap : 8 px 16 px } . sync - banner . is - failed , . sync - banner . is - interrupted { border - color : # fecaca ; background : # fef2f2 } . sync - banner . is - succeeded { border - color : # bbf7d0 ; background : # f0fdf4 }
. primary { font - weight : 600 ; color : # 1 f2937 } . muted { font - size : 12 px ; color : # 909399 }
. missing { color : # b91c1c }
. thumb { width : 44 px ; height : 44 px ; border - radius : 4 px ; object - fit : cover } . thumb . placeholder { display : flex ; align - items : center ; justify - content : center ; background : # f1f5f9 ; color : # 909399 ; font - size : 11 px }