feat(admin): 广告位管理页——上传/链接/绑定相册/开关/排序

Task 11(广告位轮播):admin 后台广告位治理页。

- utils/ads/adsLogic:scopeKind/normalizeAlbumIds/isValidAdLinkUrl 纯逻辑
  (与服务端 dto LINK_URL_PATTERN、albumIds 归一语义同源)+ vitest 6 例
- api/ads + types/api/ads.d.ts:list/create/update/delete/initUpload 五端点
  (对照 embedSlots.ts 逐字风格;列表全量不分页、imageUrl 现签)
- views/system/ads:列表(缩略图/备注名/链接截断/投放范围标签/启停开关
  失败重拉回滚/上移下移 sortOrder 互换/硬删确认)+ 新建编辑对话框
  (staging 直传 init→裸 fetch PUT、Content-Type 与申明一致;相册多选
  远程搜索复用 /v1/albums 分页+本地过滤,绑定 ID 超限/失效以裸 ID 兜底;
  编辑换图才带 image 三字段;albumIds 归一空→null 转全局)
- 路由/菜单:system 组 ads 页(ri:advertisement-line,workspace 分组);
  locales zh/en 补 menus.system.ads + adsManage 子树(键集一致入单测)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-29 01:21:33 +08:00
parent 7f9789eb46
commit 239ef5648f
9 changed files with 1001 additions and 3 deletions
+39
View File
@@ -0,0 +1,39 @@
/**
* 广告位管理接口(对齐 @photo/contracts tags [ads]:listAdminAds /
* createAd / updateAd / deleteAd / initAdUpload,广告位轮播 Task 11;仅 admin 可用)
*
* - 列表全量不分页(sortOrder 升序、并列 createdAt 升序);imageUrl 为
* 现签 GET,过期后重拉本列表刷新
* - 直传两步:init 签发 staging 凭证 → 裸 fetch PUT(预签名 URL 自带
* 凭证,Content-Type 与申明一致);无 complete 步——创建/更新端点核验
* staging 对象后 copyIfAbsent 转存版本化终键
* - PATCH 部分更新:换图三字段(imageStagingKey/imageBytes/imageContentType)
* 必须同时提供;albumIds null=转全局
* - DELETE 硬删(数据不保留、无恢复端点;幂等 404)
*/
import request from '@/utils/http'
/** 广告位列表(全量不分页) */
export function fetchAds() {
return request.get<Api.Ads.ListResponse>({ url: '/v1/admin/ads' })
}
/** 初始化广告图直传(staging 键 + 预签名凭证) */
export function fetchInitAdUpload(data: Api.Ads.UploadInitPayload) {
return request.post<Api.Ads.UploadInitResponse>({ url: '/v1/admin/ads/uploads', data })
}
/** 创建广告位(服务端核验 staging 后转存终键;albumIds 空=全局) */
export function fetchCreateAd(data: Api.Ads.CreatePayload) {
return request.post<Api.Ads.Item>({ url: '/v1/admin/ads', data })
}
/** 更新广告位(部分更新;换图三字段同时提供) */
export function fetchUpdateAd(id: string, data: Api.Ads.UpdatePayload) {
return request.patch<Api.Ads.Item>({ url: `/v1/admin/ads/${id}`, data })
}
/** 硬删除广告位(无恢复;重复删除 404) */
export function fetchDeleteAd(id: string) {
return request.del<void>({ url: `/v1/admin/ads/${id}` })
}