|
|
@@ -0,0 +1,794 @@
|
|
|
+<template>
|
|
|
+ <div class="account-analytics">
|
|
|
+ <!-- 顶部筛选栏 -->
|
|
|
+ <div class="filter-bar">
|
|
|
+ <div class="filter-left">
|
|
|
+ <span class="filter-label">开始时间</span>
|
|
|
+ <el-date-picker
|
|
|
+ v-model="startDate"
|
|
|
+ type="date"
|
|
|
+ placeholder="选择日期"
|
|
|
+ format="YYYY-MM-DD"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ style="width: 140px"
|
|
|
+ />
|
|
|
+ <span class="filter-label">结束时间</span>
|
|
|
+ <el-date-picker
|
|
|
+ v-model="endDate"
|
|
|
+ type="date"
|
|
|
+ placeholder="选择日期"
|
|
|
+ format="YYYY-MM-DD"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ style="width: 140px"
|
|
|
+ />
|
|
|
+ <div class="quick-btns">
|
|
|
+ <el-button
|
|
|
+ v-for="btn in quickDateBtns"
|
|
|
+ :key="btn.value"
|
|
|
+ :type="activeQuickBtn === btn.value ? 'primary' : 'default'"
|
|
|
+ size="small"
|
|
|
+ @click="handleQuickDate(btn.value)"
|
|
|
+ >
|
|
|
+ {{ btn.label }}
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <el-select v-model="selectedGroup" placeholder="全部分组" clearable style="width: 120px">
|
|
|
+ <el-option label="全部分组" value="" />
|
|
|
+ <el-option
|
|
|
+ v-for="group in accountGroups"
|
|
|
+ :key="group.id"
|
|
|
+ :label="group.name"
|
|
|
+ :value="group.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ <el-select v-model="selectedPlatform" placeholder="全部平台" clearable style="width: 120px">
|
|
|
+ <el-option label="全部平台" value="" />
|
|
|
+ <el-option
|
|
|
+ v-for="platform in availablePlatforms"
|
|
|
+ :key="platform.value"
|
|
|
+ :label="platform.label"
|
|
|
+ :value="platform.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ <el-button type="primary" @click="handleQuery">查询</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="filter-right">
|
|
|
+ <el-button @click="handleExport">导出数据</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 统计卡片 -->
|
|
|
+ <div class="stats-row">
|
|
|
+ <div class="stat-card" v-for="(item, index) in summaryStats" :key="index">
|
|
|
+ <div class="stat-icon">
|
|
|
+ <el-icon :size="18"><component :is="item.icon" /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="stat-info">
|
|
|
+ <div class="stat-label">{{ item.label }}</div>
|
|
|
+ <div class="stat-value">{{ item.value }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 搜索框 -->
|
|
|
+ <div class="search-bar">
|
|
|
+ <el-input
|
|
|
+ v-model="searchKeyword"
|
|
|
+ placeholder="请输入要搜索的账号"
|
|
|
+ clearable
|
|
|
+ style="width: 300px"
|
|
|
+ @clear="handleQuery"
|
|
|
+ @keyup.enter="handleQuery"
|
|
|
+ >
|
|
|
+ <template #prefix>
|
|
|
+ <el-icon><Search /></el-icon>
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 数据表格 -->
|
|
|
+ <div class="data-table">
|
|
|
+ <el-table :data="filteredAccounts" v-loading="loading" stripe>
|
|
|
+ <el-table-column label="账号" min-width="150">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="account-cell">
|
|
|
+ <el-avatar :size="36" :src="row.avatarUrl">
|
|
|
+ {{ row.nickname?.[0] || row.username?.[0] }}
|
|
|
+ </el-avatar>
|
|
|
+ <span class="account-name">{{ row.nickname || row.username }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="平台" width="120" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="platform-cell">
|
|
|
+ <img :src="getPlatformIcon(row.platform)" class="platform-icon" :alt="row.platform" />
|
|
|
+ <span>{{ getPlatformName(row.platform) }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="income" label="收益" width="100" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span>{{ row.income ?? '未支持' }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="recommendCount" label="推荐量" width="100" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span>{{ row.recommendCount ?? '未支持' }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="viewsCount" label="阅读(播放)量" width="130" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span>{{ row.viewsCount ?? 0 }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="commentsCount" label="评论量" width="90" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span>{{ row.commentsCount ?? 0 }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="likesCount" label="点赞量" width="90" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span>{{ row.likesCount ?? 0 }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="fansIncrease" label="涨粉量" width="90" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span :class="{ 'increase': row.fansIncrease > 0, 'decrease': row.fansIncrease < 0 }">
|
|
|
+ {{ row.fansIncrease ?? 0 }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="updateTime" label="更新时间" width="120" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="update-time">{{ formatTime(row.updateTime) }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="status" label="状态" width="80" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span :class="['status-tag', row.status === 'active' ? 'active' : 'inactive']">
|
|
|
+ {{ row.status === 'active' ? '正常' : '异常' }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="80" align="center" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button type="primary" link @click="handleDetail(row)">
|
|
|
+ 详情
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 账号详情抽屉 -->
|
|
|
+ <el-drawer v-model="drawerVisible" :title="drawerTitle" size="50%">
|
|
|
+ <div v-if="selectedAccount" class="account-detail">
|
|
|
+ <!-- 账号基本信息 -->
|
|
|
+ <div class="detail-header">
|
|
|
+ <el-avatar :size="64" :src="selectedAccount.avatarUrl">
|
|
|
+ {{ selectedAccount.nickname?.[0] }}
|
|
|
+ </el-avatar>
|
|
|
+ <div class="header-info">
|
|
|
+ <h3>{{ selectedAccount.nickname }}</h3>
|
|
|
+ <div class="platform-info">
|
|
|
+ <img :src="getPlatformIcon(selectedAccount.platform)" class="platform-icon" />
|
|
|
+ <span>{{ getPlatformName(selectedAccount.platform) }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 数据统计 -->
|
|
|
+ <div class="detail-stats">
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-value">{{ formatNumber(selectedAccount.fansCount || 0) }}</div>
|
|
|
+ <div class="stat-label">粉丝数</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-value">{{ formatNumber(selectedAccount.viewsCount || 0) }}</div>
|
|
|
+ <div class="stat-label">播放量</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-value">{{ formatNumber(selectedAccount.likesCount || 0) }}</div>
|
|
|
+ <div class="stat-label">点赞数</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-item">
|
|
|
+ <div class="stat-value">{{ formatNumber(selectedAccount.commentsCount || 0) }}</div>
|
|
|
+ <div class="stat-label">评论数</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 趋势图 -->
|
|
|
+ <div class="detail-chart">
|
|
|
+ <h4>数据趋势</h4>
|
|
|
+ <div ref="accountChartRef" style="height: 300px" v-loading="chartLoading"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-drawer>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, computed, onMounted, watch, nextTick } from 'vue';
|
|
|
+import * as echarts from 'echarts';
|
|
|
+import { Search, User, Coin, Pointer, View, ChatDotRound, Star, TrendCharts } from '@element-plus/icons-vue';
|
|
|
+import { PLATFORMS } from '@media-manager/shared';
|
|
|
+import type { PlatformType } from '@media-manager/shared';
|
|
|
+import { useAuthStore } from '@/stores/auth';
|
|
|
+import { ElMessage } from 'element-plus';
|
|
|
+import dayjs from 'dayjs';
|
|
|
+import request from '@/api/request';
|
|
|
+
|
|
|
+const PYTHON_API_URL = 'http://localhost:5005';
|
|
|
+
|
|
|
+const authStore = useAuthStore();
|
|
|
+const loading = ref(false);
|
|
|
+const chartLoading = ref(false);
|
|
|
+
|
|
|
+// 日期筛选
|
|
|
+const startDate = ref(dayjs().format('YYYY-MM-DD'));
|
|
|
+const endDate = ref(dayjs().format('YYYY-MM-DD'));
|
|
|
+const activeQuickBtn = ref('yesterday');
|
|
|
+
|
|
|
+// 快捷日期按钮
|
|
|
+const quickDateBtns = [
|
|
|
+ { label: '昨天', value: 'yesterday' },
|
|
|
+ { label: '前天', value: 'beforeYesterday' },
|
|
|
+ { label: '近三天', value: 'last3days' },
|
|
|
+ { label: '近七天', value: 'last7days' },
|
|
|
+ { label: '近一个月', value: 'lastMonth' },
|
|
|
+];
|
|
|
+
|
|
|
+// 分组和平台筛选
|
|
|
+const selectedGroup = ref<number | ''>('');
|
|
|
+const selectedPlatform = ref<PlatformType | ''>('');
|
|
|
+const searchKeyword = ref('');
|
|
|
+
|
|
|
+// 分组列表
|
|
|
+interface AccountGroup {
|
|
|
+ id: number;
|
|
|
+ name: string;
|
|
|
+}
|
|
|
+const accountGroups = ref<AccountGroup[]>([]);
|
|
|
+
|
|
|
+// 可用平台
|
|
|
+const availablePlatforms = computed(() => {
|
|
|
+ return Object.entries(PLATFORMS).map(([key, value]) => ({
|
|
|
+ value: key as PlatformType,
|
|
|
+ label: value.name,
|
|
|
+ }));
|
|
|
+});
|
|
|
+
|
|
|
+// 平台图标映射
|
|
|
+const platformIcons: Record<string, string> = {
|
|
|
+ douyin: 'https://lf1-cdn-tos.bytescm.com/obj/static/ies/douyin_web/public/favicon.ico',
|
|
|
+ xiaohongshu: 'https://fe-video-qc.xhscdn.com/fe-platform/ed8fe603ce7e10bff8eb0d7c0a7bdf70cedf7f92.ico',
|
|
|
+ bilibili: 'https://www.bilibili.com/favicon.ico',
|
|
|
+ kuaishou: 'https://www.kuaishou.com/favicon.ico',
|
|
|
+ weixin: 'https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico',
|
|
|
+};
|
|
|
+
|
|
|
+// 汇总统计
|
|
|
+const summaryData = ref({
|
|
|
+ totalAccounts: 0,
|
|
|
+ income: 0,
|
|
|
+ recommendCount: 0,
|
|
|
+ viewsCount: 0,
|
|
|
+ commentsCount: 0,
|
|
|
+ likesCount: 0,
|
|
|
+ fansIncrease: 0,
|
|
|
+});
|
|
|
+
|
|
|
+// 统计卡片数据
|
|
|
+const summaryStats = computed(() => [
|
|
|
+ { label: '账号总数', value: summaryData.value.totalAccounts, icon: User },
|
|
|
+ { label: '收益', value: summaryData.value.income, icon: Coin },
|
|
|
+ { label: '推荐量', value: summaryData.value.recommendCount, icon: Pointer },
|
|
|
+ { label: '播放(阅读)量', value: summaryData.value.viewsCount, icon: View },
|
|
|
+ { label: '评论量', value: summaryData.value.commentsCount, icon: ChatDotRound },
|
|
|
+ { label: '点赞量', value: summaryData.value.likesCount, icon: Star },
|
|
|
+ { label: '涨粉量', value: summaryData.value.fansIncrease, icon: TrendCharts },
|
|
|
+]);
|
|
|
+
|
|
|
+// 账号数据
|
|
|
+interface AccountData {
|
|
|
+ id: number;
|
|
|
+ nickname: string;
|
|
|
+ username: string;
|
|
|
+ avatarUrl: string;
|
|
|
+ platform: PlatformType;
|
|
|
+ groupId?: number;
|
|
|
+ fansCount: number;
|
|
|
+ income: number | null;
|
|
|
+ recommendCount: number | null;
|
|
|
+ viewsCount: number;
|
|
|
+ commentsCount: number;
|
|
|
+ likesCount: number;
|
|
|
+ fansIncrease: number;
|
|
|
+ updateTime: string;
|
|
|
+ status: string;
|
|
|
+}
|
|
|
+
|
|
|
+const accounts = ref<AccountData[]>([]);
|
|
|
+
|
|
|
+// 过滤后的账号列表
|
|
|
+const filteredAccounts = computed(() => {
|
|
|
+ let result = accounts.value;
|
|
|
+
|
|
|
+ if (selectedGroup.value) {
|
|
|
+ result = result.filter(a => a.groupId === selectedGroup.value);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (selectedPlatform.value) {
|
|
|
+ result = result.filter(a => a.platform === selectedPlatform.value);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (searchKeyword.value) {
|
|
|
+ const keyword = searchKeyword.value.toLowerCase();
|
|
|
+ result = result.filter(a =>
|
|
|
+ a.nickname?.toLowerCase().includes(keyword) ||
|
|
|
+ a.username?.toLowerCase().includes(keyword)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+});
|
|
|
+
|
|
|
+// 抽屉相关
|
|
|
+const drawerVisible = ref(false);
|
|
|
+const selectedAccount = ref<AccountData | null>(null);
|
|
|
+const accountChartRef = ref<HTMLElement>();
|
|
|
+let accountChart: echarts.ECharts | null = null;
|
|
|
+
|
|
|
+const drawerTitle = computed(() => {
|
|
|
+ if (!selectedAccount.value) return '账号详情';
|
|
|
+ return `${selectedAccount.value.nickname} - 数据详情`;
|
|
|
+});
|
|
|
+
|
|
|
+function getPlatformName(platform: PlatformType) {
|
|
|
+ return PLATFORMS[platform]?.name || platform;
|
|
|
+}
|
|
|
+
|
|
|
+function getPlatformIcon(platform: PlatformType) {
|
|
|
+ return platformIcons[platform] || '';
|
|
|
+}
|
|
|
+
|
|
|
+function formatNumber(num: number) {
|
|
|
+ if (num >= 10000) return (num / 10000).toFixed(1) + 'w';
|
|
|
+ return num.toString();
|
|
|
+}
|
|
|
+
|
|
|
+function formatTime(time: string) {
|
|
|
+ if (!time) return '-';
|
|
|
+ return dayjs(time).format('MM-DD HH:mm');
|
|
|
+}
|
|
|
+
|
|
|
+// 快捷日期选择
|
|
|
+function handleQuickDate(type: string) {
|
|
|
+ activeQuickBtn.value = type;
|
|
|
+ const today = dayjs();
|
|
|
+
|
|
|
+ switch (type) {
|
|
|
+ case 'yesterday':
|
|
|
+ startDate.value = today.subtract(1, 'day').format('YYYY-MM-DD');
|
|
|
+ endDate.value = today.subtract(1, 'day').format('YYYY-MM-DD');
|
|
|
+ break;
|
|
|
+ case 'beforeYesterday':
|
|
|
+ startDate.value = today.subtract(2, 'day').format('YYYY-MM-DD');
|
|
|
+ endDate.value = today.subtract(2, 'day').format('YYYY-MM-DD');
|
|
|
+ break;
|
|
|
+ case 'last3days':
|
|
|
+ startDate.value = today.subtract(3, 'day').format('YYYY-MM-DD');
|
|
|
+ endDate.value = today.subtract(1, 'day').format('YYYY-MM-DD');
|
|
|
+ break;
|
|
|
+ case 'last7days':
|
|
|
+ startDate.value = today.subtract(7, 'day').format('YYYY-MM-DD');
|
|
|
+ endDate.value = today.subtract(1, 'day').format('YYYY-MM-DD');
|
|
|
+ break;
|
|
|
+ case 'lastMonth':
|
|
|
+ startDate.value = today.subtract(30, 'day').format('YYYY-MM-DD');
|
|
|
+ endDate.value = today.subtract(1, 'day').format('YYYY-MM-DD');
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 查询
|
|
|
+function handleQuery() {
|
|
|
+ loadData();
|
|
|
+}
|
|
|
+
|
|
|
+// 加载分组列表
|
|
|
+async function loadGroups() {
|
|
|
+ try {
|
|
|
+ const res = await request.get('/accounts/groups');
|
|
|
+ if (res.data.success) {
|
|
|
+ accountGroups.value = res.data.data || [];
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载分组失败:', error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 加载数据
|
|
|
+async function loadData() {
|
|
|
+ const userId = authStore.user?.id;
|
|
|
+ if (!userId) return;
|
|
|
+
|
|
|
+ loading.value = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const queryParams = new URLSearchParams({
|
|
|
+ user_id: userId.toString(),
|
|
|
+ start_date: startDate.value,
|
|
|
+ end_date: endDate.value,
|
|
|
+ });
|
|
|
+
|
|
|
+ if (selectedPlatform.value) {
|
|
|
+ queryParams.append('platform', selectedPlatform.value);
|
|
|
+ }
|
|
|
+
|
|
|
+ const response = await fetch(`${PYTHON_API_URL}/work_day_statistics/accounts?${queryParams}`);
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result.success && result.data) {
|
|
|
+ accounts.value = result.data.accounts || [];
|
|
|
+
|
|
|
+ if (result.data.summary) {
|
|
|
+ summaryData.value = result.data.summary;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载账号数据失败:', error);
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 查看详情
|
|
|
+async function handleDetail(row: AccountData) {
|
|
|
+ selectedAccount.value = row;
|
|
|
+ drawerVisible.value = true;
|
|
|
+
|
|
|
+ await nextTick();
|
|
|
+ loadAccountTrend(row.id);
|
|
|
+}
|
|
|
+
|
|
|
+// 加载账号趋势数据
|
|
|
+async function loadAccountTrend(accountId: number) {
|
|
|
+ if (!accountChartRef.value) return;
|
|
|
+
|
|
|
+ chartLoading.value = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const userId = authStore.user?.id;
|
|
|
+ if (!userId) return;
|
|
|
+
|
|
|
+ const queryParams = new URLSearchParams({
|
|
|
+ user_id: userId.toString(),
|
|
|
+ account_id: accountId.toString(),
|
|
|
+ start_date: startDate.value,
|
|
|
+ end_date: endDate.value,
|
|
|
+ });
|
|
|
+
|
|
|
+ const response = await fetch(`${PYTHON_API_URL}/work_day_statistics/account_trend?${queryParams}`);
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result.success && result.data) {
|
|
|
+ updateAccountChart(result.data);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载账号趋势失败:', error);
|
|
|
+ } finally {
|
|
|
+ chartLoading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 更新账号趋势图
|
|
|
+function updateAccountChart(trendData: { dates: string[]; fans: number[]; views: number[]; likes: number[] }) {
|
|
|
+ if (!accountChartRef.value) return;
|
|
|
+
|
|
|
+ if (!accountChart) {
|
|
|
+ accountChart = echarts.init(accountChartRef.value);
|
|
|
+ }
|
|
|
+
|
|
|
+ accountChart.setOption({
|
|
|
+ tooltip: { trigger: 'axis' },
|
|
|
+ legend: { data: ['粉丝', '播放', '点赞'], bottom: 0 },
|
|
|
+ grid: { left: '3%', right: '4%', bottom: '15%', top: '10%', containLabel: true },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: trendData.dates,
|
|
|
+ axisLabel: { color: '#6b7280' },
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ axisLabel: {
|
|
|
+ color: '#6b7280',
|
|
|
+ formatter: (value: number) => value >= 10000 ? (value / 10000).toFixed(1) + '万' : value.toString(),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ { name: '粉丝', type: 'line', data: trendData.fans, smooth: true },
|
|
|
+ { name: '播放', type: 'line', data: trendData.views, smooth: true },
|
|
|
+ { name: '点赞', type: 'line', data: trendData.likes, smooth: true },
|
|
|
+ ],
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+// 导出数据
|
|
|
+function handleExport() {
|
|
|
+ ElMessage.info('导出功能开发中');
|
|
|
+}
|
|
|
+
|
|
|
+// 监听抽屉关闭
|
|
|
+watch(drawerVisible, (visible) => {
|
|
|
+ if (!visible && accountChart) {
|
|
|
+ accountChart.dispose();
|
|
|
+ accountChart = null;
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ // 默认选择昨天
|
|
|
+ handleQuickDate('yesterday');
|
|
|
+ loadGroups();
|
|
|
+ loadData();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+@use '@/styles/variables.scss' as *;
|
|
|
+
|
|
|
+.account-analytics {
|
|
|
+ .filter-bar {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ padding: 16px 20px;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: $radius-lg;
|
|
|
+ box-shadow: $shadow-sm;
|
|
|
+
|
|
|
+ .filter-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+
|
|
|
+ .filter-label {
|
|
|
+ font-size: 14px;
|
|
|
+ color: $text-regular;
|
|
|
+ }
|
|
|
+
|
|
|
+ .quick-btns {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .stats-row {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(7, 1fr);
|
|
|
+ gap: 0;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: $radius-lg;
|
|
|
+ box-shadow: $shadow-sm;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .stat-card {
|
|
|
+ padding: 20px 16px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ border-right: 1px solid #f0f0f0;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ border-right: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-icon {
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ background: $primary-color-light;
|
|
|
+ border-radius: 8px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: $primary-color;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-info {
|
|
|
+ .stat-label {
|
|
|
+ font-size: 12px;
|
|
|
+ color: $text-secondary;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-value {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: $text-primary;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-bar {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .data-table {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: $radius-lg;
|
|
|
+ box-shadow: $shadow-sm;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .account-cell {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+
|
|
|
+ .account-name {
|
|
|
+ font-weight: 500;
|
|
|
+ color: $text-primary;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .platform-cell {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 6px;
|
|
|
+
|
|
|
+ .platform-icon {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .increase {
|
|
|
+ color: #10b981;
|
|
|
+ }
|
|
|
+
|
|
|
+ .decrease {
|
|
|
+ color: #ef4444;
|
|
|
+ }
|
|
|
+
|
|
|
+ .update-time {
|
|
|
+ font-size: 12px;
|
|
|
+ color: $text-secondary;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-tag {
|
|
|
+ font-size: 12px;
|
|
|
+ padding: 2px 8px;
|
|
|
+ border-radius: 4px;
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ color: #10b981;
|
|
|
+ background: rgba(16, 185, 129, 0.1);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.inactive {
|
|
|
+ color: #ef4444;
|
|
|
+ background: rgba(239, 68, 68, 0.1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.account-detail {
|
|
|
+ .detail-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16px;
|
|
|
+ margin-bottom: 24px;
|
|
|
+
|
|
|
+ .header-info {
|
|
|
+ h3 {
|
|
|
+ margin: 0 0 8px 0;
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .platform-info {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: $text-secondary;
|
|
|
+
|
|
|
+ .platform-icon {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-stats {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(4, 1fr);
|
|
|
+ gap: 16px;
|
|
|
+ margin-bottom: 24px;
|
|
|
+
|
|
|
+ .stat-item {
|
|
|
+ background: #f8fafc;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 16px;
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ .stat-value {
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: $primary-color;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: $text-secondary;
|
|
|
+ margin-top: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-chart {
|
|
|
+ h4 {
|
|
|
+ margin: 0 0 16px 0;
|
|
|
+ font-size: 15px;
|
|
|
+ color: $text-primary;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 1400px) {
|
|
|
+ .account-analytics {
|
|
|
+ .stats-row {
|
|
|
+ grid-template-columns: repeat(4, 1fr);
|
|
|
+
|
|
|
+ .stat-card {
|
|
|
+ &:nth-child(4) {
|
|
|
+ border-right: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:nth-child(n+5) {
|
|
|
+ border-top: 1px solid #f0f0f0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 1200px) {
|
|
|
+ .account-analytics {
|
|
|
+ .filter-bar {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 12px;
|
|
|
+
|
|
|
+ .filter-left {
|
|
|
+ flex-wrap: wrap;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .stats-row {
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
+
|
|
|
+ .stat-card {
|
|
|
+ &:nth-child(3n) {
|
|
|
+ border-right: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:nth-child(n+4) {
|
|
|
+ border-top: 1px solid #f0f0f0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|