@@ -0,0 +1,450 @@
< template >
< el-drawer
:model-value = "visible"
:title = "title"
size = "900px"
append -to -body
destroy -on -close
@close ="close"
>
< div class = "price-log-drawer" v-loading = "loading" >
< div class = "toolbar" >
< el-select v-model = "query.status" clearable :placeholder="phStatus" style="width: 120px" @change="reload" >
< el -option v-for = "o in statusOptions" :key="o.value" :label="o.label" :value="o.value" / >
< / el-select >
< el-button type = "primary" @click ="reload" > 查询 < / el -button >
< el-button @click ="resetQuery" > 重置 < / el -button >
< / div >
< el-empty v-if = "!timeTabs.length" :description="emptyText" / >
< template v-else >
< el -tabs v-model = "activeTime" class="time-tabs" @tab-change="onTimeTabChange" >
< el -tab -pane
v-for = "t in timeTabs"
:key = "t.key"
:name = "t.key"
:label = "t.label"
/ >
< / el-tabs >
< el-table :data = "productRows" border height = "calc(100vh - 320px)" :empty-text = "emptyProducts" >
< el-table-column :label = "colProduct" min -width = " 120 " prop = "productCode" show -overflow -tooltip / >
< el-table-column :label = "colAction" width = "90" align = "center" >
< template # default = "{ row }" > { { actionLabelOf ( row . action ) } } < / template >
< / el-table-column >
< el-table-column :label = "colOperator" width = "110" show -overflow -tooltip >
< template # default = "{ row }" > { { row . operator || '-' } } < / template >
< / el-table-column >
< el-table-column :label = "colCount" width = "100" align = "center" prop = "changeCount" / >
< el-table-column :label = "colStatus" width = "90" align = "center" >
< template # default = "{ row }" >
< el-tag size = "small" :type = "statusTag(row.status)" > { { statusLabel ( row . status ) } } < / el-tag >
< / template >
< / el-table-column >
< el-table-column :label = "colOps" width = "90" align = "center" fixed = "right" >
< template # default = "{ row }" >
< el-button link type = "primary" @click ="openProduct(row)" > 明细 < / el -button >
< / template >
< / el-table-column >
< / el-table >
< / template >
< / div >
< el-drawer v-model = "groupOpen" size="780px" :title="groupTitle" append -to -body destroy -on -close >
< div v-loading = "groupLoading" >
< el -table :data = "groupRows" border height = "calc(100vh - 220px)" :empty-text = "emptyGroup" >
< el-table-column :label = "colCountry" min -width = " 140 " show -overflow -tooltip >
< template # default = "{ row }" > { { countryOf ( row ) } } < / template >
< / el-table-column >
< el-table-column :label = "colAction" width = "90" align = "center" >
< template # default = "{ row }" > { { actionLabel ( row ) } } < / template >
< / el-table-column >
< el-table-column :label = "colChangeSummary" min -width = " 220 " show -overflow -tooltip >
< template # default = "{ row }" > { { changeSummary ( row ) } } < / template >
< / el-table-column >
< el-table-column :label = "colStatus" width = "90" align = "center" >
< template # default = "{ row }" >
< el-tag size = "small" :type = "statusTag(row.status)" > { { statusLabel ( row . status ) } } < / el-tag >
< / template >
< / el-table-column >
< el-table-column :label = "colTime" width = "170" align = "center" prop = "updateTime" / >
< el-table-column :label = "colOps" width = "90" align = "center" fixed = "right" >
< template # default = "{ row }" >
< el-button link type = "primary" @click ="handleView(row)" > 查看 < / el -button >
< / template >
< / el-table-column >
< / el-table >
< div class = "pager" >
< el-pagination
v -model :current-page = "groupQuery.pageNum"
v -model :page-size = "groupQuery.pageSize"
:total = "groupTotal"
: page -sizes = " [ 10 , 20 , 50 , 100 ] "
layout = "total, sizes, prev, pager, next"
@ size -change = " reloadGroup "
@ current -change = " reloadGroup "
/ >
< / div >
< / div >
< / el-drawer >
< el-drawer v-model = "detailOpen" size="720px" :title="detailTitle" append -to -body destroy -on -close >
< div v-loading = "detailLoading" >
< el -empty v-if = "!detail" :description="emptyDetail" / >
< div v-else >
< el -descriptions :column = "2" border size = "small" class = "mb12" >
< el-descriptions-item :label = "lbAction" > { { actionLabel ( detail ) } } < / el-descriptions-item >
< el-descriptions-item :label = "lbOperator" > { { operatorOf ( detail ) } } < / el-descriptions-item >
< el-descriptions-item :label = "lbStatus" >
< el-tag size = "small" :type = "statusTag(detail.status)" > { { statusLabel ( detail . status ) } } < / el-tag >
< / el-descriptions-item >
< el-descriptions-item :label = "lbTime" > { { detail . updateTime || '-' } } < / el-descriptions-item >
< el-descriptions-item :label = "lbProduct" > { { productCodeOf ( detail ) } } < / el-descriptions-item >
< el-descriptions-item :label = "lbCountry" > { { countryOf ( detail ) } } < / el-descriptions-item >
< / el-descriptions >
< div class = "change-panel" >
< div class = "change-panel__head" > 改前改后 < / div >
< el-table :data = "changesOf(detail)" border size = "small" :empty-text = "emptyChanges" >
< el-table-column prop = "fieldLabel" :label = "colField" min -width = " 120 " / >
< el-table-column prop = "oldValue" :label = "colOld" min -width = " 140 " show -overflow -tooltip / >
< el-table-column prop = "newValue" :label = "colNew" min -width = " 140 " show -overflow -tooltip / >
< / el-table >
< / div >
< / div >
< / div >
< / el-drawer >
< / el-drawer >
< / template >
< script setup name = "SetVersionPriceLogDrawer" >
import { listPriceLog , listPriceLogGroups , getPriceLog } from '@/api/monitor/priceLog'
const props = defineProps ( {
modelValue : { type : Boolean , default : false } ,
version : { type : Object , default : null }
} )
const emit = defineEmits ( [ 'update:modelValue' ] )
const visible = computed ( {
get : ( ) => props . modelValue ,
set : v => emit ( 'update:modelValue' , v )
} )
const loading = ref ( false )
const allGroups = ref ( [ ] )
const query = reactive ( { status : undefined } )
const activeTime = ref ( '' )
const groupOpen = ref ( false )
const groupLoading = ref ( false )
const groupRows = ref ( [ ] )
const groupTotal = ref ( 0 )
const groupTitle = ref ( '' )
const activeProduct = ref ( null )
const groupQuery = reactive ( { pageNum : 1 , pageSize : 20 } )
const detailOpen = ref ( false )
const detailLoading = ref ( false )
const detail = ref ( null )
const detailTitle = ref ( '' )
const phStatus = '状态'
const emptyText = '该版本暂无价格变更记录'
const emptyProducts = '该时间点暂无产品变更'
const emptyGroup = '该产品暂无明细变更'
const emptyDetail = '暂无详情'
const emptyChanges = '无字段变更'
const colAction = '操作'
const colOperator = '操作人'
const colProduct = '产品代码'
const colCountry = '国家'
const colCount = '变更条数'
const colChangeSummary = '变更摘要'
const colStatus = '状态'
const colTime = '写入时间'
const colOps = '操作'
const colField = '字段'
const colOld = '改前'
const colNew = '改后'
const lbAction = '操作'
const lbOperator = '操作人'
const lbStatus = '状态'
const lbTime = '写入时间'
const lbProduct = '产品代码'
const lbCountry = '国家'
const statusOptions = [
{ label : '成功' , value : 'SUCCESS' } ,
{ label : '失败' , value : 'FAIL' }
]
const actionOptions = [
{ label : '新增' , value : 'INSERT' } ,
{ label : '修改' , value : 'UPDATE' } ,
{ label : '删除' , value : 'DELETE' } ,
{ label : '导入' , value : 'IMPORT' }
]
const title = computed ( ( ) => {
const v = props . version || { }
const ver = v . versionNo || ''
const eff = v . effectiveTime || ''
const base = '版本价格变更记录'
if ( ver || eff ) return base + ' · ' + ( ver || '' ) + ( eff ? ' (' + eff + ')' : '' )
return base
} )
/** Minute key: yyyy-MM-dd HH:mm */
function toMinuteKey ( time ) {
if ( ! time ) return '-'
const s = String ( time ) . trim ( )
return s . length >= 16 ? s . slice ( 0 , 16 ) : s
}
const timeTabs = computed ( ( ) => {
const map = new Map ( )
for ( const g of allGroups . value || [ ] ) {
const key = toMinuteKey ( g && g . updateTime )
if ( ! map . has ( key ) ) {
map . set ( key , { key , label : key , changeCount : 0 , productSet : new Set ( ) } )
}
const tab = map . get ( key )
tab . changeCount += Number ( g . changeCount ) || 0
if ( g . productCode ) tab . productSet . add ( g . productCode )
}
return Array . from ( map . values ( ) )
. sort ( ( a , b ) => String ( b . key ) . localeCompare ( String ( a . key ) ) )
. map ( t => ( {
key : t . key ,
label : t . key + ' (' + t . productSet . size + ')' ,
changeCount : t . changeCount
} ) )
} )
const productRows = computed ( ( ) => {
const minute = activeTime . value
if ( ! minute ) return [ ]
const map = new Map ( )
for ( const g of allGroups . value || [ ] ) {
if ( toMinuteKey ( g && g . updateTime ) !== minute ) continue
const code = g . productCode || '-'
let row = map . get ( code )
if ( ! row ) {
row = {
productCode : code ,
action : g . action ,
operator : g . operator ,
status : g . status ,
changeCount : 0 ,
updateTimePrefix : minute
}
map . set ( code , row )
}
row . changeCount += Number ( g . changeCount ) || 0
if ( g . status === 'FAIL' ) row . status = 'FAIL'
if ( ! row . operator && g . operator ) row . operator = g . operator
if ( ! row . action && g . action ) row . action = g . action
}
return Array . from ( map . values ( ) ) . sort ( ( a , b ) => String ( a . productCode ) . localeCompare ( String ( b . productCode ) ) )
} )
function close ( ) {
visible . value = false
}
function resetQuery ( ) {
query . status = undefined
reload ( )
}
function onTimeTabChange ( ) {
groupOpen . value = false
detailOpen . value = false
}
function parseReq ( row ) {
if ( ! row || ! row . requestJson ) return { }
try {
return typeof row . requestJson === 'object' ? row . requestJson : JSON . parse ( row . requestJson )
} catch ( e ) {
return { }
}
}
function actionLabelOf ( action ) {
const found = actionOptions . find ( i => i . value === action )
return found ? found . label : ( action || '-' )
}
function actionLabel ( row ) {
return actionLabelOf ( parseReq ( row ) . action )
}
function operatorOf ( row ) {
return parseReq ( row ) . operator || '-'
}
function productCodeOf ( row ) {
const keys = parseReq ( row ) . bizKeys || { }
return keys . productCode || '-'
}
function countryOf ( row ) {
const keys = parseReq ( row ) . bizKeys || { }
if ( keys . countryCode && keys . countryName ) return keys . countryName + ' (' + keys . countryCode + ')'
return keys . countryCode || keys . countryName || '-'
}
function changesOf ( row ) {
const req = parseReq ( row )
return Array . isArray ( req . changes ) ? req . changes : [ ]
}
function changeSummary ( row ) {
const list = changesOf ( row )
if ( ! list . length ) return '-'
return list . map ( c => {
const label = ( c && c . fieldLabel ) || ( c && c . field ) || ''
const oldV = c && c . oldValue != null ? c . oldValue : ''
const newV = c && c . newValue != null ? c . newValue : ''
return label + ': ' + oldV + ' -> ' + newV
} ) . join ( '; ' )
}
function statusLabel ( status ) {
const found = statusOptions . find ( item => item . value === status )
if ( found ) return found . label
return status === 'FAIL' ? '失败' : '成功'
}
function statusTag ( status ) {
return status === 'FAIL' ? 'danger' : 'success'
}
async function reload ( ) {
const psvId = props . version && props . version . psvId
if ( psvId == null ) {
allGroups . value = [ ]
activeTime . value = ''
return
}
loading . value = true
try {
const res = await listPriceLogGroups ( {
pageNum : 1 ,
pageSize : 500 ,
status : query . status ,
psvId ,
priceKind : 'SALES'
} )
allGroups . value = res . rows || [ ]
const tabs = timeTabs . value
if ( ! tabs . length ) {
activeTime . value = ''
} else if ( ! tabs . some ( t => t . key === activeTime . value ) ) {
activeTime . value = tabs [ 0 ] . key
}
} finally {
loading . value = false
}
}
function openProduct ( row ) {
if ( ! row ) return
activeProduct . value = row
groupTitle . value = '变更明细 · ' + ( row . productCode || '-' ) + ( activeTime . value ? ' @ ' + activeTime . value : '' )
groupQuery . pageNum = 1
groupOpen . value = true
reloadGroup ( )
}
async function reloadGroup ( ) {
const psvId = props . version && props . version . psvId
const p = activeProduct . value
if ( psvId == null || ! p ) {
groupRows . value = [ ]
groupTotal . value = 0
return
}
groupLoading . value = true
try {
const res = await listPriceLog ( {
pageNum : groupQuery . pageNum ,
pageSize : groupQuery . pageSize ,
status : query . status ,
psvId ,
priceKind : 'SALES' ,
productCode : p . productCode === '-' ? undefined : p . productCode ,
updateTimePrefix : activeTime . value || p . updateTimePrefix || undefined
} )
groupRows . value = res . rows || [ ]
groupTotal . value = Number ( res . total ) || 0
} finally {
groupLoading . value = false
}
}
function handleView ( row ) {
detailTitle . value = '价格变更 #' + ( row . bizId || row . docId )
detailOpen . value = true
detailLoading . value = true
detail . value = null
getPriceLog ( row . docId ) . then ( res => {
detail . value = res . data || row
} ) . finally ( ( ) => {
detailLoading . value = false
} )
}
watch (
( ) => [ props . modelValue , props . version && props . version . psvId ] ,
( [ open ] ) => {
if ( open ) {
activeTime . value = ''
reload ( )
} else {
groupOpen . value = false
detailOpen . value = false
detail . value = null
activeProduct . value = null
allGroups . value = [ ]
}
}
)
< / script >
< style scoped >
. price - log - drawer { min - height : 240 px ; }
. toolbar {
display : flex ;
align - items : center ;
gap : 8 px ;
margin - bottom : 12 px ;
}
. time - tabs {
margin - bottom : 8 px ;
}
. time - tabs : deep ( . el - tabs _ _header ) {
margin - bottom : 8 px ;
}
. pager {
display : flex ;
justify - content : flex - end ;
margin - top : 12 px ;
}
. mb12 { margin - bottom : 12 px ; }
. change - panel {
margin - bottom : 12 px ;
border : 1 px solid # e8ecf0 ;
border - radius : 6 px ;
overflow : hidden ;
}
. change - panel _ _head {
padding : 8 px 12 px ;
font - size : 13 px ;
font - weight : 600 ;
color : # 606266 ;
background : # fafbfc ;
border - bottom : 1 px solid # ebeef5 ;
}
< / style >