feat(admin): country drag-sort + always-available member detail sync

- Country 加 sort_order(默认 0 保持 id 序);PATCH /countries/sort 批量保存
  顺序(对齐 /tags/sort 模式);findAll 与公开 /public/countries 均按
  sortOrder 排序
- CountriesView 表格改为可拖拽行列表:拖动松开即全量保存新顺序,失败回滚
- 商品编辑弹窗成员展开面板:同步详情按钮常驻(已同步显示 重新同步详情),
  不再只在未同步态出现
- goods.service.spec 的 FamilyRecomputeService mock 补齐 syncFamilyTags 等
  方法(全量并行时其他套件的扫名归族会把本套件夹具收进族,create/update
  会调用到,mock 缺方法导致偶发 TypeError)
- api 164/164、admin typecheck+22/22+构建全绿
This commit is contained in:
yeuimu
2026-08-28 20:00:56 +08:00
parent 77f9c05346
commit 1e07c62fc8
12 changed files with 261 additions and 60 deletions
@@ -647,12 +647,17 @@ async function handleDeleteGood() {
</el-table>
</div>
<div v-if="row.source === 'SDS'" class="member-extra">
<div v-if="!memberDetail(row.id) || memberDetail(row.id).loading" class="md-empty">详情加载中</div>
<div v-else-if="!memberDetail(row.id).hasDetail" class="md-empty">
尚未同步详情
<el-button size="small" :icon="Refresh" :loading="detailSyncing === row.id" @click="handleSyncMemberDetail(row)">同步详情</el-button>
<div class="member-detail-head">
<span v-if="!memberDetail(row.id) || memberDetail(row.id).loading" class="md-empty">详情加载中</span>
<span v-else-if="!memberDetail(row.id).hasDetail" class="md-empty">尚未同步详情</span>
<el-button
size="small"
:icon="Refresh"
:loading="detailSyncing === row.id"
@click="handleSyncMemberDetail(row)"
>{{ memberDetail(row.id)?.hasDetail ? '重新同步详情' : '同步详情' }}</el-button>
</div>
<el-tabs v-else class="member-detail-tabs">
<el-tabs v-if="memberDetail(row.id) && memberDetail(row.id).hasDetail" class="member-detail-tabs">
<el-tab-pane label="商品详情">
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="商品编码">{{ memberDetail(row.id).detail.productCode || '-' }}</el-descriptions-item>
@@ -908,6 +913,11 @@ async function handleDeleteGood() {
display: flex; align-items: center; justify-content: space-between; gap: 8px;
font-size: 12px; color: var(--el-text-color-placeholder); padding: 4px 0;
}
.member-detail-head {
display: flex; align-items: center; justify-content: space-between; gap: 8px;
margin-bottom: 4px;
}
.member-detail-head .md-empty { padding: 0; }
.member-detail-tabs :deep(.el-tabs__header) { margin-bottom: 8px; }
.edit-family-loading { color: var(--el-text-color-placeholder); font-size: 12px; padding: 4px 0; }
.family-detail-tabs { margin-top: 10px; }