listing-overview-table.component.html 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. <section aria-labelledby="overview-table-title">
  2. <div class="table-heading">
  3. <div><h2 id="overview-table-title">Listing 评分列表</h2><p>默认按可提升分降序,商品 ID 始终作为稳定次级排序键。</p></div>
  4. @if (currentCursor) { <button type="button" class="page-action" (click)="firstPage.emit()">回到第一页</button> }
  5. </div>
  6. <div class="desktop-table">
  7. <table>
  8. <thead><tr>
  9. <th scope="col">商品</th><th scope="col">分类 / 状态</th>
  10. <th scope="col"><button type="button" (click)="toggleSort('overallScore')">总分 {{ sortIndicator('overallScore') }}</button></th>
  11. @for (dimension of dimensions; track dimension.key) { <th scope="col"><button type="button" (click)="toggleSort(dimension.key)">{{ dimension.label }} {{ sortIndicator(dimension.key) }}</button></th> }
  12. <th scope="col">最弱维度</th><th scope="col"><button type="button" (click)="toggleSort('improvementPotential')">可提升分 {{ sortIndicator('improvementPotential') }}</button></th><th scope="col">覆盖率 / 版本</th><th scope="col">优先级</th>
  13. <th scope="col">评分性质</th><th scope="col"><button type="button" (click)="toggleSort('scoredAt')">评分时间 {{ sortIndicator('scoredAt') }}</button></th><th scope="col">操作</th>
  14. </tr></thead>
  15. <tbody>
  16. @for (item of items; track item.productId) {
  17. <tr [class.simulation-row]="item.scoreNature === 'simulation'">
  18. <td><div class="product-cell">@if (item.imageUrl) { <img [src]="item.imageUrl" [alt]="item.title || '商品图片'"> } @else { <span class="image-placeholder" aria-label="无商品图片">无图</span> }<div><strong>{{ item.title || '暂无商品标题' }}</strong><span>ID {{ item.productId }}</span></div></div></td>
  19. <td><strong>{{ item.categoryName || '未分类' }}</strong><small>{{ item.categoryPath.length ? item.categoryPath.join(' / ') : item.itemStatusLabel }}</small><span class="status-text">{{ item.itemStatusLabel }}</span></td>
  20. <td class="overall-cell"><strong class="overall-score" [class.missing]="item.overallScore === null">{{ overallText(item) }}</strong></td>
  21. @for (dimension of dimensions; track dimension.key) { <td><strong [class.zero-score]="dimensionValue(item,dimension.key)?.score === 0" [class.missing]="dimensionValue(item,dimension.key)?.score === null">{{ dimensionText(item, dimension.key) }}</strong><small>{{ rateText(dimensionValue(item,dimension.key)?.rate ?? null) }}</small></td> }
  22. <td>{{ item.weakestDimension ? dimensionLabels[item.weakestDimension] : '无法判断' }}</td>
  23. <td><strong>{{ item.improvementPotential === null ? '未知' : item.improvementPotential + ' 分' }}</strong></td>
  24. <td><strong>{{ item.coveragePercent === null || item.coveragePercent === undefined ? '未知' : item.coveragePercent + '%' }}</strong><small>{{ item.rubricVersion || '未评分版本' }}</small></td><td><span class="priority-cell">{{ item.priority || '—' }}</span></td>
  25. <td><span class="nature-badge" [class.simulation]="item.scoreNature === 'simulation'">{{ item.scoreNatureLabel }}</span>@if (item.scoreNature === 'simulation') { <small class="simulation-note">非正式业务评分</small> }</td>
  26. <td><time [attr.datetime]="item.scoredAt || null">{{ item.scoredAt ? (item.scoredAt | date:'yyyy-MM-dd HH:mm') : '尚未评分' }}</time><small>同步 {{ item.syncedAt | date:'MM-dd HH:mm' }}</small></td>
  27. <td><a class="detail-link" [routerLink]="['/listing-ai/workbench', item.productId]" [queryParams]="{ returnTo: returnTo }" (click)="detailOpen.emit()">查看详情</a></td>
  28. </tr>
  29. }
  30. </tbody>
  31. </table>
  32. </div>
  33. <div class="mobile-list" aria-label="移动端 Listing 评分列表">
  34. @for (item of items; track item.productId) {
  35. <article class="mobile-card" [class.simulation-row]="item.scoreNature === 'simulation'">
  36. <header><div><strong>{{ item.title || '暂无商品标题' }}</strong><span>ID {{ item.productId }} · {{ item.categoryName || '未分类' }}</span></div><strong class="mobile-score">{{ overallText(item) }}{{ item.overallScore === null ? '' : ' 分' }}</strong></header>
  37. <div class="mobile-meta"><span>{{ item.itemStatusLabel }}</span><span>{{ item.coveragePercent === null || item.coveragePercent === undefined ? '覆盖未知' : '覆盖 ' + item.coveragePercent + '%' }}</span><span class="nature-badge" [class.simulation]="item.scoreNature === 'simulation'">{{ item.scoreNatureLabel }}{{ item.scoreNature === 'simulation' ? ' · 非正式业务评分' : '' }}</span><span>{{ item.priority || '无优先级' }}</span></div>
  38. <div class="mobile-dimensions">@for (dimension of dimensions; track dimension.key) { <span><small>{{ dimension.label }}</small><strong>{{ dimensionText(item, dimension.key) }}</strong><small>{{ rateText(dimensionValue(item,dimension.key)?.rate ?? null) }}</small></span> }</div>
  39. <footer><span>最弱:{{ item.weakestDimension ? dimensionLabels[item.weakestDimension] : '无法判断' }}</span><span>可提升:{{ item.improvementPotential === null ? '未知' : item.improvementPotential + ' 分' }}</span><a [routerLink]="['/listing-ai/workbench', item.productId]" [queryParams]="{ returnTo: returnTo }" (click)="detailOpen.emit()">查看详情</a></footer>
  40. </article>
  41. }
  42. </div>
  43. <div class="pagination" aria-label="游标分页">
  44. @if (currentCursor) { <button type="button" (click)="firstPage.emit()" [disabled]="loading">第一页</button> }
  45. @if (nextCursor) { <button type="button" class="next" (click)="nextPage.emit()" [disabled]="loading">{{ loading ? '加载中…' : '下一页' }}</button> }
  46. @if (!nextCursor && items.length) { <span>已到当前筛选结果末页</span> }
  47. </div>
  48. </section>