117 lines
4.3 KiB
Vue
117 lines
4.3 KiB
Vue
<template>
|
|
<div class="space-y-6">
|
|
<!-- Header -->
|
|
<div class="flex items-center space-x-4">
|
|
<button @click="$router.back()" class="text-gray-400 hover:text-white">
|
|
← 返回
|
|
</button>
|
|
<div>
|
|
<h2 class="text-2xl font-bold">{{ profile.name || '未命名身份' }}</h2>
|
|
<p class="text-sm text-gray-400">全域身份 ID: {{ identityId }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading -->
|
|
<div v-if="loading" class="text-center py-12 text-gray-500">載入中...</div>
|
|
|
|
<!-- Content -->
|
|
<div v-else-if="detail" class="grid gap-6">
|
|
<!-- Profile Card -->
|
|
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
|
|
<h3 class="text-lg font-semibold text-blue-400 mb-4">人物檔案</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<span class="text-gray-500 text-sm">本名</span>
|
|
<p class="text-white text-lg">{{ profile.name || '-' }}</p>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-500 text-sm">角色名</span>
|
|
<p class="text-white text-lg">{{ profile.character_name || '-' }}</p>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-500 text-sm">別名 (Aliases)</span>
|
|
<div class="flex flex-wrap gap-2 mt-1">
|
|
<span v-for="(alias, idx) in profile.aliases" :key="idx" class="bg-gray-700 text-gray-300 px-2 py-1 rounded text-sm">
|
|
{{ alias }}
|
|
</span>
|
|
<span v-if="!profile.aliases || profile.aliases.length === 0" class="text-gray-600 text-sm">無</span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-500 text-sm">Speaker ID</span>
|
|
<p class="text-white text-lg font-mono">{{ profile.speaker_id || '-' }}</p>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-500 text-sm">性別</span>
|
|
<p class="text-white">{{ profile.gender || '-' }}</p>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-500 text-sm">年齡</span>
|
|
<p class="text-white">{{ profile.age || '-' }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Videos List -->
|
|
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
|
|
<h3 class="text-lg font-semibold text-green-400 mb-4">出現影片 ({{ videos.length }})</h3>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-sm text-left text-gray-400">
|
|
<thead class="text-xs text-gray-500 uppercase bg-gray-700">
|
|
<tr>
|
|
<th scope="col" class="px-6 py-3 rounded-l-lg">影片名稱</th>
|
|
<th scope="col" class="px-6 py-3">區域 ID</th>
|
|
<th scope="col" class="px-6 py-3">出現次數</th>
|
|
<th scope="col" class="px-6 py-3 rounded-r-lg">首次出現</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="video in videos" :key="video.file_uuid" class="bg-gray-800 border-b border-gray-700 hover:bg-gray-750">
|
|
<td class="px-6 py-4 font-medium text-white">{{ video.file_name }}</td>
|
|
<td class="px-6 py-4 font-mono text-blue-300">{{ video.person_id }}</td>
|
|
<td class="px-6 py-4">{{ video.appearance_count }}</td>
|
|
<td class="px-6 py-4">{{ video.first_appearance?.toFixed(2) || '-' }}s</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import { httpFetch, getCurrentConfig } from '@/api/client'
|
|
|
|
const route = useRoute()
|
|
const identityId = ref('')
|
|
const loading = ref(false)
|
|
const detail = ref<any>(null)
|
|
const profile = ref<any>({})
|
|
const videos = ref<any[]>([])
|
|
|
|
const loadDetail = async () => {
|
|
identityId.value = route.params.id as string
|
|
loading.value = true
|
|
|
|
try {
|
|
const config = getCurrentConfig()
|
|
const result = await httpFetch<any>(`${config.api_base_url}/api/v1/identity/${identityId.value}`)
|
|
detail.value = result
|
|
profile.value = result.profile || {}
|
|
videos.value = result.videos || []
|
|
} catch (error) {
|
|
console.error('Failed to load identity detail:', error)
|
|
alert('載入失敗: ' + error)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadDetail()
|
|
})
|
|
</script>
|