| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 |
- <template>
- <div class="dashboard-container">
- <!-- 团队统计卡片 -->
- <div v-if="isAdmin || isTeam" class="stats-cards">
- <!-- 管理员视图 -->
- <div v-if="isAdmin" class="admin-stats">
- <div class="card">
- <h3>团队总数</h3>
- <p class="stat-value">{{ teamStats?.totalTeams || 0 }}</p>
- </div>
- <div class="card">
- <h3>总收入</h3>
- <p class="stat-value">{{ formatAmount(teamStats?.totalRevenue) }}</p>
- </div>
- <div class="card">
- <h3>今日收入</h3>
- <p class="stat-value">{{ formatAmount(teamStats?.todayRevenue) }}</p>
- </div>
- </div>
- <!-- 队长视图 -->
- <div v-else-if="isTeam" class="team-stats">
- <div class="card">
- <h3>团队名称</h3>
- <p class="stat-value">{{ teamStats?.name || '-' }}</p>
- </div>
- <div class="card">
- <h3>总收入</h3>
- <p class="stat-value">{{ formatAmount(teamStats?.totalRevenue) }}</p>
- </div>
- <div class="card">
- <h3>今日收入</h3>
- <p class="stat-value">{{ formatAmount(teamStats?.todayRevenue) }}</p>
- </div>
- <div class="card">
- <h3>佣金率</h3>
- <p class="stat-value">{{ formatRate(teamStats?.commissionRate) }}%</p>
- </div>
- </div>
- </div>
- <!-- 收入图表 -->
- <div class="chart-container">
- <div class="chart-header">
- <h2>最近7天收入统计</h2>
- <div v-if="isAdmin" class="chart-team-selector">
- <Select
- v-model="selectedChartTeamId"
- :options="teamOptions"
- optionLabel="label"
- optionValue="value"
- placeholder="选择团队"
- class="w-full"
- />
- </div>
- </div>
- <div class="chart-wrapper">
- <canvas id="incomeChart" height="300"></canvas>
- </div>
- </div>
- <!-- 团队数据表格 (仅管理员可见) -->
- <div v-if="isAdmin" class="teams-table-container">
- <h2>团队数据一览</h2>
- <DataTable :value="teams" stripedRows class="teams-table">
- <Column field="id" header="ID" sortable></Column>
- <Column field="name" header="团队名称" sortable></Column>
- <Column field="totalRevenue" header="总收入" sortable>
- <template #body="slotProps">
- {{ formatAmount(slotProps.data.totalRevenue) }}
- </template>
- </Column>
- <Column field="todayRevenue" header="今日收入" sortable>
- <template #body="slotProps">
- {{ formatAmount(slotProps.data.todayRevenue) }}
- </template>
- </Column>
- </DataTable>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, onMounted, computed, inject, watch } from 'vue'
- import { useUserStore } from '@/stores/user'
- import { useTeamStore } from '@/stores/team'
- import { getAllTeamStatistics, getTeamStatistics, getIncomeStatistics } from '@/services/api'
- import Chart from 'chart.js/auto'
- import Select from 'primevue/select'
- import DataTable from 'primevue/datatable'
- import Column from 'primevue/column'
- const userStore = useUserStore()
- const teamStore = useTeamStore()
- const role = computed(() => userStore.userInfo?.role)
- const isAdmin = inject('isAdmin')
- const isTeam = inject('isTeam')
- const isPromoter = inject('isPromoter')
- // 数据
- const teamStats = ref(null)
- const selectedTeamId = ref(null)
- const incomeStats = ref(null)
- const loading = ref(true)
- const teams = ref([])
- const chartInstance = ref(null)
- const selectedChartTeamId = ref(null) // 用于图表的团队选择
- // 团队选项
- const teamOptions = computed(() => {
- return [
- { label: '所有团队', value: null },
- ...teamStore.teams.map((team) => ({
- label: team.name,
- value: team.id
- }))
- ]
- })
- // 获取选中团队的图表数据
- const selectedTeamChartData = computed(() => {
- if (!incomeStats.value || !selectedChartTeamId.value) {
- return null
- }
- const teamData = incomeStats.value.teams.find((team) => team.teamId === selectedChartTeamId.value)
- return teamData || null
- })
- // 格式化金额,保留2位小数
- const formatAmount = (amount) => {
- if (amount === undefined || amount === null) return '0.00'
- return Number(amount).toFixed(2)
- }
- // 格式化比率,保留2位小数
- const formatRate = (rate) => {
- if (rate === undefined || rate === null) return '0.00'
- return Number(rate).toFixed(2)
- }
- // 获取当前日期和7天前的日期
- const getDateRange = () => {
- const endDate = new Date()
- const startDate = new Date()
- startDate.setDate(startDate.getDate() - 6) // 最近7天(包括今天)
- return {
- startDate: formatDate(startDate),
- endDate: formatDate(endDate)
- }
- }
- // 格式化日期为 YYYY-MM-DD
- const formatDate = (date) => {
- const year = date.getFullYear()
- const month = String(date.getMonth() + 1).padStart(2, '0')
- const day = String(date.getDate()).padStart(2, '0')
- return `${year}-${month}-${day}`
- }
- // 加载团队统计数据
- const loadTeamStats = async () => {
- try {
- loading.value = true
- console.log('当前用户角色:', role.value)
- console.log('用户信息:', userStore.userInfo)
- console.log('角色判断:', { isAdmin, isTeam, isPromoter })
- if (isAdmin.value) {
- console.log('加载管理员团队统计数据')
- const data = await getAllTeamStatistics()
- console.log('管理员团队统计数据:', data)
- teamStats.value = data
- teams.value = data.allTeams || []
- if (teams.value.length > 0 && !selectedTeamId.value) {
- selectedTeamId.value = teams.value[0].id
- }
- } else if (isTeam.value) {
- console.log('加载队长团队统计数据')
- const data = await getTeamStatistics()
- console.log('队长团队统计数据:', data)
- teamStats.value = data
- }
- } catch (error) {
- console.error('加载团队统计数据失败:', error)
- } finally {
- loading.value = false
- }
- }
- // 加载收入统计数据
- const loadIncomeStats = async () => {
- try {
- loading.value = true
- const { startDate, endDate } = getDateRange()
- let params = { startDate, endDate }
- const data = await getIncomeStatistics(startDate, endDate)
- incomeStats.value = data
- // 渲染图表
- renderChart()
- } catch (error) {
- console.error('加载收入统计数据失败:', error)
- } finally {
- loading.value = false
- }
- }
- // 渲染图表
- const renderChart = () => {
- if (!incomeStats.value) return
- const chartElement = document.getElementById('incomeChart')
- if (!chartElement) return
- // 如果已有图表实例,先销毁
- if (chartInstance.value) {
- chartInstance.value.destroy()
- }
- const ctx = chartElement.getContext('2d')
- // 准备数据
- const labels = incomeStats.value.dates
- let data, tipData, commissionData
- // 根据是否选择了特定团队来决定使用哪组数据
- if (selectedTeamChartData.value) {
- // 使用选中团队的数据
- data = selectedTeamChartData.value.data
- tipData = selectedTeamChartData.value.tip
- commissionData = selectedTeamChartData.value.commission
- } else {
- // 使用总体数据
- data = incomeStats.value.total
- tipData = incomeStats.value.totalTip
- commissionData = incomeStats.value.totalCommission
- }
- const datasets = [
- {
- label: '总收入',
- data: data,
- borderColor: '#2563eb',
- backgroundColor: 'rgba(37, 99, 235, 0.1)',
- fill: true,
- tension: 0.4
- },
- {
- label: '打赏收入',
- data: tipData,
- borderColor: '#10b981',
- backgroundColor: 'rgba(16, 185, 129, 0.1)',
- fill: true,
- tension: 0.4
- },
- {
- label: '返佣收入',
- data: commissionData,
- borderColor: '#f59e0b',
- backgroundColor: 'rgba(245, 158, 11, 0.1)',
- fill: true,
- tension: 0.4
- }
- ]
- // 创建图表
- chartInstance.value = new Chart(ctx, {
- type: 'line',
- data: { labels, datasets },
- options: {
- responsive: true,
- maintainAspectRatio: false,
- plugins: {
- legend: {
- position: 'top'
- },
- tooltip: {
- mode: 'index',
- intersect: false
- }
- },
- scales: {
- y: {
- beginAtZero: true
- }
- }
- }
- })
- }
- // 监听团队选择变化
- watch(selectedChartTeamId, () => {
- renderChart()
- })
- // 组件挂载时加载数据
- onMounted(async () => {
- if (isAdmin.value) {
- await teamStore.loadTeams()
- }
- await loadTeamStats()
- await loadIncomeStats()
- })
- </script>
- <style scoped>
- .dashboard-container {
- padding: 1.5rem;
- }
- .stats-cards {
- display: flex;
- flex-wrap: wrap;
- gap: 1rem;
- margin-bottom: 2rem;
- }
- .admin-stats {
- display: flex;
- flex-wrap: wrap;
- gap: 1rem;
- width: 100%;
- }
- .admin-stats .card {
- flex: 1;
- min-width: 200px;
- max-width: 300px;
- }
- .team-stats {
- display: flex;
- flex-wrap: wrap;
- gap: 1rem;
- width: 100%;
- }
- .team-stats .card {
- flex: 1;
- min-width: 200px;
- max-width: 300px;
- }
- .card {
- background-color: white;
- border-radius: 0.5rem;
- padding: 1.5rem;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- text-align: center;
- }
- .card h3 {
- font-size: 1rem;
- color: #6b7280;
- margin-top: 0;
- margin-bottom: 0.75rem;
- white-space: nowrap;
- }
- .stat-value {
- font-size: 1.75rem;
- font-weight: bold;
- color: #2563eb;
- margin: 0;
- line-height: 1.2;
- }
- .team-selector {
- grid-column: 1 / -1;
- margin-top: 1rem;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- }
- .team-selector select {
- padding: 0.5rem;
- border-radius: 0.25rem;
- border: 1px solid #d1d5db;
- }
- .chart-container {
- background-color: white;
- border-radius: 0.5rem;
- padding: 1.5rem;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
- }
- .chart-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 1rem;
- }
- .chart-header h2 {
- font-size: 1.25rem;
- margin: 0;
- color: #1f2937;
- }
- .chart-team-selector {
- width: 200px;
- }
- /* PrimeVue Select 组件样式 */
- :deep(.p-dropdown) {
- width: 100%;
- }
- :deep(.p-dropdown-label) {
- padding: 0.5rem;
- }
- .chart-wrapper {
- height: 400px;
- position: relative;
- }
- .teams-table-container {
- background-color: white;
- border-radius: 0.5rem;
- padding: 1.5rem;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
- margin-top: 1.5rem;
- }
- .teams-table-container h2 {
- font-size: 1.25rem;
- margin-top: 0;
- margin-bottom: 1rem;
- color: #1f2937;
- }
- .teams-table {
- width: 100%;
- }
- :deep(.p-datatable .p-datatable-thead > tr > th) {
- background-color: #f3f4f6;
- color: #4b5563;
- font-weight: 600;
- padding: 0.75rem 1rem;
- }
- :deep(.p-datatable .p-datatable-tbody > tr) {
- transition: background-color 0.2s;
- }
- :deep(.p-datatable .p-datatable-tbody > tr:nth-child(even)) {
- background-color: #f9fafb;
- }
- :deep(.p-datatable .p-datatable-tbody > tr:hover) {
- background-color: #f3f4f6;
- }
- :deep(.p-datatable .p-datatable-tbody > tr > td) {
- padding: 0.75rem 1rem;
- border-bottom: 1px solid #e5e7eb;
- }
- </style>
|