|
@@ -1,33 +1,41 @@
|
|
|
<!-- 微信私域运营数据看板页面 -->
|
|
|
<template>
|
|
|
- <PageWrapper class="page-wrapper">
|
|
|
+ <PageWrapper class="dashboard">
|
|
|
<!-- 页面头部内容 -->
|
|
|
<template #headerContent>
|
|
|
<Flex justify="space-between" align="center">
|
|
|
- <h1 class="title">微信私域用户运营数据看板</h1>
|
|
|
+ <h1 class="dashboard__title">微信私域用户运营数据看板</h1>
|
|
|
<Space>
|
|
|
<!-- 快捷日期选择按钮组 -->
|
|
|
- <div>
|
|
|
- <a-button type="link" size="small" @click="toDate(7)">近7天</a-button>
|
|
|
- <a-button type="link" size="small" @click="toDate(1)">昨天</a-button>
|
|
|
- <a-button type="link" size="small" @click="toDate(0)">今天</a-button>
|
|
|
+ <div class="dashboard__date-shortcuts">
|
|
|
+ <a-button
|
|
|
+ v-for="item in DATE_SHORTCUTS"
|
|
|
+ :key="item.days"
|
|
|
+ type="link"
|
|
|
+ size="small"
|
|
|
+ @click="handleDateShortcut(item.days)"
|
|
|
+ >
|
|
|
+ {{ item.label }}
|
|
|
+ </a-button>
|
|
|
</div>
|
|
|
+
|
|
|
<!-- 日期范围选择器 -->
|
|
|
<RangePicker
|
|
|
v-model:value="date"
|
|
|
size="small"
|
|
|
:allowClear="false"
|
|
|
inputReadOnly
|
|
|
- @change="fetchData"
|
|
|
+ @change="handleDateChange"
|
|
|
/>
|
|
|
+
|
|
|
<!-- 超级管理员可见的组织选择器 -->
|
|
|
<template v-if="isSuper">
|
|
|
<UserOutlined />
|
|
|
<Select
|
|
|
+ v-model:value="organizationId"
|
|
|
size="small"
|
|
|
style="width: 100px"
|
|
|
- v-model:value="organizationId"
|
|
|
- @change="fetchData"
|
|
|
+ @change="handleOrgChange"
|
|
|
>
|
|
|
<SelectOption :value="0">所有账号</SelectOption>
|
|
|
<SelectOption v-for="item in departmentList" :key="item.id" :value="item.id">
|
|
@@ -38,13 +46,13 @@
|
|
|
</Space>
|
|
|
</Flex>
|
|
|
</template>
|
|
|
- <!-- 数据加载状态 -->
|
|
|
+
|
|
|
+ <!-- 数据展示区域 -->
|
|
|
<Spin :spinning="loading" size="large">
|
|
|
- <!-- 图表展示区域 -->
|
|
|
<Space v-if="charts" direction="vertical" :size="32">
|
|
|
- <!-- 第一行:数据概览卡片 -->
|
|
|
+ <!-- 数据概览卡片 -->
|
|
|
<Row :gutter="[32, 32]">
|
|
|
- <Col :span="6" v-for="item in chartsAreaLineMap" :key="item.key">
|
|
|
+ <Col v-for="item in CHART_CONFIG.AREA_LINE_MAP" :key="item.key" :span="6">
|
|
|
<ChartsAreaLine
|
|
|
:label="item.label"
|
|
|
:rate="charts[item.key].rate"
|
|
@@ -54,30 +62,32 @@
|
|
|
/>
|
|
|
</Col>
|
|
|
</Row>
|
|
|
- <!-- 第二行:趋势图表 -->
|
|
|
+
|
|
|
+ <!-- 趋势图表 -->
|
|
|
<Row :gutter="32">
|
|
|
<Col :span="12">
|
|
|
<ChartsLine
|
|
|
title="Token 消耗趋势"
|
|
|
- :labels="charts.consume_token.label"
|
|
|
- :data="charts.consume_token.val"
|
|
|
+ :labels="charts.consume_token.label || []"
|
|
|
+ :data="charts.consume_token.val || []"
|
|
|
/>
|
|
|
</Col>
|
|
|
<Col :span="12">
|
|
|
<ChartsLine
|
|
|
title="用户增长趋势"
|
|
|
- :labels="charts.new_user.label"
|
|
|
- :data="charts.new_user.val"
|
|
|
+ :labels="charts.new_user.label || []"
|
|
|
+ :data="charts.new_user.val || []"
|
|
|
/>
|
|
|
</Col>
|
|
|
</Row>
|
|
|
- <!-- 第三行:标签分布和账号分析 -->
|
|
|
+
|
|
|
+ <!-- 标签分布和账号分析 -->
|
|
|
<Row :gutter="32">
|
|
|
<Col :span="12">
|
|
|
<ChartsPie title="标签分布" :data="charts.label_dist" />
|
|
|
</Col>
|
|
|
<Col :span="12">
|
|
|
- <TableList title="账号分析" :columns="columns" :data="tableData" />
|
|
|
+ <TableList title="账号分析" :columns="TABLE_COLUMNS" :data="tableData" />
|
|
|
</Col>
|
|
|
</Row>
|
|
|
</Space>
|
|
@@ -86,91 +96,124 @@
|
|
|
</template>
|
|
|
|
|
|
<script lang="ts" setup>
|
|
|
- // 导入所需的组件和工具
|
|
|
- import { onMounted, ref } from 'vue';
|
|
|
- import { PageWrapper } from '@/components/Page';
|
|
|
+ // Vue 核心
|
|
|
+ import { onMounted, ref, computed } from 'vue';
|
|
|
+
|
|
|
+ // 第三方组件
|
|
|
import { Flex, Row, Col, Space, Select, SelectOption, RangePicker, Spin } from 'ant-design-vue';
|
|
|
import { UserOutlined } from '@ant-design/icons-vue';
|
|
|
+ import dayjs, { Dayjs } from 'dayjs';
|
|
|
+
|
|
|
+ // 自定义组件
|
|
|
+ import { PageWrapper } from '@/components/Page';
|
|
|
import ChartsAreaLine from './components/ChartsAreaLine.vue';
|
|
|
import ChartsLine from './components/ChartsLine.vue';
|
|
|
import ChartsPie from './components/ChartsPie.vue';
|
|
|
import TableList from './components/TableList.vue';
|
|
|
- import dayjs from 'dayjs';
|
|
|
- import { getDashboardCharts, getDashboardWxList } from '/@/api/dashboard/dashboard';
|
|
|
+
|
|
|
+ // API 和工具
|
|
|
+ import { getDashboardCharts, getDashboardWxList } from '@/api/dashboard/dashboard';
|
|
|
import { getDepartmentList } from '@/api/sys/department';
|
|
|
- import { useUserStore } from '/@/store/modules/user';
|
|
|
- import { DepartmentInfo } from '/@/api/sys/model/departmentModel';
|
|
|
+ import { useUserStore } from '@/store/modules/user';
|
|
|
+ import type { DepartmentInfo } from '@/api/sys/model/departmentModel';
|
|
|
|
|
|
- // 用户信息和权限判断
|
|
|
- const userStore = useUserStore();
|
|
|
- const userInfo = userStore.getUserInfo;
|
|
|
- const isSuper = userInfo.roleName.some((str) => str == '超级管理员');
|
|
|
+ // 类型定义
|
|
|
+ interface ChartData {
|
|
|
+ rate: number;
|
|
|
+ count: number;
|
|
|
+ label: string[] | null;
|
|
|
+ val: number[] | null;
|
|
|
+ }
|
|
|
|
|
|
- // 页面状态管理
|
|
|
- const loading = ref(false);
|
|
|
- const organizationId = ref(0);
|
|
|
- const departmentList = ref<DepartmentInfo[]>([]);
|
|
|
- const charts = ref();
|
|
|
- const date = ref<[dayjs.Dayjs, dayjs.Dayjs]>();
|
|
|
- const tableData = ref<object[]>([]);
|
|
|
-
|
|
|
- // 图表配置数据
|
|
|
- const chartsAreaLineMap = [
|
|
|
- { label: 'AI 回复次数', key: 'ai_response' },
|
|
|
- { label: 'SOP执行次数', key: 'sop_run' },
|
|
|
- { label: '总好友数', key: 'total_friend' },
|
|
|
- { label: '总群数', key: 'total_group' },
|
|
|
- { label: '账户余额', key: 'account_balance' },
|
|
|
- { label: 'Token消耗', key: 'consume_token' },
|
|
|
- { label: '活跃用户', key: 'active_user' },
|
|
|
- { label: '新增好友', key: 'new_user' },
|
|
|
- ];
|
|
|
+ interface DashboardCharts {
|
|
|
+ ai_response: ChartData;
|
|
|
+ sop_run: ChartData;
|
|
|
+ total_friend: ChartData;
|
|
|
+ total_group: ChartData;
|
|
|
+ account_balance: ChartData;
|
|
|
+ consume_token: ChartData;
|
|
|
+ active_user: ChartData;
|
|
|
+ new_user: ChartData;
|
|
|
+ label_dist: any[];
|
|
|
+ }
|
|
|
+
|
|
|
+ interface TableRecord {
|
|
|
+ nickname: string;
|
|
|
+ total_friend: number;
|
|
|
+ total_group: number;
|
|
|
+ interaction_rate: number;
|
|
|
+ }
|
|
|
|
|
|
- // 表格列配置
|
|
|
- const columns = [
|
|
|
+ // 常量配置
|
|
|
+ const CHART_CONFIG = {
|
|
|
+ AREA_LINE_MAP: [
|
|
|
+ { label: 'AI 回复次数', key: 'ai_response' },
|
|
|
+ { label: 'SOP执行次数', key: 'sop_run' },
|
|
|
+ { label: '总好友数', key: 'total_friend' },
|
|
|
+ { label: '总群数', key: 'total_group' },
|
|
|
+ { label: '账户余额', key: 'account_balance' },
|
|
|
+ { label: 'Token消耗', key: 'consume_token' },
|
|
|
+ { label: '活跃用户', key: 'active_user' },
|
|
|
+ { label: '新增好友', key: 'new_user' },
|
|
|
+ ],
|
|
|
+ };
|
|
|
+
|
|
|
+ const TABLE_COLUMNS = [
|
|
|
{ title: '账号', dataIndex: 'nickname' },
|
|
|
{ title: '好友数', dataIndex: 'total_friend' },
|
|
|
{ title: '群数量', dataIndex: 'total_group' },
|
|
|
{ title: '互动率', dataIndex: 'interaction_rate' },
|
|
|
];
|
|
|
|
|
|
- // 页面初始化
|
|
|
- onMounted(async () => {
|
|
|
- toDate(0); // 默认显示今天的数据
|
|
|
- if (isSuper) {
|
|
|
- // 如果是超级管理员,加载部门列表
|
|
|
- const res = await getDepartmentList({ page: 1, pageSize: 100 });
|
|
|
- departmentList.value = res.data.data;
|
|
|
- }
|
|
|
+ const DATE_SHORTCUTS = [
|
|
|
+ { label: '近7天', days: 7 },
|
|
|
+ { label: '昨天', days: 1 },
|
|
|
+ { label: '今天', days: 0 },
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 组件逻辑
|
|
|
+ const userStore = useUserStore();
|
|
|
+ const userInfo = userStore.getUserInfo;
|
|
|
+ const isSuper = computed(() => userInfo.roleName.some((str) => str === '超级管理员'));
|
|
|
+
|
|
|
+ // 状态管理
|
|
|
+ const loading = ref<boolean>(false);
|
|
|
+ const organizationId = ref<number>(0);
|
|
|
+ const departmentList = ref<DepartmentInfo[]>([]);
|
|
|
+ const charts = ref<DashboardCharts | null>(null);
|
|
|
+ const date = ref<[Dayjs, Dayjs]>();
|
|
|
+ const tableData = ref<TableRecord[]>([]);
|
|
|
+
|
|
|
+ // 计算属性
|
|
|
+ computed(() => {
|
|
|
+ if (!date.value?.length) return null;
|
|
|
+ return {
|
|
|
+ start_date: date.value[0].format('YYYY-MM-DD'),
|
|
|
+ end_date: date.value[1].format('YYYY-MM-DD'),
|
|
|
+ };
|
|
|
});
|
|
|
|
|
|
- // 获取图表数据
|
|
|
+ // 方法定义
|
|
|
async function fetchCharts() {
|
|
|
try {
|
|
|
loading.value = true;
|
|
|
- let start_date = '';
|
|
|
- let end_date = '';
|
|
|
- if (date.value && date.value.length) {
|
|
|
- [start_date, end_date] = date.value.map((o) => o.format('YYYY-MM-DD'));
|
|
|
- }
|
|
|
- const res = await getDashboardCharts({
|
|
|
- start_date,
|
|
|
- end_date,
|
|
|
+ const params = {
|
|
|
organizationId: organizationId.value,
|
|
|
- });
|
|
|
+ start_date: date.value?.[0].format('YYYY-MM-DD') || dayjs().format('YYYY-MM-DD'),
|
|
|
+ end_date: date.value?.[1].format('YYYY-MM-DD') || dayjs().format('YYYY-MM-DD'),
|
|
|
+ };
|
|
|
+ const res = await getDashboardCharts(params);
|
|
|
charts.value = res.data;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取图表数据失败:', error);
|
|
|
} finally {
|
|
|
loading.value = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 获取账号列表数据
|
|
|
async function fetchList() {
|
|
|
try {
|
|
|
- let end_date = dayjs().format('YYYY-MM-DD');
|
|
|
- if (date.value && date.value.length) {
|
|
|
- end_date = date.value[1].format('YYYY-MM-DD');
|
|
|
- }
|
|
|
+ const end_date = date.value?.[1].format('YYYY-MM-DD') || dayjs().format('YYYY-MM-DD');
|
|
|
const res = await getDashboardWxList({
|
|
|
page: 1,
|
|
|
pageSize: 1000,
|
|
@@ -179,30 +222,59 @@
|
|
|
});
|
|
|
tableData.value = res.data.data;
|
|
|
} catch (error) {
|
|
|
- console.error(error);
|
|
|
+ console.error('获取列表数据失败:', error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 刷新所有数据
|
|
|
+ function handleDateShortcut(days: number) {
|
|
|
+ date.value = [dayjs().subtract(days, 'day'), dayjs()];
|
|
|
+ fetchData();
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleDateChange() {
|
|
|
+ fetchData();
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleOrgChange() {
|
|
|
+ fetchData();
|
|
|
+ }
|
|
|
+
|
|
|
function fetchData() {
|
|
|
fetchCharts();
|
|
|
fetchList();
|
|
|
}
|
|
|
|
|
|
- // 快捷设置日期范围
|
|
|
- function toDate(day) {
|
|
|
- date.value = [dayjs().subtract(day, 'day'), dayjs()];
|
|
|
- fetchData();
|
|
|
- }
|
|
|
+ // 生命周期
|
|
|
+ onMounted(async () => {
|
|
|
+ handleDateShortcut(0); // 默认显示今天的数据
|
|
|
+
|
|
|
+ if (isSuper.value) {
|
|
|
+ try {
|
|
|
+ const res = await getDepartmentList({ page: 1, pageSize: 100 });
|
|
|
+ departmentList.value = res.data.data;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取部门列表失败:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
- // 页面样式
|
|
|
- .page-wrapper {
|
|
|
- background: #fff;
|
|
|
- }
|
|
|
+ .dashboard {
|
|
|
+ &__title {
|
|
|
+ margin: 0;
|
|
|
+ color: #000000d9;
|
|
|
+ font-size: 1.5em;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
|
|
|
- .title {
|
|
|
- font-size: 1.5em;
|
|
|
+ &__date-shortcuts {
|
|
|
+ display: inline-flex;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.ant-spin-container) {
|
|
|
+ padding: 24px;
|
|
|
+ }
|
|
|
}
|
|
|
</style>
|