🐛 修复角色卡导出失败的bug

This commit is contained in:
2026-02-11 14:34:34 +08:00
parent cf3197929e
commit 1757b92b5f
8 changed files with 198 additions and 37 deletions

View File

@@ -84,32 +84,45 @@ export function exportCharacterAsPNG(id: number) {
* 下载角色卡为 JSON 文件
*/
export async function downloadCharacterJSON(id: number, filename?: string) {
const data = await exportCharacter(id)
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = filename || `character_${id}.json`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
try {
const response = await exportCharacter(id)
// 导出接口返回的是原始响应,需要访问 response.data
const cardData = response.data
const blob = new Blob([JSON.stringify(cardData, null, 2)], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = filename || `character_${id}.json`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
} catch (error) {
console.error('下载 JSON 失败:', error)
throw error
}
}
/**
* 下载角色卡为 PNG 文件
*/
export async function downloadCharacterPNG(id: number, filename?: string) {
const { data } = await exportCharacterAsPNG(id)
const blob = new Blob([data], { type: 'image/png' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = filename || `character_${id}.png`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
try {
const response = await exportCharacterAsPNG(id)
// blob 响应直接返回 response不需要 .data
const blob = new Blob([response.data], { type: 'image/png' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = filename || `character_${id}.png`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
} catch (error) {
console.error('下载 PNG 失败:', error)
throw error
}
}
/**

View File

@@ -30,8 +30,18 @@ service.interceptors.request.use(
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse<ApiResponse>) => {
// 对于 blob 类型的响应(如文件下载),直接返回
if (response.config.responseType === 'blob') {
return response
}
const res = response.data
// 对于导出接口(非标准 ApiResponse 格式),直接返回
if (response.config.url?.includes('/export')) {
return response
}
// code 不为 0 表示业务错误
if (res.code !== 0) {
ElMessage.error(res.msg || '请求失败')

View File

@@ -4,7 +4,15 @@
<!-- 角色头部 -->
<div class="character-header">
<div class="character-avatar-large">
<img :src="character.avatar || '/default-avatar.png'" :alt="character.name" />
<img
v-if="character.avatar"
:src="character.avatar"
:alt="character.name"
@error="handleImageError"
/>
<div v-else class="avatar-placeholder">
<span>{{ character.name.charAt(0) }}</span>
</div>
</div>
<div class="character-header-info">
@@ -306,6 +314,16 @@ async function handleDelete() {
}
}
// 图片加载失败处理
function handleImageError(e: Event) {
const target = e.target as HTMLImageElement
target.style.display = 'none'
const placeholder = target.parentElement?.querySelector('.avatar-placeholder') as HTMLElement
if (placeholder) {
placeholder.style.display = 'flex'
}
}
// 初始化
onMounted(async () => {
const characterId = Number(route.params.id)
@@ -349,6 +367,7 @@ onMounted(async () => {
border-radius: 12px;
overflow: hidden;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
position: relative;
img {
width: 100%;
@@ -356,6 +375,18 @@ onMounted(async () => {
object-fit: cover;
}
.avatar-placeholder {
display: none;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
font-size: 120px;
font-weight: bold;
color: white;
text-transform: uppercase;
}
@media (max-width: 768px) {
width: 100%;
height: 400px;

View File

@@ -40,9 +40,14 @@
<!-- 头像 -->
<div class="character-avatar" @click="goToDetail(character.id)">
<img
:src="character.avatar || '/default-avatar.png'"
v-if="character.avatar"
:src="character.avatar"
:alt="character.name"
@error="(e) => (e.target as HTMLImageElement).style.display = 'none'"
/>
<div v-else class="avatar-placeholder">
{{ character.name.charAt(0) }}
</div>
<!-- 公开状态标识 -->
<el-tag
@@ -263,10 +268,26 @@ onMounted(() => {
object-fit: cover;
}
.avatar-placeholder {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 80px;
font-weight: bold;
color: white;
text-transform: uppercase;
}
.public-tag {
position: absolute;
top: 8px;
right: 8px;
z-index: 1;
}
}

View File

@@ -38,10 +38,14 @@
<!-- 角色头像 -->
<div class="character-avatar">
<img
:src="character.avatar || '/default-avatar.png'"
v-if="character.avatar"
:src="character.avatar"
:alt="character.name"
@error="handleImageError"
/>
<div v-else class="avatar-placeholder">
{{ character.name.charAt(0) }}
</div>
<!-- 悬浮操作按钮 -->
<div class="card-actions">
@@ -176,7 +180,11 @@ function startChat(id: number) {
// 图片加载失败处理
function handleImageError(e: Event) {
const target = e.target as HTMLImageElement
target.src = '/default-avatar.png'
target.style.display = 'none'
const placeholder = target.parentElement?.querySelector('.avatar-placeholder') as HTMLElement
if (placeholder) {
placeholder.style.display = 'flex'
}
}
// 初始化
@@ -249,6 +257,21 @@ onMounted(() => {
object-fit: cover;
}
.avatar-placeholder {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 80px;
font-weight: bold;
color: white;
text-transform: uppercase;
}
.card-actions {
position: absolute;
top: 8px;
@@ -257,6 +280,7 @@ onMounted(() => {
gap: 8px;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 2;
.el-button {
background: rgba(255, 255, 255, 0.9);