kindergarten_java/reading-platform-frontend/src/views/parent/DashboardView.vue

593 lines
13 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="parent-dashboard">
<!-- 欢迎横幅 -->
<div class="welcome-banner">
<div class="banner-content">
<div class="banner-text">
<h1>
<HomeOutlined class="banner-icon" />
<span class="banner-title-text">家长中心</span>
</h1>
<p>关注孩子的阅读成长陪伴每一步进步</p>
</div>
<div class="banner-decorations">
<span class="decoration"><BookOutlined /></span>
<span class="decoration"><StarOutlined /></span>
<span class="decoration"><HeartOutlined /></span>
</div>
</div>
</div>
<a-spin :spinning="loading">
<!-- 我的孩子 -->
<div class="section-card">
<div class="section-header">
<h3><TeamOutlined /> 我的孩子</h3>
</div>
<div class="children-grid" v-if="children.length > 0">
<div
v-for="child in children"
:key="child.id"
class="child-card"
@click="goToChildDetail(child.id)"
>
<div class="child-avatar">
<a-avatar :size="isMobile ? 56 : 64" :style="{ backgroundColor: getAvatarColor(child.id) }">
{{ child.name.charAt(0) }}
</a-avatar>
</div>
<div class="child-info">
<div class="child-name">
{{ child.name }}
<span class="relationship">{{ getRelationshipText(child.relationship) }}</span>
</div>
<div class="child-class">{{ child.class?.name || '未分班' }}</div>
<div class="child-stats">
<span><BookOutlined /> {{ child.readingCount }} 次阅读</span>
<span><ReadOutlined /> {{ child.lessonCount }} 节课</span>
</div>
</div>
<div class="card-arrow">
<RightOutlined />
</div>
</div>
</div>
<div class="empty-state" v-else>
<InboxOutlined class="empty-icon" />
<p>暂无孩子信息</p>
<p class="empty-hint">请联系学校添加孩子信息</p>
</div>
</div>
<!-- 最近任务 -->
<div class="section-card">
<div class="section-header">
<h3><CheckSquareOutlined /> 最近任务</h3>
<a-button type="link" @click="goToTasks" class="view-all-btn">查看全部</a-button>
</div>
<div class="task-list" v-if="recentTasks.length > 0">
<div v-for="task in recentTasks" :key="task.id" class="task-item">
<div class="task-status" :class="getStatusClass(task.status)">
{{ getStatusText(task.status) }}
</div>
<div class="task-content">
<div class="task-title">{{ task.task.title }}</div>
<div class="task-deadline">
<ClockCircleOutlined />
<span>截止:{{ formatDate(task.task.endDate) }}</span>
</div>
</div>
</div>
</div>
<div class="empty-state compact" v-else>
<p>暂无任务</p>
</div>
</div>
<!-- 成长档案 -->
<div class="section-card">
<div class="section-header">
<h3><FileImageOutlined /> 成长档案</h3>
<a-button type="link" @click="goToGrowth" class="view-all-btn">查看全部</a-button>
</div>
<div class="growth-list" v-if="recentGrowth.length > 0">
<div v-for="record in recentGrowth" :key="record.id" class="growth-item">
<div class="growth-images" v-if="record.images && record.images.length > 0">
<img :src="record.images[0]" alt="成长照片" />
</div>
<div class="growth-content">
<div class="growth-title">{{ record.title }}</div>
<div class="growth-date">{{ formatDate(record.recordDate) }}</div>
</div>
</div>
</div>
<div class="empty-state compact" v-else>
<p>暂无成长记录</p>
</div>
</div>
</a-spin>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import { message } from 'ant-design-vue';
import {
HomeOutlined,
BookOutlined,
StarOutlined,
HeartOutlined,
TeamOutlined,
ReadOutlined,
CheckSquareOutlined,
FileImageOutlined,
ClockCircleOutlined,
RightOutlined,
InboxOutlined,
} from '@ant-design/icons-vue';
import { getChildren, type ChildInfo } from '@/api/parent';
import dayjs from 'dayjs';
const router = useRouter();
const loading = ref(false);
const children = ref<ChildInfo[]>([]);
const recentTasks = ref<any[]>([]);
const recentGrowth = ref<any[]>([]);
const isMobile = ref(false);
const avatarColors = ['#52c41a', '#1890ff', '#fa8c16', '#eb2f96', '#722ed1'];
const getAvatarColor = (id: number) => avatarColors[id % avatarColors.length];
const getRelationshipText = (relationship: string) => {
const map: Record<string, string> = {
FATHER: '爸爸',
MOTHER: '妈妈',
GRANDFATHER: '爷爷',
GRANDMOTHER: '奶奶',
OTHER: '监护人',
};
return map[relationship] || relationship;
};
const statusMap: Record<string, { text: string; class: string }> = {
PENDING: { text: '待完成', class: 'status-pending' },
IN_PROGRESS: { text: '进行中', class: 'status-progress' },
COMPLETED: { text: '已完成', class: 'status-completed' },
};
const getStatusText = (status: string) => statusMap[status]?.text || status;
const getStatusClass = (status: string) => statusMap[status]?.class || '';
const formatDate = (date: string) => dayjs(date).format('YYYY-MM-DD');
const checkMobile = () => {
isMobile.value = window.innerWidth < 768;
};
const goToChildDetail = (childId: number) => {
router.push(`/parent/children/${childId}`);
};
const goToTasks = () => {
router.push('/parent/tasks');
};
const goToGrowth = () => {
router.push('/parent/growth');
};
const loadData = async () => {
loading.value = true;
try {
const data = await getChildren();
children.value = data;
// 如果有孩子,加载第一个孩子的任务和成长记录
if (data.length > 0) {
// 这里可以加载任务和成长记录
}
} catch (error: any) {
message.error(error.response?.data?.message || '加载数据失败');
} finally {
loading.value = false;
}
};
onMounted(() => {
checkMobile();
window.addEventListener('resize', checkMobile);
loadData();
});
onUnmounted(() => {
window.removeEventListener('resize', checkMobile);
});
</script>
<style scoped lang="scss">
$primary-color: #52c41a;
$primary-light: #f6ffed;
$primary-dark: #389e0d;
.parent-dashboard {
// 欢迎横幅
.welcome-banner {
background: linear-gradient(135deg, $primary-color 0%, #73d13d 100%);
border-radius: 16px;
padding: 24px 32px;
margin-bottom: 24px;
color: white;
.banner-content {
display: flex;
justify-content: space-between;
align-items: center;
}
h1 {
font-size: 24px;
margin: 0 0 8px;
display: flex;
align-items: center;
gap: 8px;
.banner-icon {
font-size: 28px;
}
}
p {
margin: 0;
opacity: 0.9;
font-size: 14px;
}
.banner-decorations {
display: flex;
gap: 16px;
font-size: 32px;
opacity: 0.8;
}
}
// 区块卡片
.section-card {
background: white;
border-radius: 12px;
padding: 20px;
margin-bottom: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
h3 {
margin: 0;
font-size: 18px;
display: flex;
align-items: center;
gap: 8px;
color: #333;
}
.view-all-btn {
padding: 0;
font-size: 14px;
}
}
}
// 孩子卡片
.children-grid {
display: flex;
flex-direction: column;
gap: 12px;
}
.child-card {
display: flex;
align-items: center;
padding: 16px;
background: #fafafa;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s;
&:hover {
background: $primary-light;
.card-arrow {
color: $primary-color;
}
}
&:active {
transform: scale(0.98);
}
.child-info {
flex: 1;
margin-left: 16px;
min-width: 0;
.child-name {
font-size: 16px;
font-weight: 600;
color: #333;
.relationship {
margin-left: 8px;
font-size: 12px;
color: #999;
font-weight: normal;
}
}
.child-class {
font-size: 13px;
color: #666;
margin: 4px 0;
}
.child-stats {
font-size: 12px;
color: #999;
display: flex;
gap: 16px;
flex-wrap: wrap;
span {
display: flex;
align-items: center;
gap: 4px;
}
}
}
.card-arrow {
color: #d9d9d9;
font-size: 16px;
transition: color 0.3s;
}
}
// 任务和成长列表
.task-list,
.growth-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.task-item,
.growth-item {
display: flex;
align-items: center;
padding: 12px;
background: #fafafa;
border-radius: 8px;
gap: 12px;
cursor: pointer;
transition: all 0.3s;
&:hover {
background: $primary-light;
}
&:active {
transform: scale(0.98);
}
.task-status {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
&.status-pending {
background: #fff7e6;
color: #fa8c16;
}
&.status-progress {
background: #e6f7ff;
color: #1890ff;
}
&.status-completed {
background: $primary-light;
color: $primary-color;
}
}
.task-content,
.growth-content {
flex: 1;
min-width: 0;
.task-title,
.growth-title {
font-size: 14px;
font-weight: 500;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.task-deadline,
.growth-date {
font-size: 12px;
color: #999;
margin-top: 4px;
display: flex;
align-items: center;
gap: 4px;
}
}
.growth-images {
width: 48px;
height: 48px;
border-radius: 8px;
overflow: hidden;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
// 空状态
.empty-state {
text-align: center;
padding: 32px;
color: #999;
&.compact {
padding: 24px;
}
.empty-icon {
font-size: 48px;
color: #d9d9d9;
margin-bottom: 12px;
}
p {
margin: 4px 0;
}
.empty-hint {
font-size: 13px;
color: #bfbfbf;
}
}
}
// =============== 移动端响应式 ===============
@media screen and (max-width: 768px) {
.parent-dashboard {
.welcome-banner {
padding: 20px 16px;
margin-bottom: 16px;
border-radius: 12px;
h1 {
font-size: 20px;
.banner-icon {
font-size: 24px;
}
}
p {
font-size: 13px;
}
.banner-decorations {
gap: 12px;
font-size: 24px;
}
}
.section-card {
padding: 16px;
margin-bottom: 16px;
border-radius: 12px;
.section-header {
margin-bottom: 12px;
h3 {
font-size: 16px;
}
.view-all-btn {
font-size: 13px;
}
}
}
.child-card {
padding: 14px;
.child-info {
margin-left: 14px;
.child-name {
font-size: 15px;
}
.child-class {
font-size: 12px;
}
.child-stats {
font-size: 11px;
gap: 12px;
}
}
}
.task-item,
.growth-item {
padding: 12px;
.task-status {
padding: 3px 6px;
font-size: 11px;
}
.task-content,
.growth-content {
.task-title,
.growth-title {
font-size: 13px;
}
.task-deadline,
.growth-date {
font-size: 11px;
}
}
.growth-images {
width: 40px;
height: 40px;
}
}
.empty-state {
padding: 24px;
&.compact {
padding: 20px;
}
.empty-icon {
font-size: 40px;
}
}
}
}
// 平板适配
@media screen and (min-width: 769px) and (max-width: 1024px) {
.parent-dashboard {
.welcome-banner {
padding: 22px 28px;
}
}
}
</style>