feat(market): 我的发布记录回显 appealStatus(生产者飞轮端到端收口)

my-publish-records 经 MuseMarketAppealMapper.selectLatestByAssetIdAndUser +
resolveAppealStatus 回填该物化资产被当前发布者发起的最新申诉态到记录 appealStatus
(无物化资产/无申诉则 null;marketAssetId 与 appealStatus 两路共用各只查一次)。
前端 MarketPublish 发布记录副文本以中文徽标渲染「申诉:<待处理/审核中/已关闭…>」。

活体证(反假绿,MSW off 直连重建后单体 48080):
- market-publish.spec.ts 第 8 例 chromium 绿(recId=5/marketAssetId=2 回显 appealStatus=closed→「申诉:已关闭」)。
- 后端 MarketPublishServiceTest 14/14 回归通过(appealMapper 注入无 NPE);前端 tsc 干净 + vitest 47/47 + market-publish 8/8。

至此市场生产者飞轮端到端完整:发布(草稿→检查→提交)→上架状态可视化→
申诉(提交/补充材料/撤回)全生命周期 + 申诉态回显。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lili 2026-06-15 07:08:09 -07:00
parent ce6bbd8625
commit 90daa3088b
5 changed files with 46 additions and 7 deletions

File diff suppressed because one or more lines are too long

View File

