|
|
@@ -14,78 +14,75 @@
|
|
|
<el-col :span="6">
|
|
|
<el-card class="stat-card" shadow="hover">
|
|
|
<div class="stat-icon blue">
|
|
|
- <i class="el-icon-user"></i>
|
|
|
+ <i class="el-icon-s-home"></i>
|
|
|
</div>
|
|
|
<div class="stat-info">
|
|
|
- <div class="stat-value">{{ stats.totalUsers || 0 }}</div>
|
|
|
- <div class="stat-label">用户总数</div>
|
|
|
- <div class="stat-trend up">
|
|
|
- <i class="el-icon-top"></i>
|
|
|
- 较昨日 +12%
|
|
|
- </div>
|
|
|
+ <div class="stat-value">{{ stats.totalFamilies || 0 }}</div>
|
|
|
+ <div class="stat-label">家庭总数</div>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
<el-col :span="6">
|
|
|
<el-card class="stat-card" shadow="hover">
|
|
|
<div class="stat-icon green">
|
|
|
- <i class="el-icon-s-custom"></i>
|
|
|
+ <i class="el-icon-user"></i>
|
|
|
</div>
|
|
|
<div class="stat-info">
|
|
|
- <div class="stat-value">{{ stats.totalChildren || 0 }}</div>
|
|
|
- <div class="stat-label">孩子数量</div>
|
|
|
- <div class="stat-trend up">
|
|
|
- <i class="el-icon-top"></i>
|
|
|
- 较昨日 +5%
|
|
|
- </div>
|
|
|
+ <div class="stat-value">{{ stats.totalParents || 0 }}</div>
|
|
|
+ <div class="stat-label">家长总数</div>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
<el-col :span="6">
|
|
|
<el-card class="stat-card" shadow="hover">
|
|
|
<div class="stat-icon orange">
|
|
|
- <i class="el-icon-s-order"></i>
|
|
|
+ <i class="el-icon-s-custom"></i>
|
|
|
</div>
|
|
|
<div class="stat-info">
|
|
|
- <div class="stat-value">{{ taskStats.total || 0 }}</div>
|
|
|
- <div class="stat-label">总任务数</div>
|
|
|
- <div class="stat-trend">
|
|
|
- 今日新增 {{ taskStats.today || 0 }}
|
|
|
- </div>
|
|
|
+ <div class="stat-value">{{ stats.totalChildren || 0 }}</div>
|
|
|
+ <div class="stat-label">孩子总数</div>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
<el-col :span="6">
|
|
|
<el-card class="stat-card" shadow="hover">
|
|
|
<div class="stat-icon purple">
|
|
|
- <i class="el-icon-s-goods"></i>
|
|
|
+ <i class="el-icon-s-cooperation"></i>
|
|
|
</div>
|
|
|
<div class="stat-info">
|
|
|
- <div class="stat-value">{{ stats.pendingRewards || 0 }}</div>
|
|
|
- <div class="stat-label">待审批奖励</div>
|
|
|
- <div class="stat-trend warning">
|
|
|
- 需尽快处理
|
|
|
- </div>
|
|
|
+ <div class="stat-value">{{ stats.totalTeachers || 0 }}</div>
|
|
|
+ <div class="stat-label">规划师总数</div>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
- <!-- 待办事项 -->
|
|
|
- <el-row :gutter="20" class="todo-row" v-if="stats.pendingGuideCount > 0">
|
|
|
- <el-col :span="24">
|
|
|
- <el-card class="todo-card" shadow="hover">
|
|
|
- <div slot="header" class="card-header">
|
|
|
- <span class="card-title">🔔 待办事项</span>
|
|
|
+ <!-- 待审核卡片 -->
|
|
|
+ <el-row :gutter="20" class="stats-row">
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card todo-stat-card" shadow="hover" @click.native="stats.pendingGuideCount > 0 && $router.push('/guide-audit')">
|
|
|
+ <div class="stat-icon" :class="stats.pendingGuideCount > 0 ? 'todo-warning' : 'todo-disabled'">
|
|
|
+ <i class="el-icon-s-check"></i>
|
|
|
</div>
|
|
|
- <div class="todo-items">
|
|
|
- <div class="todo-item" @click="$router.push('/guide-audit')">
|
|
|
- <div class="todo-badge warning">{{ stats.pendingGuideCount }}</div>
|
|
|
- <div class="todo-info">
|
|
|
- <div class="todo-text">待审核成长规划师申请</div>
|
|
|
- <div class="todo-hint">点击前往审核</div>
|
|
|
- </div>
|
|
|
- <i class="el-icon-arrow-right todo-arrow"></i>
|
|
|
+ <div class="stat-info">
|
|
|
+ <div class="stat-value">{{ stats.pendingGuideCount }}</div>
|
|
|
+ <div class="stat-label">待审核规划师</div>
|
|
|
+ <div class="stat-trend" :class="stats.pendingGuideCount > 0 ? '' : 'disabled'">
|
|
|
+ {{ stats.pendingGuideCount > 0 ? '点击前往审核' : '暂无待审核' }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card class="stat-card todo-stat-card" shadow="hover" @click.native="stats.pendingPackageCount > 0 && $router.push('/package-audit')">
|
|
|
+ <div class="stat-icon" :class="stats.pendingPackageCount > 0 ? 'todo-danger' : 'todo-disabled'">
|
|
|
+ <i class="el-icon-s-goods"></i>
|
|
|
+ </div>
|
|
|
+ <div class="stat-info">
|
|
|
+ <div class="stat-value">{{ stats.pendingPackageCount }}</div>
|
|
|
+ <div class="stat-label">待审核套餐</div>
|
|
|
+ <div class="stat-trend" :class="stats.pendingPackageCount > 0 ? '' : 'disabled'">
|
|
|
+ {{ stats.pendingPackageCount > 0 ? '点击前往审核' : '暂无待审核' }}
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
@@ -185,7 +182,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import { getUserList, getChildrenList, getRewardsList, getTasksList, getTaskStats, getPendingGuideCount } from '@/api/admin'
|
|
|
+import { getUserList, getFamilyList, getChildrenList, getTasksList, getPendingGuideCount, getPendingPackageCount } from '@/api/admin'
|
|
|
|
|
|
export default {
|
|
|
name: 'Dashboard',
|
|
|
@@ -193,14 +190,12 @@ export default {
|
|
|
return {
|
|
|
loadingTasks: false,
|
|
|
stats: {
|
|
|
- totalUsers: 156,
|
|
|
- totalChildren: 89,
|
|
|
- pendingRewards: 12,
|
|
|
- pendingGuideCount: 0
|
|
|
- },
|
|
|
- taskStats: {
|
|
|
- total: 1245,
|
|
|
- today: 28
|
|
|
+ totalFamilies: 0,
|
|
|
+ totalParents: 0,
|
|
|
+ totalChildren: 0,
|
|
|
+ totalTeachers: 0,
|
|
|
+ pendingGuideCount: 0,
|
|
|
+ pendingPackageCount: 0
|
|
|
},
|
|
|
recentTasks: []
|
|
|
}
|
|
|
@@ -218,28 +213,29 @@ export default {
|
|
|
methods: {
|
|
|
async loadDashboardData() {
|
|
|
await Promise.all([
|
|
|
- this.loadUserStats(),
|
|
|
+ this.loadFamilyStats(),
|
|
|
+ this.loadParentStats(),
|
|
|
this.loadChildrenStats(),
|
|
|
- this.loadRewardsStats(),
|
|
|
- this.loadTaskStats(),
|
|
|
+ this.loadTeacherStats(),
|
|
|
this.loadRecentTasks(),
|
|
|
- this.loadPendingGuideCount()
|
|
|
+ this.loadPendingGuideCount(),
|
|
|
+ this.loadPendingPackageCount()
|
|
|
])
|
|
|
},
|
|
|
- async loadPendingGuideCount() {
|
|
|
+ async loadFamilyStats() {
|
|
|
try {
|
|
|
- const res = await getPendingGuideCount()
|
|
|
- this.stats.pendingGuideCount = res.data.count || 0
|
|
|
+ const res = await getFamilyList({ page: 1, size: 1 })
|
|
|
+ this.stats.totalFamilies = res.data.total || 0
|
|
|
} catch (error) {
|
|
|
- console.error('加载待审核规划师统计失败:', error)
|
|
|
+ console.error('加载家庭统计失败:', error)
|
|
|
}
|
|
|
},
|
|
|
- async loadUserStats() {
|
|
|
+ async loadParentStats() {
|
|
|
try {
|
|
|
- const res = await getUserList({ page: 1, size: 1 })
|
|
|
- this.stats.totalUsers = res.data.total || 0
|
|
|
+ const res = await getUserList({ page: 1, size: 1, role: 'parent' })
|
|
|
+ this.stats.totalParents = res.data.total || 0
|
|
|
} catch (error) {
|
|
|
- console.error('加载用户统计失败:', error)
|
|
|
+ console.error('加载家长统计失败:', error)
|
|
|
}
|
|
|
},
|
|
|
async loadChildrenStats() {
|
|
|
@@ -250,20 +246,12 @@ export default {
|
|
|
console.error('加载孩子统计失败:', error)
|
|
|
}
|
|
|
},
|
|
|
- async loadRewardsStats() {
|
|
|
+ async loadTeacherStats() {
|
|
|
try {
|
|
|
- const res = await getRewardsList({ page: 1, size: 1, status: 'pending' })
|
|
|
- this.stats.pendingRewards = res.data.total || 0
|
|
|
+ const res = await getUserList({ page: 1, size: 1, role: 'teacher' })
|
|
|
+ this.stats.totalTeachers = res.data.total || 0
|
|
|
} catch (error) {
|
|
|
- console.error('加载奖励统计失败:', error)
|
|
|
- }
|
|
|
- },
|
|
|
- async loadTaskStats() {
|
|
|
- try {
|
|
|
- const res = await getTaskStats()
|
|
|
- this.taskStats = res.data || { total: 0, today: 0 }
|
|
|
- } catch (error) {
|
|
|
- console.error('加载任务统计失败:', error)
|
|
|
+ console.error('加载规划师统计失败:', error)
|
|
|
}
|
|
|
},
|
|
|
async loadRecentTasks() {
|
|
|
@@ -276,6 +264,22 @@ export default {
|
|
|
} finally {
|
|
|
this.loadingTasks = false
|
|
|
}
|
|
|
+ },
|
|
|
+ async loadPendingGuideCount() {
|
|
|
+ try {
|
|
|
+ const res = await getPendingGuideCount()
|
|
|
+ this.stats.pendingGuideCount = res.data.count || 0
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载待审核规划师统计失败:', error)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async loadPendingPackageCount() {
|
|
|
+ try {
|
|
|
+ const res = await getPendingPackageCount()
|
|
|
+ this.stats.pendingPackageCount = res.data.count || 0
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载待审核套餐统计失败:', error)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -330,6 +334,16 @@ export default {
|
|
|
padding: 24px;
|
|
|
}
|
|
|
|
|
|
+.todo-stat-card {
|
|
|
+ cursor: pointer;
|
|
|
+ transition: transform 0.2s, box-shadow 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.todo-stat-card:hover {
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
|
+}
|
|
|
+
|
|
|
.stat-icon {
|
|
|
width: 56px;
|
|
|
height: 56px;
|
|
|
@@ -340,12 +354,16 @@ export default {
|
|
|
font-size: 24px;
|
|
|
color: #fff;
|
|
|
margin-right: 16px;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.stat-icon.blue { background: linear-gradient(135deg, #6366F1, #818CF8); }
|
|
|
.stat-icon.green { background: linear-gradient(135deg, #10B981, #34D399); }
|
|
|
.stat-icon.orange { background: linear-gradient(135deg, #F59E0B, #FBBF24); }
|
|
|
.stat-icon.purple { background: linear-gradient(135deg, #8B5CF6, #A78BFA); }
|
|
|
+.stat-icon.todo-warning { background: linear-gradient(135deg, #F59E0B, #FBBF24); }
|
|
|
+.stat-icon.todo-danger { background: linear-gradient(135deg, #EF4444, #F87171); }
|
|
|
+.stat-icon.todo-disabled { background: linear-gradient(135deg, #CBD5E1, #94A3B8); }
|
|
|
|
|
|
.stat-value {
|
|
|
font-size: 28px;
|
|
|
@@ -362,16 +380,12 @@ export default {
|
|
|
|
|
|
.stat-trend {
|
|
|
font-size: 12px;
|
|
|
- color: #10B981;
|
|
|
+ color: #6366F1;
|
|
|
margin-top: 8px;
|
|
|
}
|
|
|
|
|
|
-.stat-trend.up {
|
|
|
- color: #10B981;
|
|
|
-}
|
|
|
-
|
|
|
-.stat-trend.warning {
|
|
|
- color: #F59E0B;
|
|
|
+.stat-trend.disabled {
|
|
|
+ color: #94A3B8;
|
|
|
}
|
|
|
|
|
|
/* 内容卡片 */
|
|
|
@@ -461,73 +475,6 @@ export default {
|
|
|
color: #64748B;
|
|
|
}
|
|
|
|
|
|
-/* 待办事项 */
|
|
|
-.todo-row {
|
|
|
- margin-bottom: 24px;
|
|
|
-}
|
|
|
-
|
|
|
-.todo-card {
|
|
|
- border-radius: 12px;
|
|
|
- border: none;
|
|
|
-}
|
|
|
-
|
|
|
-.todo-items {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
-}
|
|
|
-
|
|
|
-.todo-item {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- padding: 16px;
|
|
|
- background: #FFFBEB;
|
|
|
- border-radius: 8px;
|
|
|
- cursor: pointer;
|
|
|
- transition: background 0.2s;
|
|
|
-}
|
|
|
-
|
|
|
-.todo-item:hover {
|
|
|
- background: #FEF3C7;
|
|
|
-}
|
|
|
-
|
|
|
-.todo-badge {
|
|
|
- width: 36px;
|
|
|
- height: 36px;
|
|
|
- border-radius: 50%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- font-size: 16px;
|
|
|
- font-weight: 700;
|
|
|
- color: #fff;
|
|
|
- margin-right: 16px;
|
|
|
-}
|
|
|
-
|
|
|
-.todo-badge.warning {
|
|
|
- background: linear-gradient(135deg, #F59E0B, #FBBF24);
|
|
|
-}
|
|
|
-
|
|
|
-.todo-info {
|
|
|
- flex: 1;
|
|
|
-}
|
|
|
-
|
|
|
-.todo-text {
|
|
|
- font-size: 14px;
|
|
|
- font-weight: 500;
|
|
|
- color: #1E293B;
|
|
|
-}
|
|
|
-
|
|
|
-.todo-hint {
|
|
|
- font-size: 12px;
|
|
|
- color: #64748B;
|
|
|
- margin-top: 4px;
|
|
|
-}
|
|
|
-
|
|
|
-.todo-arrow {
|
|
|
- font-size: 16px;
|
|
|
- color: #94A3B8;
|
|
|
-}
|
|
|
-
|
|
|
/* 快捷操作 */
|
|
|
.quick-card .el-card__body {
|
|
|
padding: 12px;
|