完善后台精选活动卡上传

后台精选活动卡支持选择图片并通过 OSS 直传凭证上传。

保存活动卡时同步 imageSrc 和 imageObjectKey。

移除后台素材包分类选项,清空分类时不再自动隐藏精选素材。

补充后台活动卡上传和分类交互测试。
This commit is contained in:
2026-07-06 18:57:20 +08:00
parent b5aa68fc6a
commit 68e9087552
4 changed files with 226 additions and 19 deletions
+78
View File
@@ -11,8 +11,11 @@ import type {
AdminDatabaseTableListResponse,
AdminDatabaseTableRowsQuery,
AdminDatabaseTableRowsResponse,
AdminCreateEditorShowcaseCampaignImageUploadTicketRequest,
AdminCreateEditorShowcaseCampaignImageUploadTicketResponse,
AdminEditorAssetListQuery,
AdminEditorAssetListResponse,
AdminDirectUploadTicketPayload,
AdminEditorShowcaseAssetResponse,
AdminEditorShowcaseCampaignResponse,
AdminEditorShowcaseDisplayRequest,
@@ -27,6 +30,7 @@ import type {
AdminTrackingEventListResponse,
AdminUpdateWorkVisibilityRequest,
AdminUpdateWorkVisibilityResponse,
AdminUploadedEditorShowcaseCampaignImage,
AdminUpsertEditorShowcaseCampaignRequest,
AdminUpsertProfileInviteCodeRequest,
AdminUpsertProfileRechargeProductRequest,
@@ -394,6 +398,32 @@ export function upsertAdminEditorShowcaseCampaign(
);
}
export async function uploadAdminEditorShowcaseCampaignImage(
token: string,
file: File,
): Promise<AdminUploadedEditorShowcaseCampaignImage> {
const contentType = resolveAdminImageContentType(file);
const response = await request<AdminCreateEditorShowcaseCampaignImageUploadTicketResponse>(
'/admin/api/editor-showcase/campaign/image-upload-ticket',
{
method: 'POST',
token,
body: {
fileName: file.name.trim() || 'showcase-campaign.png',
contentType,
contentLength: file.size,
} satisfies AdminCreateEditorShowcaseCampaignImageUploadTicketRequest,
},
);
await postAdminDirectUploadFile(response.upload, file);
const objectKey = response.upload.objectKey.trim().replace(/^\/+/u, '');
return {
imageSrc: objectKey ? `/${objectKey}` : response.upload.legacyPublicPath,
imageObjectKey: objectKey,
legacyPublicPath: response.upload.legacyPublicPath,
};
}
export function listProfileRedeemCodes(token: string) {
return request<ProfileRedeemCodeAdminListResponse>(
'/admin/api/profile/redeem-codes',
@@ -556,6 +586,54 @@ function buildAssetReadUrlQuery(query: AdminAssetReadUrlQuery) {
return queryString ? `?${queryString}` : '';
}
function resolveAdminImageContentType(file: File) {
const declaredType = file.type.trim();
if (declaredType.startsWith('image/')) {
return declaredType;
}
const extension = file.name.trim().toLowerCase().match(/\.([a-z0-9]+)$/u)?.[1];
if (extension === 'jpg' || extension === 'jpeg') {
return 'image/jpeg';
}
if (extension === 'png') {
return 'image/png';
}
if (extension === 'webp') {
return 'image/webp';
}
if (extension === 'gif') {
return 'image/gif';
}
return declaredType || 'application/octet-stream';
}
function buildAdminDirectUploadFormData(
upload: AdminDirectUploadTicketPayload,
file: File,
) {
const formData = new FormData();
Object.entries(upload.formFields).forEach(([key, value]) => {
if (value !== null && value !== undefined) {
formData.append(key, value);
}
});
formData.append('file', file, file.name);
return formData;
}
async function postAdminDirectUploadFile(
upload: AdminDirectUploadTicketPayload,
file: File,
) {
const response = await fetch(upload.host, {
method: 'POST',
body: buildAdminDirectUploadFormData(upload, file),
});
if (!response.ok) {
throw new Error(`上传活动卡图片失败:HTTP ${response.status}`);
}
}
function buildQueryString(query: AdminTrackingEventListQuery) {
const params = new URLSearchParams();
appendQueryParam(params, 'eventKey', query.eventKey);