186 lines
7.7 KiB
Vue
186 lines
7.7 KiB
Vue
<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>
|