@@ -1,11 +1,13 @@
< script setup lang = "ts" >
import { formatDateTime } from '@/utils/datetime'
/** 店铺数据任务/记录页:每店铺卡片网格(分组 /任务状态/站点/文件 下载删除)+ 筛选 + 批量下载 + 数据范围授权。 */
/** 店铺数据任务/记录页:每条任务一张卡片(店铺名 /任务号/ 状态/分组/最新/国家/文件 + 下载文件/删除)
* + 筛选 + 批量下载 + 数据范围授权(对齐 admin.js renderShopDataRecordTable)。 */
import { computed , onMounted , reactive , ref } from 'vue'
import { ElMessage , ElMessageBox } from 'element-plus'
import { asinCountryLabel } from '../asin/asin-country.ts'
import { createShopDataFilter , type ShopDataFilter } from './shop-data-filter.ts'
import type { ShopDataResultRow , ShopDataTaskGroup } from './shop-data-model.ts'
import { isUsableShopDataResult } from './shop-data-model.ts'
import {
deleteShopDataResultHistory ,
fetchShopDataResultDownload ,
@@ -94,8 +96,9 @@ function resultFileCount(): number {
return groups . value . reduce ( ( sum , group ) => sum + group . results . length , 0 )
}
/** 可勾选行(对齐 admin.js syncShopDataSelectionUi: file_ready && resultId>0,不附加终态约束)。 */
function selectableRows ( ) : ShopDataResultRow [ ] {
return allResultRows . value . filter ( ( row ) => row . fileReady && isTerminalStatus ( row . status ) )
return allResultRows . value . filter ( ( row ) => isUsableShopDataResult ( row ) )
}
function toggleRow ( row : ShopDataResultRow ) {
@@ -150,9 +153,9 @@ async function downloadBatch() {
}
}
async function removeRow ( group : ShopDataTaskGroup , row : ShopDataResultRow ) {
async function removeRowByRow ( row : ShopDataResultRow ) {
try {
await ElMessageBox . confirm ( ` 确认删除店铺“ ${ group . shopName } ”的任务 ${ row . taskNo || row . resultId } 及结果文件吗 ? ` , '删除前确认' , {
await ElMessageBox . confirm ( ` 确认删除店铺“ ${ row . shopName } ”的任务 ${ row . taskNo || row . resultId } 及结果文件? ` , '删除前确认' , {
confirmButtonText : '确认删除' ,
cancelButtonText : '取消' ,
type : 'warning' ,
@@ -275,63 +278,65 @@ onMounted(load)
/>
</div>
<div v-if=" groups . length " class=" shop - card - grid ">
<section v-for=" group in groups " :key=" group . shopId || group . shopName " class=" shop - card ">
<div v-if=" allResultRows . length " class=" shop - card - grid ">
<article v-for=" row in allResultRows " :key=" row . resultId || ` ${ row . taskNo } - ${ row . filename } ` " class=" shop - card ">
<header class=" shop - card - head ">
<span class=" shop - name ">{{ g roup .shopName }}</span>
<el-tag v-if=" group . groupName " size=" small " effect=" plain ">{{ group.groupName }}</el-tag>
<span v-if=" group . latestCreatedAt " class=" shop - latest ">{{ formatDateTime(group.latestCreatedAt) }}</span>
<span class=" shop - name " :title=" row . shopName || '-' " >{{ row .shopName || '-' }}</span>
</header>
<div class=" file - list ">
<div v-for=" row in group . results " :key=" row . resultId " class=" file - row ">
<div class=" shop - card - select - row ">
<el-checkbox
:model-value=" rowSelected ( row ) "
:disabled=" ! ( row . fileReady && isTerminalStatus ( row . status ) ) "
:disabled=" ! isUsableShopDataResult ( row ) "
@change=" toggleRow ( row ) "
/>
<div class=" file - main " >
<div class=" file - top ">
<span class=" file - task ">任务 {{ row.taskNo || row.resultId }}</span>
<span class=" shop - card - task - no ">任务 {{ row.taskNo || row.resultId }}</span >
<el-tag :type=" statusType ( row . status ) " size=" small ">{{ statusLabel(row.status) }}</el-tag>
</div>
<div class=" shop - card - row ">
<span class=" shop - card - label ">分组</span>
<span class=" shop - card - value ">{{ row.groupName || '-' }}</span>
</div>
<div class=" shop - card - row ">
<span class=" shop - card - label ">最新</span>
<span class=" shop - card - value ">{{ formatDateTime(row.updatedAt || row.createdAt) || '-' }}</span>
</div>
<div class=" shop - card - row ">
<span class=" shop - card - label ">国家</span>
<span class=" shop - card - value ">
<span v-for=" code in row . countryCodes " :key=" code " class=" country - chip ">{{ asinCountryLabel(code) }}</span>
<span v-if=" ! row . countryCodes . length " class=" dim ">-</span>
</span>
</div>
<div class=" file - name " :title=" row . filename ">{{ row.filename || '未命名文件' }}</div >
<div class=" file - meta " >
<span>{{ row.user name || '— ' }}</span>
<span v-if=" row . rowCount != null ">· {{ row.rowCount }} 行</span>
<span v-if=" row . fileSize != null ">· {{ row.fileSize }} 字节</span>
<span>· {{ formatDateTime(row.createdAt) }}</span>
<div class=" shop - card - row " >
<span class=" shop - card - label ">文件</span >
<span class=" shop - card - value shop - card - file " :title=" row . filename " >{{ row.file name || '- ' }}</span>
</div>
</div >
<div class=" file - actions ">
<div class=" shop - card - actions " >
<el-button
text
type=" success "
size=" small "
:loading=" busyKey === row . resultId "
:disabled=" ! row . fileReady "
:disabled=" ! isUsableShopDataResult ( row ) "
@click=" downloadRow ( row ) "
>
下载
下载文件
</el-button>
<el-button
v-if=" isTerminalStatus ( row . status ) "
v-if=" isTerminalStatus ( row . status ) && ! ! row . resultId "
text
type=" danger "
size=" small "
:loading=" deletingKey === row . resultId "
@click=" removeRow ( group , row ) "
@click=" removeRowByRow ( row ) "
>
删除
</el-button>
</div>
</div >
<div v-if=" ! group . results . length " class=" file - empty ">暂无结果文件</div>
</div>
</section>
</article >
</div>
<el-empty v-else-if=" ! loading " description=" 暂无店铺数据任务 " />
<el-empty v-else-if=" ! loading " description=" 暂无符合条件的 店铺数据任务 " />
<el-dialog v-model=" permissionVisible " title=" 店铺数据任务数据范围授权 " width=" 620 px ">
<p class=" dim ">勾选允许查看/操作店铺数据任务的用户(仅数据范围)。当前已授权 {{ grantedCount }} 人。</p>
@@ -365,17 +370,13 @@ onMounted(load)
. shop - card { border : 1 px solid var ( -- el - border - color ) ; border - radius : 12 px ; background : # fff ; box - shadow : 0 1 px 2 px rgba ( 39 , 67 , 94 , 0.04 ) , 0 14 px 30 px - 24 px rgba ( 39 , 67 , 94 , 0.28 ) ; overflow : hidden ; }
. shop - card - head { display : flex ; align - items : center ; gap : 10 px ; padding : 12 px 16 px ; border - bottom : 1 px solid var ( -- el - border - color - lighter ) ; }
. shop - name { font - weight : 600 ; color : var ( -- el - text - color - primary ) ; }
. shop - latest { margin - left : auto ; color : var ( -- el - text - color - secondary ) ; font - size : 12.5 px ; }
. file - list { padding : 4 px 16 px ; }
. file - row { display : flex ; align - items : flex - start ; gap : 12 px ; padding : 12 px 0 ; border - bottom : 1 px dashed var ( -- el - border - color - lighter ) ; }
. file - row : last - child { border - bottom : 0 ; }
. file - main { flex : 1 ; min - width : 0 ; }
. file - top { display : flex ; align - items : center ; gap : 8 px ; flex - wrap : wrap ; }
. file - task { font - weight : 600 ; font - size : 13 px ; color : var ( -- el - text - color - primary ) ; }
. country - chip { padding : 1 px 8 px ; border - radius : 999 px ; background : var ( -- el - fill - color - light ) ; color : var ( -- el - text - color - regular ) ; font - size : 12 px ; }
. file - name { margin - top : 6 px ; overflow : hidden ; white - space : nowrap ; text - overflow : ellipsis ; color : var ( -- el - text - color - regular ) ; font - size : 13 px ; }
. file - meta { margin - top : 4 px ; color : var ( -- el - text - color - secondary ) ; font - size : 12.5 px ; }
. file - actions { display : flex ; gap : 6 px ; }
. file - empty { padding : 14 px 0 ; color : var ( -- el - text - color - secondary ) ; font - size : 13 px ; }
. shop - card - select - row { display : flex ; align - items : center ; gap : 10 px ; padding : 10 px 16 px 0 ; }
. shop - card - task - no { font - weight : 600 ; font - size : 13 px ; color : var ( -- el - text - color - primary ) ; }
. shop - card - row { display : flex ; align - items : flex - start ; gap : 10 px ; padding : 6 px 16 px ; font - size : 13 px ; }
. shop - card - label { flex : none ; width : 52 px ; color : var ( -- el - text - color - secondary ) ; }
. shop - card - value { color : var ( -- el - text - color - regular ) ; min - width : 0 ; }
. shop - card - file { overflow : hidden ; white - space : nowrap ; text - overflow : ellipsis ; }
. shop - card - actions { display : flex ; gap : 6 px ; padding : 4 px 16 px 12 px ; }
. country - chip { padding : 1 px 8 px ; margin - right : 4 px ; border - radius : 999 px ; background : var ( -- el - fill - color - light ) ; color : var ( -- el - text - color - regular ) ; font - size : 12 px ; }
. dim { color : var ( -- el - text - color - secondary ) ; font - size : 12 px ; }
< / style >