Files
huihuiSquare/digital-avatar-app/src/views/AvatarCard.vue
2026-07-24 14:04:21 +08:00

186 lines
7.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="avatar-card-page">
<!-- 顶部导航 -->
<header class="page-header">
<div class="header-left">
<button class="back-btn" @click="goBack"></button>
<h1 class="page-title">分身名片</h1>
</div>
<button class="share-btn" @click="shareCard">分享</button>
</header>
<!-- 名片预览 -->
<section class="card-preview">
<div class="digital-card">
<div class="card-top">
<div class="card-avatar">{{ avatar.emoji }}</div>
<span class="card-status" :class="avatar.status"> {{ statusText(avatar.status) }}</span>
</div>
<h2 class="card-name">{{ avatar.displayName }}</h2>
<p class="card-desc">{{ avatar.description }}</p>
<div class="card-tags">
<span class="card-tag" v-for="t in avatar.tags" :key="t">{{ t }}</span>
</div>
<div class="card-qr">
<div class="qr-placeholder" aria-hidden="true"></div>
<span class="qr-tip">扫码添加我的分身</span>
</div>
<div class="card-id">会会号{{ avatar.huihuiId }}</div>
</div>
</section>
<!-- 名片信息 -->
<section class="info-section">
<h3 class="section-title">名片信息</h3>
<div class="info-list">
<div class="info-row"><span class="info-label">分身名称</span><span class="info-value">{{ avatar.displayName }}</span></div>
<div class="info-row"><span class="info-label">会会号</span><span class="info-value">{{ avatar.huihuiId }}</span></div>
<div class="info-row"><span class="info-label">状态</span><span class="info-value">{{ statusText(avatar.status) }}</span></div>
<div class="info-row"><span class="info-label">创建时间</span><span class="info-value">{{ formatDate(avatar.createdAt) }}</span></div>
</div>
</section>
<!-- 操作 -->
<section class="action-section">
<button class="primary-btn" @click="copyLink">复制分享链接</button>
<p class="toast" v-if="toast">{{ toast }}</p>
</section>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAvatarStore } from '@/store/avatar'
import { getAvatarDetail } from '@/api'
import { pickAvatarId } from '@/utils/avatar-page-data'
const router = useRouter()
const route = useRoute()
const avatarStore = useAvatarStore()
const toast = ref('')
const loading = ref(true)
const selectedAvatarId = ref('')
const avatar = ref({
emoji: '🤖',
displayName: '会会助手',
description: '我是您的AI数字分身可以帮您管理日程、回复消息、处理任务。',
huihuiId: 'huihui_8848',
status: 'active',
createdAt: '2026-07-01T10:00:00Z',
tags: ['智能对话', '日程管理', '社交助手']
})
const applyAvatar = (a: any) => {
if (!a) return
avatar.value = {
emoji: a.emoji || '🤖',
displayName: a.displayName || a.name || '我的分身',
description: a.description || '暂无描述',
huihuiId: a.huihuiId || 'huihui_' + (a.id || '0000'),
status: a.status || 'active',
createdAt: a.createdAt || new Date().toISOString(),
tags: a.tags || ['智能对话', '日程管理']
}
}
onMounted(async () => {
try {
if (!avatarStore.avatars.length) await avatarStore.loadAvatars()
const requestedId = String(route.query.id || '')
const id = pickAvatarId(requestedId, avatarStore.avatars)
selectedAvatarId.value = id || ''
const localAvatar = avatarStore.avatars.find((a) => String(a.id) === id)
if (localAvatar) {
applyAvatar(localAvatar)
} else if (requestedId) {
applyAvatar(await getAvatarDetail(requestedId))
selectedAvatarId.value = requestedId
} else {
applyAvatar(avatarStore.avatars[0])
}
} finally {
loading.value = false
}
})
const statusText = (s: string) => ({ active: '活跃中', inactive: '未激活', training: '训练中' }[s] || '活跃中')
const formatDate = (t: string) => new Date(t).toLocaleDateString('zh-CN')
const goBack = () => router.back()
const shareCard = () => {
toast.value = '请复制链接分享给对方'
setTimeout(() => (toast.value = ''), 2000)
}
const copyLink = async () => {
const id = selectedAvatarId.value
const redirect = router.resolve({ path: '/avatar/card', query: id ? { id: String(id) } : undefined }).href
const loginUrl = router.resolve({ path: '/login/sms', query: { redirect } }).href
const hash = loginUrl.includes('#') ? loginUrl.slice(loginUrl.indexOf('#')) : `#${loginUrl}`
const link = `${window.location.origin}${window.location.pathname}${hash}`
try {
await navigator.clipboard.writeText(link)
toast.value = '分享链接已复制'
} catch {
toast.value = link
}
setTimeout(() => (toast.value = ''), 2000)
}
</script>
<style scoped>
.avatar-card-page { min-height: 100vh; background: #F8F9FA; padding-bottom: 40px; }
.page-header {
display: flex; align-items: center; justify-content: space-between;
padding: 16px 20px; background: linear-gradient(135deg, #F97316 0%, #FB923C 100%); color: white;
}
.header-left { display: flex; align-items: center; gap: 12px; }
.back-btn { background: none; border: none; color: white; font-size: 24px; cursor: pointer; padding: 4px; }
.page-title { font-size: 18px; font-weight: 600; margin: 0; }
.share-btn { background: rgba(255,255,255,0.2); border: none; color: white; padding: 6px 16px; border-radius: 20px; font-size: 14px; cursor: pointer; }
.card-preview { padding: 16px 20px; }
.digital-card {
background: linear-gradient(160deg, #FFF7ED 0%, #FFFFFF 60%);
border: 1px solid #FFE4CC; border-radius: 20px; padding: 24px;
box-shadow: 0 8px 24px rgba(249,115,22,0.12);
}
.card-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
.card-avatar {
width: 64px; height: 64px; border-radius: 18px; background: #FFF0E6;
display: flex; align-items: center; justify-content: center; font-size: 34px;
}
.card-status { font-size: 12px; color: #22C55E; background: #ECFDF5; padding: 4px 10px; border-radius: 20px; }
.card-name { font-size: 22px; font-weight: 700; color: #18191C; margin: 0 0 8px; }
.card-desc { font-size: 14px; color: #6B7280; line-height: 1.6; margin: 0 0 16px; }
.card-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; }
.card-tag { padding: 4px 12px; background: #FFF0E6; color: #F97316; border-radius: 20px; font-size: 12px; font-weight: 500; }
.card-qr {
display: flex; flex-direction: column; align-items: center; gap: 6px;
padding: 16px; background: white; border-radius: 14px; border: 1px dashed #FED7AA; margin-bottom: 14px;
}
.qr-placeholder { font-size: 56px; line-height: 1; color: #F97316; letter-spacing: 4px; }
.qr-tip { font-size: 12px; color: #9398AE; }
.card-id { text-align: center; font-size: 13px; color: #9398AE; }
.info-section { padding: 0 20px 16px; }
.section-title { font-size: 16px; font-weight: 600; margin: 0 0 12px; color: #18191C; }
.info-list { background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); overflow: hidden; }
.info-row { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid #F3F4F6; }
.info-row:last-child { border-bottom: none; }
.info-label { font-size: 14px; color: #6B7280; }
.info-value { font-size: 14px; color: #18191C; font-weight: 500; }
.action-section { padding: 0 20px; }
.primary-btn {
width: 100%; padding: 14px; background: #F97316; color: white;
border: none; border-radius: 12px; font-size: 16px; font-weight: 600; cursor: pointer; transition: opacity 0.2s;
}
.primary-btn:hover { opacity: 0.9; }
.toast { text-align: center; font-size: 13px; color: #F97316; margin: 12px 0 0; word-break: break-all; }
</style>