🐛 修复角色卡导出失败的bug
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 || '请求失败')
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user