All checks were successful
Build and Deploy LTY / build-and-deploy (push) Successful in 1h5m35s
- Update card models, serializers, views and URLs - Update dances, songs, users admin pages and API modules - Add card migrations (merge furniture into decoration) - Update middleware and settings Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
332 lines
9.9 KiB
TypeScript
332 lines
9.9 KiB
TypeScript
import type { Song } from "./types"
|
||
import { apiClient } from "./client"
|
||
import type { PaginatedResponse, PaginationParams } from "./client"
|
||
import {
|
||
CardSong,
|
||
CardSongResponse,
|
||
CardSongDetail,
|
||
CardSongDetailResponse,
|
||
SongBatch,
|
||
SongBatchesResponse,
|
||
CreateBatchRequest,
|
||
CreateBatchResponse,
|
||
MarkBatchAsProducedResponse,
|
||
CreateSongRequest,
|
||
UpdateSongRequest,
|
||
UploadSongFileResponse,
|
||
UploadSongFileRequest,
|
||
PublishSongResponse
|
||
} from "./song.type"
|
||
|
||
// 将API返回的歌曲数据转换为应用内使用的格式
|
||
export const transformCardSongToSong = (cardSong: CardSong): Song => {
|
||
return {
|
||
id: String(cardSong.id),
|
||
name: cardSong.name,
|
||
composer: cardSong.attributes.composer || "",
|
||
lyricist: cardSong.attributes.lyricist || "",
|
||
duration: cardSong.attributes.duration || "",
|
||
releaseDate: cardSong.published_at ? new Date(cardSong.published_at).toISOString().split('T')[0] : "",
|
||
status: cardSong.status_display,
|
||
image: cardSong.image_url || "/placeholder.svg?height=300&width=300",
|
||
audioUrl: cardSong.attributes.audio_file,
|
||
description: cardSong.description,
|
||
rarity: cardSong.rarity_display,
|
||
cardType: cardSong.card_type_display,
|
||
genre: cardSong.attributes.genre,
|
||
lyrics: cardSong.attributes.lyrics,
|
||
rawData: cardSong // 存储完整的原始数据
|
||
}
|
||
}
|
||
|
||
// 获取歌曲列表
|
||
export const getSongs = async (params?: PaginationParams): Promise<PaginatedResponse<Song>> => {
|
||
try {
|
||
const page = params?.page || 1;
|
||
const pageSize = params?.pageSize || 10;
|
||
const searchParam = params?.search ? `&search=${encodeURIComponent(params.search)}` : '';
|
||
|
||
const response = await apiClient.get(
|
||
`/card/category/song/?page_size=${pageSize}&page=${page}${searchParam}`
|
||
);
|
||
|
||
const data: CardSongResponse = response.data;
|
||
|
||
if (!data.success) {
|
||
throw new Error(data.message || '获取歌曲列表失败');
|
||
}
|
||
|
||
// 保存完整的歌曲数据,包括原始数据
|
||
const songs = data.data.results.map((item: any) => {
|
||
const attributes = item.attributes || {};
|
||
return {
|
||
id: String(item.id),
|
||
name: item.name,
|
||
composer: attributes.composer || '',
|
||
lyricist: attributes.lyricist || '',
|
||
duration: attributes.duration || '',
|
||
releaseDate: item.published_at ? new Date(item.published_at).toISOString().split('T')[0] : '',
|
||
status: item.status_display,
|
||
image: item.image_url || '/placeholder.svg?height=300&width=300',
|
||
audioUrl: attributes.audio_file || '',
|
||
description: item.description,
|
||
rarity: item.rarity_display,
|
||
cardType: item.card_type_display,
|
||
genre: attributes.genre,
|
||
lyrics: attributes.lyrics,
|
||
rawData: item // 存储完整的原始数据
|
||
};
|
||
});
|
||
|
||
return {
|
||
items: songs,
|
||
total: data.data.count,
|
||
page,
|
||
pageSize,
|
||
totalPages: Math.ceil(data.data.count / pageSize),
|
||
};
|
||
} catch (error) {
|
||
console.error('获取歌曲列表失败:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// 获取单个歌曲
|
||
export const getSong = async (id: string): Promise<Song> => {
|
||
try {
|
||
const response = await apiClient.get(`/card/templates/${id}/`);
|
||
|
||
const data: CardSongDetailResponse = response.data;
|
||
|
||
if (!data.success) {
|
||
throw new Error(data.message || '获取歌曲详情失败');
|
||
}
|
||
|
||
return transformCardSongDetailToSong(data.data);
|
||
} catch (error) {
|
||
console.error('获取歌曲详情失败:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// 将API返回的歌曲详情数据转换为应用内使用的格式
|
||
export const transformCardSongDetailToSong = (cardSong: CardSongDetail): Song => {
|
||
return {
|
||
id: String(cardSong.id),
|
||
name: cardSong.name,
|
||
composer: cardSong.attributes.composer || "",
|
||
lyricist: cardSong.attributes.lyricist || "",
|
||
duration: cardSong.attributes.duration || "",
|
||
releaseDate: cardSong.published_at ? new Date(cardSong.published_at).toISOString().split('T')[0] : "",
|
||
status: cardSong.status_display,
|
||
image: cardSong.image_url || "/placeholder.svg?height=300&width=300",
|
||
audioUrl: cardSong.attributes.audio_file,
|
||
description: cardSong.description,
|
||
rarity: cardSong.rarity_display,
|
||
cardType: cardSong.card_type_display,
|
||
genre: cardSong.attributes.genre,
|
||
lyrics: cardSong.attributes.lyrics,
|
||
price: cardSong.price,
|
||
batchesCount: cardSong.batches_count,
|
||
cardsCount: cardSong.active_cards_count,
|
||
createdAt: cardSong.created_at,
|
||
updatedAt: cardSong.updated_at,
|
||
rawData: cardSong // 存储完整的原始数据
|
||
}
|
||
}
|
||
|
||
// 创建歌曲
|
||
export const createSong = async (data: CreateSongRequest): Promise<any> => {
|
||
try {
|
||
const response = await apiClient.post('/card/templates/', data);
|
||
|
||
if (!response.data.success) {
|
||
throw new Error(response.data.message || '创建歌曲卡片模板失败');
|
||
}
|
||
|
||
return response.data.data;
|
||
} catch (error) {
|
||
console.error('创建歌曲失败:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// 更新歌曲
|
||
export const updateSong = async (id: string, data: UpdateSongRequest): Promise<any> => {
|
||
// 过滤空值
|
||
const filteredData = Object.entries(data).reduce((acc: any, [key, value]) => {
|
||
if (value !== null && value !== undefined && value !== '') {
|
||
if (key === 'song_attributes' && typeof value === 'object') {
|
||
// 过滤song_attributes中的空值
|
||
const filteredAttrs = Object.entries(value).reduce((attrAcc: any, [attrKey, attrValue]) => {
|
||
if (attrValue !== null && attrValue !== undefined && attrValue !== '') {
|
||
attrAcc[attrKey] = attrValue;
|
||
}
|
||
return attrAcc;
|
||
}, {});
|
||
|
||
// 只有当属性对象有内容时才添加
|
||
if (Object.keys(filteredAttrs).length > 0) {
|
||
acc[key] = filteredAttrs;
|
||
}
|
||
} else {
|
||
acc[key] = value;
|
||
}
|
||
}
|
||
return acc;
|
||
}, {});
|
||
|
||
try {
|
||
const response = await apiClient.put(`/card/templates/${id}/`, filteredData);
|
||
|
||
if (!response.data.success) {
|
||
throw new Error(response.data.message || '更新歌曲卡片模板失败');
|
||
}
|
||
|
||
return response.data.data;
|
||
} catch (error) {
|
||
console.error('更新歌曲失败:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// 删除歌曲
|
||
export const deleteSong = async (id: string): Promise<boolean> => {
|
||
try {
|
||
const response = await apiClient.delete(`/card/templates/${id}/`);
|
||
|
||
const data = response.data;
|
||
|
||
if (!data.success) {
|
||
throw new Error(data.message || '删除歌曲失败');
|
||
}
|
||
|
||
return true;
|
||
} catch (error) {
|
||
console.error('删除歌曲失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 发布歌曲
|
||
export const publishSong = async (id: string): Promise<Song> => {
|
||
try {
|
||
const response = await apiClient.post(`/card/templates/${id}/publish/`);
|
||
|
||
const data: PublishSongResponse = response.data;
|
||
|
||
if (!data.success) {
|
||
throw new Error(data.message || '发布歌曲失败');
|
||
}
|
||
|
||
// 将返回的模板数据转换为Song类型
|
||
return transformCardSongDetailToSong(data.data.template);
|
||
} catch (error) {
|
||
console.error('发布歌曲失败:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// 归档歌曲
|
||
export const archiveSong = async (id: string): Promise<Song> => {
|
||
try {
|
||
const response = await apiClient.post(`/card/templates/${id}/archive/`);
|
||
|
||
const data = response.data;
|
||
|
||
if (!data.success) {
|
||
throw new Error(data.message || '归档歌曲失败');
|
||
}
|
||
|
||
return transformCardSongDetailToSong(data.data.template);
|
||
} catch (error) {
|
||
console.error('归档歌曲失败:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// 获取歌曲批次数据
|
||
export const getSongBatches = async (songId: string): Promise<SongBatch[]> => {
|
||
try {
|
||
const response = await apiClient.get(`/card/batches/?template=${songId}`);
|
||
|
||
const data: SongBatchesResponse = response.data;
|
||
|
||
if (!data.success) {
|
||
throw new Error(data.message || '获取歌曲批次数据失败');
|
||
}
|
||
|
||
return data.data.results;
|
||
} catch (error) {
|
||
console.error('获取歌曲批次数据失败:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// 创建歌曲批次
|
||
export const createSongBatch = async (data: CreateBatchRequest): Promise<SongBatch> => {
|
||
try {
|
||
const response = await apiClient.post('/card/batches/generate/', data);
|
||
|
||
const responseData: CreateBatchResponse = response.data;
|
||
|
||
if (!responseData.success) {
|
||
throw new Error(responseData.message || '创建批次失败');
|
||
}
|
||
|
||
return responseData.data.batch;
|
||
} catch (error) {
|
||
console.error('创建批次失败:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// 将批次标记为已生产
|
||
export const markBatchAsProduced = async (batchId: number): Promise<SongBatch> => {
|
||
try {
|
||
const response = await apiClient.post(`/card/batches/${batchId}/mark_produced/`);
|
||
|
||
const responseData: MarkBatchAsProducedResponse = response.data;
|
||
|
||
if (!responseData.success) {
|
||
throw new Error(responseData.message || '标记批次为已生产失败');
|
||
}
|
||
|
||
return responseData.data.batch;
|
||
} catch (error) {
|
||
console.error('标记批次为已生产失败:', error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// 上传歌曲文件
|
||
export async function uploadSongFile(
|
||
params: UploadSongFileRequest
|
||
): Promise<UploadSongFileResponse> {
|
||
try {
|
||
const { file, isPermanent = true, filename } = params;
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
formData.append('is_permanent', isPermanent ? 'true' : 'false');
|
||
if (filename) formData.append('filename', filename);
|
||
|
||
const response = await apiClient.post('/common/upload/', formData, {
|
||
headers: {
|
||
// 使用multipart/form-data格式,让axios自动设置正确的Content-Type和边界
|
||
'Content-Type': 'multipart/form-data'
|
||
}
|
||
});
|
||
|
||
const data = response.data;
|
||
|
||
if (!data.success) {
|
||
throw new Error(data.message || '上传文件失败');
|
||
}
|
||
|
||
return data.data;
|
||
} catch (error) {
|
||
console.error('上传文件失败:', error);
|
||
throw error;
|
||
}
|
||
}
|