| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- <template>
- <div class="teacher-dashboard-wrapper">
- <!-- 统计卡片 -->
- <el-row :gutter="24" class="td-stat-row">
- <el-col :span="6">
- <div class="td-stat-card">
- <div class="td-stat-icon" style="background: linear-gradient(135deg, #3B82F6 0%, #60A5FA 100%)">
- <i class="el-icon-user-solid"></i>
- </div>
- <div class="td-stat-content">
- <div class="td-stat-value">{{ teacherStats.serviceFamilyCount }}</div>
- <div class="td-stat-label">服务家庭</div>
- </div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="td-stat-card">
- <div class="td-stat-icon" style="background: linear-gradient(135deg, #10B981 0%, #34D399 100%)">
- <i class="el-icon-s-data"></i>
- </div>
- <div class="td-stat-content">
- <div class="td-stat-value">{{ teacherStats.activeFamilyCount }}</div>
- <div class="td-stat-label">活跃家庭</div>
- </div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="td-stat-card">
- <div class="td-stat-icon" style="background: linear-gradient(135deg, #F59E0B 0%, #FBBF24 100%)">
- <i class="el-icon-coin"></i>
- </div>
- <div class="td-stat-content">
- <div class="td-stat-value">{{ formatPriceWithSymbol(teacherStats.monthlySales) }}</div>
- <div class="td-stat-label">本月销售额</div>
- </div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="td-stat-card">
- <div class="td-stat-icon" style="background: linear-gradient(135deg, #8B5CF6 0%, #A78BFA 100%)">
- <i class="el-icon-wallet"></i>
- </div>
- <div class="td-stat-content">
- <div class="td-stat-value">{{ formatPriceWithSymbol(teacherStats.myCommission) }}</div>
- <div class="td-stat-label">我的佣金</div>
- </div>
- </div>
- </el-col>
- </el-row>
- <!-- 快捷操作 + 今日概览 -->
- <el-row :gutter="24" class="td-section-row">
- <el-col :span="8">
- <el-card class="td-quick-card" shadow="hover">
- <div slot="header" class="td-card-header">
- <span class="td-card-title">快捷操作</span>
- </div>
- <div class="td-quick-actions">
- <div class="td-action-item" @click="$router.push('/teacher-families')">
- <div class="td-action-icon" style="background: #EBF5FF">
- <i class="el-icon-user" style="color: #3B82F6"></i>
- </div>
- <span>管理家庭</span>
- </div>
- <div class="td-action-item" @click="$router.push('/teacher-messages')">
- <div class="td-action-icon" style="background: #ECFDF5">
- <i class="el-icon-chat-line-round" style="color: #10B981"></i>
- </div>
- <span>消息中心</span>
- </div>
- <div class="td-action-item" @click="$router.push('/teacher-orders')">
- <div class="td-action-icon" style="background: #FFFBEB">
- <i class="el-icon-document" style="color: #F59E0B"></i>
- </div>
- <span>查看订单</span>
- </div>
- <div class="td-action-item" @click="$router.push('/teacher-assessment')">
- <div class="td-action-icon" style="background: #F5F3FF">
- <i class="el-icon-edit-outline" style="color: #8B5CF6"></i>
- </div>
- <span>DAN测评</span>
- </div>
- <div class="td-action-item" @click="$router.push('/teacher-health-plan-review')">
- <div class="td-action-icon" style="background: #FEF3C7">
- <i class="el-icon-document-checked" style="color: #D97706"></i>
- </div>
- <span>健康方案审核</span>
- </div>
- </div>
- </el-card>
- </el-col>
- <el-col :span="16">
- <el-card class="td-overview-card" shadow="hover">
- <div slot="header" class="td-card-header">
- <span class="td-card-title">今日概览</span>
- <span class="td-card-date">{{ todayDate }}</span>
- </div>
- <div class="td-today-stats">
- <div class="td-today-stat-item">
- <div class="td-today-stat-value" style="color: #3B82F6">{{ todayStats.newFamilies }}</div>
- <div class="td-today-stat-label">新增家庭</div>
- </div>
- <div class="td-today-stat-item">
- <div class="td-today-stat-value" style="color: #10B981">{{ todayStats.completedTasks }}</div>
- <div class="td-today-stat-label">完成任务</div>
- </div>
- <div class="td-today-stat-item">
- <div class="td-today-stat-value" style="color: #F59E0B">{{ todayStats.newOrders }}</div>
- <div class="td-today-stat-label">新订单</div>
- </div>
- <div class="td-today-stat-item">
- <div class="td-today-stat-value" style="color: #8B5CF6">{{ todayStats.pendingReviews }}</div>
- <div class="td-today-stat-label">待审核</div>
- </div>
- </div>
- </el-card>
- </el-col>
- </el-row>
- <!-- 我的家庭 + 我的团队 -->
- <el-row :gutter="24" class="td-section-row">
- <el-col :span="12">
- <el-card class="td-list-card" shadow="hover">
- <div slot="header" class="td-card-header">
- <span class="td-card-title">我的家庭</span>
- <el-button type="text" @click="$router.push('/teacher-families')">查看全部 <i class="el-icon-arrow-right"></i></el-button>
- </div>
- <div class="td-family-list">
- <div v-for="family in families" :key="family.id" class="td-family-item">
- <div class="td-family-avatar">
- <i class="el-icon-house"></i>
- </div>
- <div class="td-family-info">
- <div class="td-family-name">{{ family.familyName || '家庭' + family.familyId }}</div>
- <div class="td-family-meta">
- <span class="td-meta-tag"><i class="el-icon-user"></i> {{ family.childrenCount || 0 }}个孩子</span>
- </div>
- </div>
- <div class="td-family-stats">
- <el-progress type="circle" :percentage="parseFloat(family.taskCompletionRate) || 0" :width="50" :stroke-width="4"></el-progress>
- </div>
- </div>
- <div v-if="families.length === 0" class="td-empty-state">
- <i class="el-icon-folder-delete"></i>
- <p>暂无服务家庭</p>
- </div>
- </div>
- </el-card>
- </el-col>
- <el-col :span="12">
- <el-card class="td-list-card" shadow="hover">
- <div slot="header" class="td-card-header">
- <span class="td-card-title">我的团队</span>
- <el-button type="text" @click="$router.push('/teacher-team')">查看全部 <i class="el-icon-arrow-right"></i></el-button>
- </div>
- <div class="td-team-list">
- <div v-for="member in teamMembers" :key="member.id" class="td-team-item">
- <div class="td-member-avatar">
- <i class="el-icon-user"></i>
- </div>
- <div class="td-member-info">
- <div class="td-member-name">{{ member.nickname || '规划师' + member.id }}</div>
- <div class="td-member-level">
- <el-tag size="mini" :type="getLevelType(member.level)" effect="dark">{{ member.level || '初级' }}</el-tag>
- </div>
- </div>
- <div class="td-member-performance">
- <div class="td-performance-value">{{ formatPriceWithSymbol(member.monthlySales || 0) }}</div>
- <div class="td-performance-label">本月业绩</div>
- </div>
- </div>
- <div v-if="teamMembers.length === 0" class="td-empty-state">
- <i class="el-icon-user-solid"></i>
- <p>暂无团队成员</p>
- </div>
- </div>
- </el-card>
- </el-col>
- </el-row>
- <!-- 最近订单 -->
- <el-row :gutter="24" class="td-section-row">
- <el-col :span="24">
- <el-card class="td-orders-card" shadow="hover">
- <div slot="header" class="td-card-header">
- <span class="td-card-title">最近订单</span>
- <el-button type="text" @click="$router.push('/teacher-orders')">查看全部 <i class="el-icon-arrow-right"></i></el-button>
- </div>
- <div class="adm-table-wrap">
- <el-table :data="recentOrders" stripe style="width: 100%" class="td-orders-table" height="100%">
- <el-table-column prop="orderNo" label="订单号" width="200">
- <template slot-scope="scope">
- <span class="td-order-no">{{ scope.row.orderNo }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="familyName" label="家庭">
- <template slot-scope="scope">
- <div class="td-family-cell"><i class="el-icon-house"></i> {{ scope.row.familyName }}</div>
- </template>
- </el-table-column>
- <el-table-column prop="packageName" label="任务模板">
- <template slot-scope="scope">
- <el-tag size="small" effect="plain">{{ scope.row.packageName }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="amount" label="金额" width="120">
- <template slot-scope="scope">
- <span class="td-amount-value">{{ formatPriceWithSymbol(scope.row.amount) }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="status" label="状态" width="100">
- <template slot-scope="scope">
- <el-tag :type="getStatusType(scope.row.status)" size="small" effect="dark">{{ scope.row.status }}</el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="createdAt" label="时间" width="160"></el-table-column>
- </el-table>
- </div>
- </el-card>
- </el-col>
- </el-row>
- </div>
- </template>
- <script>
- import { getBoundFamilies, getTeamMembers, getGuideOrders, getGuideDashboardStats } from '@/api/guide'
- export default {
- name: 'TeacherDashboard',
- data() {
- return {
- teacherStats: {
- serviceFamilyCount: 0,
- activeFamilyCount: 0,
- monthlySales: 0,
- myCommission: 0
- },
- families: [],
- teamMembers: [],
- recentOrders: [],
- todayStats: {
- newFamilies: 0,
- completedTasks: 0,
- newOrders: 0,
- pendingReviews: 0
- }
- }
- },
- computed: {
- todayDate() {
- const now = new Date()
- const months = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']
- return `${months[now.getMonth()]}月${now.getDate()}日 星期${['日', '一', '二', '三', '四', '五', '六'][now.getDay()]}`
- }
- },
- mounted() {
- this.loadTeacherData()
- },
- methods: {
- async loadTeacherData() {
- try {
- const results = await Promise.all([
- getBoundFamilies().catch(err => { console.error('获取家庭列表失败:', err); return { data: [] } }),
- getTeamMembers().catch(err => { console.error('获取团队成员失败:', err); return { data: [] } }),
- getGuideOrders().catch(err => { console.error('获取订单失败:', err); return { data: { records: [] } } }),
- getGuideDashboardStats().catch(err => { console.error('获取统计数据失败:', err); return { data: {} } })
- ])
- const [familyRes, teamRes, orderRes, statsRes] = results
- // Guard against malformed responses
- var familyList = familyRes && familyRes.data ? familyRes.data : []
- var teamList = teamRes && teamRes.data ? teamRes.data : []
- var orderData = orderRes && orderRes.data ? orderRes.data : { records: [] }
- var statsData = statsRes && statsRes.data ? statsRes.data : {}
- this.families = (Array.isArray(familyList) ? familyList : []).slice(0, 5)
- this.teacherStats.serviceFamilyCount = this.families.length
- this.teacherStats.activeFamilyCount = this.families.filter(function(f) { return f.status === 'active' }).length
- this.teamMembers = (Array.isArray(teamList) ? teamList : []).slice(0, 5).map(function(m) { return Object.assign({}, m, { trend: 0 }) })
- var records = Array.isArray(orderData.records) ? orderData.records : []
- this.recentOrders = records.slice(0, 10)
- var totalSales = records.reduce(function(sum, o) { return sum + (o.amount || 0) }, 0)
- this.teacherStats.monthlySales = totalSales
- this.teacherStats.myCommission = Math.round(totalSales * 0.1)
- var self = this
- this.todayStats.newOrders = records.filter(function(o) {
- var today = new Date().toDateString()
- return o.createdAt && new Date(o.createdAt).toDateString() === today
- }).length
- this.todayStats.newFamilies = statsData.totalFamilies || 0
- this.todayStats.completedTasks = statsData.todayCompletedTasks || 0
- this.todayStats.pendingReviews = statsData.pendingReviewTasks || 0
- } catch (e) {
- console.error('加载教师工作台数据失败:', e)
- }
- },
- formatPriceWithSymbol(price) {
- if (price === null || price === undefined) return '¥0'
- return '¥' + Number(price).toLocaleString()
- },
- getLevelType(level) {
- const map = { '初级': '', '中级': 'success', '高级': 'warning' }
- return map[level] || ''
- },
- getStatusType(status) {
- const map = { '已完成': 'success', '待支付': 'warning', '已取消': 'info' }
- return map[status] || 'info'
- }
- }
- }
- </script>
- <style scoped>
- /* ===== Teacher Dashboard Styles ===== */
- .teacher-dashboard-wrapper { padding: 0; }
- .td-stat-row { margin-bottom: 24px; }
- .td-stat-card { display: flex; align-items: center; padding: 20px 24px; background: #FFFFFF; border-radius: 16px; box-shadow: 0 2px 12px rgba(59,130,246,0.08); border: none; transition: all 0.3s; }
- .td-stat-card:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(59,130,246,0.12); }
- .td-stat-icon { width: 56px; height: 56px; border-radius: 14px; display: flex; align-items: center; justify-content: center; margin-right: 16px; font-size: 24px; color: #FFFFFF; flex-shrink: 0; }
- .td-stat-value { font-size: 28px; font-weight: 700; color: #1E293B; line-height: 1.2; }
- .td-stat-label { font-size: 14px; color: #64748B; margin-top: 4px; }
- .td-section-row { margin-bottom: 24px; }
- .td-card-header { display: flex; justify-content: space-between; align-items: center; }
- .td-card-title { font-size: 16px; font-weight: 600; color: #1E293B; }
- .td-card-date { font-size: 13px; color: #64748B; }
- .td-quick-card, .td-overview-card, .td-list-card, .td-orders-card { border-radius: 16px; border: none; box-shadow: 0 2px 12px rgba(59,130,246,0.06); }
- .td-quick-card >>> .el-card__header, .td-overview-card >>> .el-card__header, .td-list-card >>> .el-card__header, .td-orders-card >>> .el-card__header { border-bottom: 1px solid #F1F5F9; padding: 16px 20px; }
- .td-quick-actions { display: flex; flex-wrap: wrap; gap: 16px; }
- .td-action-item { flex: 1 1 calc(50% - 8px); min-width: 120px; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 16px; background: #F8FAFC; border-radius: 12px; cursor: pointer; transition: all 0.2s; box-sizing: border-box; }
- .td-action-item:hover { background: #F1F5F9; transform: translateY(-2px); }
- .td-action-icon { width: 44px; height: 44px; border-radius: 12px; display: flex; align-items: center; justify-content: center; margin-bottom: 8px; font-size: 20px; }
- .td-action-item span { font-size: 13px; color: #64748B; }
- .td-today-stats { display: flex; flex-wrap: wrap; gap: 16px; }
- .td-today-stat-item { flex: 1 1 calc(25% - 12px); min-width: 100px; text-align: center; padding: 12px; background: #F8FAFC; border-radius: 12px; box-sizing: border-box; }
- .td-today-stat-value { font-size: 28px; font-weight: 700; line-height: 1.2; }
- .td-today-stat-label { font-size: 13px; color: #64748B; margin-top: 4px; }
- .td-family-list, .td-team-list { max-height: 400px; overflow-y: auto; }
- .td-family-item, .td-team-item { display: flex; align-items: center; padding: 12px 0; border-bottom: 1px solid #F1F5F9; }
- .td-family-item:last-child, .td-team-item:last-child { border-bottom: none; }
- .td-family-avatar, .td-member-avatar { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; background: #EBF5FF; color: #3B82F6; font-size: 20px; margin-right: 12px; flex-shrink: 0; }
- .td-family-info, .td-member-info { flex: 1; min-width: 0; }
- .td-family-name, .td-member-name { font-size: 14px; font-weight: 600; color: #1E293B; margin-bottom: 4px; }
- .td-family-meta { display: flex; gap: 12px; }
- .td-meta-tag { font-size: 12px; color: #64748B; }
- .td-meta-tag i { margin-right: 2px; }
- .td-family-stats { text-align: center; margin-left: 12px; }
- .td-member-level { margin-top: 2px; }
- .td-member-performance { text-align: right; margin-left: 12px; }
- .td-performance-value { font-size: 14px; font-weight: 600; color: #1E293B; }
- .td-performance-label { font-size: 11px; color: #64748B; margin-top: 2px; }
- .td-empty-state { text-align: center; padding: 40px 0; color: #94A3B8; }
- .td-empty-state i { font-size: 40px; margin-bottom: 8px; }
- .td-empty-state p { font-size: 14px; margin: 0; }
- .td-orders-table >>> .el-table__header th { background: #F8FAFC; color: #64748B; font-weight: 600; }
- .td-order-no { color: #3B82F6; font-weight: 500; }
- .td-family-cell i { margin-right: 4px; color: #64748B; }
- .td-amount-value { font-weight: 600; color: #1E293B; }
- </style>
|