Files
dreamweaver/frontend/src/views/ChildProfiles.vue
torin b8d3cb4644
Some checks are pending
Build and Push Docker Images / changes (push) Waiting to run
Build and Push Docker Images / build-backend (push) Blocked by required conditions
Build and Push Docker Images / build-frontend (push) Blocked by required conditions
Build and Push Docker Images / build-admin-frontend (push) Blocked by required conditions
wip: snapshot full local workspace state
2026-04-17 18:58:11 +08:00

175 lines
5.2 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.
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { api } from '../api/client'
import BaseButton from '../components/ui/BaseButton.vue'
import BaseCard from '../components/ui/BaseCard.vue'
import BaseInput from '../components/ui/BaseInput.vue'
import BaseSelect from '../components/ui/BaseSelect.vue'
import EmptyState from '../components/ui/EmptyState.vue'
import LoadingSpinner from '../components/ui/LoadingSpinner.vue'
import { ExclamationCircleIcon, UserGroupIcon } from '@heroicons/vue/24/outline'
interface ChildProfile {
id: string
name: string
avatar_url: string | null
birth_date: string | null
gender: string | null
age: number | null
interests: string[]
growth_themes: string[]
stories_count: number
total_reading_time: number
}
interface ProfileListResponse {
profiles: ChildProfile[]
total: number
}
const profiles = ref<ChildProfile[]>([])
const total = ref(0)
const loading = ref(true)
const error = ref('')
const form = ref({
name: '',
birth_date: '',
gender: '',
interests: '',
growth_themes: '',
})
function parseTags(input: string) {
return input
.split(/[,]/)
.map(tag => tag.trim())
.filter(Boolean)
}
async function fetchProfiles() {
loading.value = true
error.value = ''
try {
const data = await api.get<ProfileListResponse>('/api/profiles')
profiles.value = data.profiles
total.value = data.total
} catch (e) {
error.value = e instanceof Error ? e.message : '加载失败'
} finally {
loading.value = false
}
}
async function createProfile() {
if (!form.value.name.trim()) {
error.value = '请输入孩子姓名'
return
}
error.value = ''
try {
await api.post<ChildProfile>('/api/profiles', {
name: form.value.name.trim(),
birth_date: form.value.birth_date || undefined,
gender: form.value.gender || undefined,
interests: parseTags(form.value.interests),
growth_themes: parseTags(form.value.growth_themes),
})
form.value = {
name: '',
birth_date: '',
gender: '',
interests: '',
growth_themes: '',
}
await fetchProfiles()
} catch (e) {
error.value = e instanceof Error ? e.message : '创建失败'
}
}
onMounted(fetchProfiles)
</script>
<template>
<div class="max-w-5xl mx-auto px-4">
<div class="flex items-center justify-between mb-8">
<div>
<h1 class="text-3xl font-bold gradient-text mb-2">孩子档案</h1>
<p class="text-gray-500">为每个孩子建立专属档案</p>
</div>
<div class="text-sm text-gray-500"> {{ total }} 个档案</div>
</div>
<BaseCard class="mb-8" padding="lg">
<h2 class="text-lg font-semibold text-gray-700 mb-4">创建新档案</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<BaseInput v-model="form.name" placeholder="孩子姓名" />
<BaseInput v-model="form.birth_date" type="date" />
<BaseSelect
v-model="form.gender"
:options="[
{ value: '', label: '性别(可选)' },
{ value: 'male', label: '男' },
{ value: 'female', label: '女' },
{ value: 'other', label: '其他' },
]"
/>
<BaseInput v-model="form.interests" placeholder="兴趣标签(逗号分隔)" />
<BaseInput v-model="form.growth_themes" placeholder="成长主题(逗号分隔)" class="md:col-span-2" />
</div>
<div class="mt-4 flex items-center justify-between">
<span v-if="error" class="text-sm text-red-500">{{ error }}</span>
<BaseButton @click="createProfile">创建档案</BaseButton>
</div>
</BaseCard>
<div v-if="loading" class="py-10">
<LoadingSpinner text="加载中..." />
</div>
<div v-else-if="error" class="py-10">
<EmptyState
:icon="ExclamationCircleIcon"
title="加载失败"
:description="error"
/>
</div>
<div v-else-if="profiles.length === 0" class="py-10">
<EmptyState
:icon="UserGroupIcon"
title="暂无档案"
description="创建你的第一个孩子档案"
/>
</div>
<div v-else class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<router-link
v-for="profile in profiles"
:key="profile.id"
:to="`/profiles/${profile.id}`"
class="block"
>
<BaseCard hover>
<div class="flex items-center space-x-3">
<div class="w-12 h-12 rounded-full bg-gradient-to-br from-purple-400 to-pink-400 text-white font-bold flex items-center justify-center">
{{ profile.name.charAt(0) }}
</div>
<div>
<div class="font-semibold text-gray-800">{{ profile.name }}</div>
<div class="text-sm text-gray-500">{{ profile.age ?? '未知' }} </div>
</div>
</div>
<div class="mt-4 text-sm text-gray-500">
兴趣{{ profile.interests.length ? profile.interests.join('、') : '未设置' }}
</div>
<div class="mt-1 text-sm text-gray-500">
成长主题{{ profile.growth_themes.length ? profile.growth_themes.join('、') : '未设置' }}
</div>
</BaseCard>
</router-link>
</div>
</div>
</template>