@ -5,12 +5,14 @@ import cn.iocoder.muse.framework.common.pojo.PageResult;
import cn.iocoder.muse.framework.common.util.json.JsonUtils;
import cn.iocoder.muse.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.muse.module.market.dal.dataobject.muse.MuseMarketAccountProjectionDO;
import cn.iocoder.muse.module.market.dal.dataobject.muse.MuseMarketAppealDO;
import cn.iocoder.muse.module.market.dal.dataobject.muse.MuseMarketAssetDO;
import cn.iocoder.muse.module.market.dal.dataobject.muse.MuseMarketCommandDO;
import cn.iocoder.muse.module.market.dal.dataobject.muse.MuseMarketPublishCheckDO;
import cn.iocoder.muse.module.market.dal.dataobject.muse.MuseMarketPublishDraftDO;
import cn.iocoder.muse.module.market.dal.dataobject.muse.MuseMarketPublishRequestDO;
import cn.iocoder.muse.module.market.dal.dataobject.muse.MuseMarketReviewEventDO;
import cn.iocoder.muse.module.market.dal.mysql.muse.MuseMarketAppealMapper;
import cn.iocoder.muse.module.market.dal.mysql.muse.MuseMarketAssetMapper;
import cn.iocoder.muse.module.market.dal.mysql.muse.MuseMarketPublishCheckMapper;
import cn.iocoder.muse.module.market.dal.mysql.muse.MuseMarketPublishDraftMapper;
@ -70,6 +72,7 @@ public class MarketPublishServiceImpl implements MarketPublishService {
private final MuseMarketReviewEventMapper reviewEventMapper;
@SuppressWarnings("unused")
private final MuseMarketAssetMapper assetMapper;
private final MuseMarketAppealMapper appealMapper;
private final MarketCommandService commandService;
private final MarketAccountProjectionProvider accountProjectionProvider;
@ -565,13 +568,16 @@ public class MarketPublishServiceImpl implements MarketPublishService {
MuseMarketPublishRequestDO request) {
MuseMarketPublishDraftDO draft = request.getAssetId() == null ? null
: publishDraftMapper.selectOwnedById(tenantId, request.getAssetId(), loginUserId);
// marketAssetId(物化资产 id) appealStatus(该资产最新申诉态)只依赖 request,两路共用各只查一次
String marketAssetId = resolveMarketAssetId(tenantId, loginUserId, request.getAssetId());
String appealStatus = resolveAppealStatus(tenantId, loginUserId, marketAssetId);
if (draft == null) {
log.warn("[listMyPublishRecords][发布申请缺少可回读草稿tenantId={}, userId={}, requestId={}, assetId={}]",
tenantId, loginUserId, request.getId(), request.getAssetId());
return assetType == null ? new PublishRecordItem(String.valueOf(request.getId()),
String.valueOf(request.getAssetId()), null, String.valueOf(request.getDraftVersion()),
request.getStatus(), nextAction(request.getStatus()), null, null, "publisher_only",
resolveMarketAssetId(tenantId, loginUserId, request.getAssetId())) : null;
request.getStatus(), nextAction(request.getStatus()), null, appealStatus, "publisher_only",
marketAssetId) : null;
}
if (StringUtils.hasText(assetType) && !assetType.equals(draft.getAssetType())) {
return null;
@ -585,9 +591,9 @@ public class MarketPublishServiceImpl implements MarketPublishService {
request.getStatus(),
nextAction(request.getStatus()),
null,
null,
appealStatus,
"publisher_only",
resolveMarketAssetId(tenantId, loginUserId, request.getAssetId()));
marketAssetId);
}
/**
@ -607,6 +613,19 @@ public class MarketPublishServiceImpl implements MarketPublishService {
return String.valueOf(asset.getId());
}
/**
* 回显该物化资产被当前发布者发起的最新申诉状态(pending/reviewing/supplementing/maintained/restored/closed),
* 无物化资产或无申诉则 nullWHY:我的发布记录直接呈现申诉进展,无需另开我的申诉对照
*/
private String resolveAppealStatus(Long tenantId, Long loginUserId, String marketAssetId) {
if (marketAssetId == null) {
return null;
}
MuseMarketAppealDO appeal = appealMapper.selectLatestByAssetIdAndUser(tenantId,
Long.valueOf(marketAssetId), loginUserId);
return appeal == null ? null : appeal.getStatus();
}
private String nextAction(String status) {
if (STATUS_DRAFT.equals(status)) {
return "run_check";

View File

@ -45,6 +45,16 @@ public interface MuseMarketAppealMapper extends BaseMapperX<MuseMarketAppealDO>
.orderByDesc(MuseMarketAppealDO::getId));
}
/** 查某资产被当前发布者发起的最新一条申诉,供「我的发布记录」回显 appealStatus。 */
default MuseMarketAppealDO selectLatestByAssetIdAndUser(Long tenantId, Long assetId, Long userId) {
return selectOne(new LambdaQueryWrapperX<MuseMarketAppealDO>()
.eq(MuseMarketAppealDO::getTenantId, tenantId)
.eq(MuseMarketAppealDO::getAssetId, assetId)
.eq(MuseMarketAppealDO::getUserId, userId)
.orderByDesc(MuseMarketAppealDO::getId)
.last("LIMIT 1"));
}
default PageResult<MuseMarketAppealDO> selectAdminPage(Long tenantId, String appealType, String status,
Long appellantId, Long assetId,
Integer pageNo, Integer pageSize) {

View File

@ -168,4 +168,14 @@ test.describe('市场生产侧发布草稿(真实后端, MSW off)', () => {
expect((await resp.json()).code).toBe(0);
await expect(page.getByText(/材料已补充/)).toBeVisible({ timeout: 15_000 });
});
// appealStatus 记录回显:有申诉的物化资产记录在「我的发布记录」副文本显示「申诉:<中文状态>」(后端 my-publish-records join 申诉表)。
// 前置asset_id=2 的记录(marketAssetId 非空)已有申诉(种子 + 申诉提交测产生)。
test('我的发布记录回显 appealStatus(申诉:中文状态)', async ({ page }) => {
await seedToken(page);
await page.goto('/market/publish');
await expect(
page.getByText(/申诉:(待处理|审核中|待补充材料|已关闭|维持原判|已恢复|部分恢复)/).first()
).toBeVisible({ timeout: 15_000 });
});
});

View File

@ -314,7 +314,7 @@ export default function MarketPublish() {
<div className="mt-1 text-xs text-slate-400">
v{rec.version}
{rec.nextAction ? ` · 下一步:${NEXT_ACTION_LABEL[rec.nextAction] ?? rec.nextAction}` : ''}
{rec.appealStatus ? ` · 申诉:${rec.appealStatus}` : ''}
{rec.appealStatus ? ` · 申诉:${appealStatusMeta(rec.appealStatus).label}` : ''}
</div>
</div>
<div className="flex shrink-0 items-center gap-2">