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

593 lines
13 KiB
Vue
Raw Normal View History

<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>