ss
Some checks failed
Deploy Website on push / Deploy Push Element Ftp (push) Waiting to run
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled

This commit is contained in:
Mrx
2026-01-30 16:03:46 +08:00
commit 62e532846e
1451 changed files with 127726 additions and 0 deletions

View File

@@ -0,0 +1,216 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { statsHistory, statsTotal } from '#/api/promotion/analytics';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);
// 获取30天前的日期
const getDateString = (daysAgo: number) => {
const date = new Date();
date.setDate(date.getDate() - daysAgo);
return date.toISOString().split('T')[0];
};
onMounted(async () => {
try {
// 获取趋势数据
const endDate = getDateString(0); // 今天
const startDate = getDateString(29); // 29天前
const trendData = await statsHistory({ start_date: startDate, end_date: endDate });
// 获取统计数据
const statsData = await statsTotal();
// 准备图表数据
const dates = Array.from({ length: 30 }).map((_, index) => {
const date = new Date();
date.setDate(date.getDate() - 29 + index);
return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
});
// 如果有历史数据,使用历史数据;否则使用模拟数据
let clickData = Array(30).fill(0);
if (trendData && trendData.length > 0) {
// 将历史数据按日期排序并映射到数组
const sortedData = trendData.sort((a, b) =>
new Date(a.stats_date).getTime() - new Date(b.stats_date).getTime()
);
sortedData.forEach((item) => {
const itemDate = new Date(item.stats_date);
const today = new Date();
const daysDiff = Math.floor((today.getTime() - itemDate.getTime()) / (1000 * 60 * 60 * 24));
if (daysDiff >= 0 && daysDiff < 30) {
// 使用实际日期索引
clickData[29 - daysDiff] = item.click_count || 0;
}
});
} else {
// 没有历史数据时,使用统计数据生成模拟数据
const todayClickCount = statsData?.today_click_count || 0;
const totalClickCount = statsData?.total_click_count || 0;
// 简单的线性分布模拟数据
for (let i = 0; i < 30; i++) {
// 最后一天使用今日数据,其他天按比例分布
if (i === 29) {
clickData[i] = todayClickCount;
} else {
// 按指数衰减模拟历史数据
clickData[i] = Math.max(0, Math.floor(todayClickCount * Math.exp(-0.05 * (29 - i))));
}
}
// 确保总和不超过总计数
const sum = clickData.reduce((a, b) => a + b, 0);
if (sum > totalClickCount && totalClickCount > 0) {
const ratio = totalClickCount / sum;
clickData = clickData.map(val => Math.floor(val * ratio));
}
}
// 计算Y轴最大值
const maxValue = Math.max(...clickData) || 10;
renderEcharts({
grid: {
bottom: 0,
containLabel: true,
left: '1%',
right: '1%',
top: '2%',
},
series: [
{
areaStyle: {},
data: clickData,
itemStyle: {
color: '#5ab1ef',
},
smooth: true,
type: 'line',
name: '推广访问量',
},
],
tooltip: {
axisPointer: {
lineStyle: {
color: '#5ab1ef',
width: 1,
},
},
trigger: 'axis',
formatter: (params: any) => {
const param = params[0];
return `${param.axisValue}<br/>${param.seriesName}: ${param.value}`;
},
},
xAxis: {
axisTick: {
show: false,
},
boundaryGap: false,
data: dates,
splitLine: {
lineStyle: {
type: 'solid',
width: 1,
},
show: true,
},
type: 'category',
},
yAxis: [
{
axisTick: {
show: false,
},
max: Math.ceil(maxValue * 1.2), // 比最大值大20%作为Y轴上限
splitArea: {
show: true,
},
splitNumber: 4,
type: 'value',
},
],
});
} catch (error) {
console.error('获取推广趋势数据失败:', error);
// 发生错误时显示默认图表
renderEcharts({
grid: {
bottom: 0,
containLabel: true,
left: '1%',
right: '1%',
top: '2%',
},
series: [
{
areaStyle: {},
data: Array(30).fill(0),
itemStyle: {
color: '#5ab1ef',
},
smooth: true,
type: 'line',
name: '推广访问量',
},
],
tooltip: {
axisPointer: {
lineStyle: {
color: '#5ab1ef',
width: 1,
},
},
trigger: 'axis',
},
xAxis: {
axisTick: {
show: false,
},
boundaryGap: false,
data: Array.from({ length: 30 }).map((_, index) => {
const date = new Date();
date.setDate(date.getDate() - 29 + index);
return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
}),
splitLine: {
lineStyle: {
type: 'solid',
width: 1,
},
show: true,
},
type: 'category',
},
yAxis: [
{
axisTick: {
show: false,
},
max: 10,
splitArea: {
show: true,
},
splitNumber: 4,
type: 'value',
},
],
});
}
});
</script>
<template>
<EchartsUI ref="chartRef" />
</template>

View File

@@ -0,0 +1,73 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);
onMounted(() => {
renderEcharts({
legend: {
bottom: 0,
data: ['渠道A', '渠道B', '渠道C', '渠道D'],
},
radar: {
indicator: [
{ name: '渠道A' },
{ name: '渠道B' },
{ name: '渠道C' },
{ name: '渠道D' },
],
radius: '60%',
splitNumber: 8,
},
series: [
{
areaStyle: {
opacity: 1,
shadowBlur: 0,
shadowColor: 'rgba(0,0,0,.2)',
shadowOffsetX: 0,
shadowOffsetY: 10,
},
data: [
{
itemStyle: { color: '#b6a2de' },
name: '渠道A',
value: [90, 50, 86, 40],
},
{
itemStyle: { color: '#5ab1ef' },
name: '渠道B',
value: [70, 75, 70, 76],
},
{
itemStyle: { color: '#67e0e3' },
name: '渠道C',
value: [60, 65, 60, 66],
},
{
itemStyle: { color: '#2ec7c9' },
name: '渠道D',
value: [50, 55, 50, 56],
},
],
itemStyle: {
borderRadius: 10,
borderWidth: 2,
},
symbolSize: 0,
type: 'radar',
},
],
tooltip: {},
});
});
</script>
<template>
<EchartsUI ref="chartRef" />
</template>

View File

@@ -0,0 +1,42 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);
onMounted(() => {
renderEcharts({
series: [
{
animationDelay() {
return Math.random() * 400;
},
animationEasing: 'exponentialInOut',
animationType: 'scale',
center: ['50%', '50%'],
color: ['#5ab1ef', '#b6a2de', '#67e0e3'],
data: [
{ name: '佣金', value: 500 },
{ name: '奖励', value: 310 },
{ name: '提现', value: 274 },
],
name: '金额占比',
radius: '80%',
roseType: 'radius',
type: 'pie',
},
],
tooltip: {
trigger: 'item',
},
});
});
</script>
<template>
<EchartsUI ref="chartRef" />
</template>

View File

@@ -0,0 +1,94 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { getOrderSourceStatistics } from '#/api/order/order';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);
const loading = ref(false);
// 获取订单来源统计数据
async function fetchOrderSourceStatistics() {
try {
loading.value = true;
const response = await getOrderSourceStatistics();
// 提取产品名称和订单数量
const data = response.items.map(item => ({
name: item.product_name,
value: item.order_count,
}));
// 如果有数据,则渲染图表
if (data && data.length > 0) {
renderEcharts({
legend: {
bottom: '2%',
left: 'center',
data: data.map(item => item.name),
},
series: [
{
animationDelay() {
return Math.random() * 100;
},
animationEasing: 'exponentialInOut',
animationType: 'scale',
avoidLabelOverlap: false,
color: [
'#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9',
'#ffb980', '#d87a80', '#8d98b3', '#e5cf0d',
'#97b552', '#95706d', '#dc69aa', '#07a2a4',
'#9a7fd1', '#588dd5', '#f5994e', '#c05050'
],
data: data,
emphasis: {
label: {
fontSize: '12',
fontWeight: 'bold',
show: true,
},
},
itemStyle: {
borderRadius: 10,
borderWidth: 2,
},
label: {
position: 'center',
show: false,
},
labelLine: {
show: false,
},
name: '订单来源',
radius: ['40%', '65%'],
type: 'pie',
},
],
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)',
},
});
}
} catch (error) {
console.error('获取订单来源统计数据失败:', error);
} finally {
loading.value = false;
}
}
onMounted(() => {
fetchOrderSourceStatistics();
});
</script>
<template>
<div v-if="loading" class="flex justify-center items-center h-64">
加载中...
</div>
<EchartsUI v-else ref="chartRef" />
</template>

View File

@@ -0,0 +1,262 @@
<script lang="ts" setup>
import type { EchartsUIType } from '@vben/plugins/echarts';
import { onMounted, ref, watch } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { Button } from 'ant-design-vue';
import { getOrderStatistics } from '#/api/order/order-statistics';
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);
// 时间维度状态
const timeDimension = ref<'day' | 'month' | 'year' | 'all'>('day');
// 获取订单统计数据
async function fetchOrderStatistics() {
try {
console.log('Fetching order statistics with dimension:', timeDimension.value);
// 使用后端API获取数据
const response = await getOrderStatistics(timeDimension.value);
console.log('Order statistics response:', response);
let items = response.items || [];
// 如果后端返回空数据,显示空状态
if (items.length === 0) {
console.log('No data from backend, showing empty chart');
items = [];
}
console.log('Items for chart:', items);
// 按日期排序
items.sort((a: any, b: any) => a.date.localeCompare(b.date));
// 提取日期和数量
const dates = items.map((item: any) => {
// 根据时间维度格式化日期显示
const date = new Date(item.date);
if (timeDimension.value === 'year') {
return `${date.getFullYear()}`;
} else if (timeDimension.value === 'month') {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
} else {
return `${date.getMonth() + 1}-${String(date.getDate()).padStart(2, '0')}`;
}
});
const orderData = items.map((item: any) => item.count);
const amountData = items.map((item: any) => item.amount);
// 计算Y轴最大值
const maxOrderValue = Math.max(...orderData) || 10;
const maxAmountValue = Math.max(...amountData) || 10;
renderEcharts({
grid: {
bottom: 0,
containLabel: true,
left: '1%',
right: '1%',
top: '10%',
},
legend: {
data: ['订单数', '订单金额'],
top: 0,
},
series: [
{
data: orderData,
type: 'line',
name: '订单数',
smooth: true,
itemStyle: {
color: '#4f9cff',
},
areaStyle: {
opacity: 0.3,
color: '#4f9cff',
},
},
{
data: amountData,
type: 'line',
name: '订单金额',
smooth: true,
itemStyle: {
color: '#52c41a',
},
areaStyle: {
opacity: 0.3,
color: '#52c41a',
},
},
],
tooltip: {
axisPointer: {
lineStyle: {
width: 1,
},
},
trigger: 'axis',
formatter: (params: any) => {
let result = `${params[0].axisValue}<br/>`;
params.forEach((param: any) => {
if (param.seriesName === '订单金额') {
result += `${param.seriesName}: ¥${param.value.toFixed(2)}<br/>`;
} else {
result += `${param.seriesName}: ${param.value}<br/>`;
}
});
return result;
},
},
xAxis: {
data: dates,
type: 'category',
boundaryGap: false,
},
yAxis: [
{
type: 'value',
name: '订单数',
position: 'left',
max: Math.ceil(maxOrderValue * 1.2),
splitNumber: 4,
},
{
type: 'value',
name: '金额(¥)',
position: 'right',
max: Math.ceil(maxAmountValue * 1.2),
splitNumber: 4,
},
],
});
} catch (error) {
console.error('获取订单趋势数据失败:', error);
// 发生错误时显示默认图表
renderEcharts({
grid: {
bottom: 0,
containLabel: true,
left: '1%',
right: '1%',
top: '10%',
},
legend: {
data: ['订单数', '订单金额'],
top: 0,
},
series: [
{
data: Array(30).fill(0),
type: 'line',
name: '订单数',
smooth: true,
itemStyle: {
color: '#4f9cff',
},
areaStyle: {
opacity: 0.3,
color: '#4f9cff',
},
},
{
data: Array(30).fill(0),
type: 'line',
name: '订单金额',
smooth: true,
itemStyle: {
color: '#52c41a',
},
areaStyle: {
opacity: 0.3,
color: '#52c41a',
},
},
],
tooltip: {
axisPointer: {
lineStyle: {
width: 1,
},
},
trigger: 'axis',
},
xAxis: {
data: Array.from({ length: 30 }).map((_, index) => {
const date = new Date();
date.setDate(date.getDate() - 29 + index);
return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' });
}),
type: 'category',
boundaryGap: false,
},
yAxis: [
{
type: 'value',
name: '订单数',
position: 'left',
max: 10,
splitNumber: 4,
},
{
type: 'value',
name: '金额(¥)',
position: 'right',
max: 10,
splitNumber: 4,
},
],
});
}
}
// 组件挂载时获取数据
onMounted(() => {
fetchOrderStatistics();
});
// 监听时间维度变化
watch(timeDimension, () => {
fetchOrderStatistics();
});
</script>
<template>
<div>
<div class="mb-4 flex justify-end space-x-2">
<Button
:type="timeDimension === 'day' ? 'primary' : 'default'"
@click="timeDimension = 'day'"
>
</Button>
<Button
:type="timeDimension === 'month' ? 'primary' : 'default'"
@click="timeDimension = 'month'"
>
</Button>
<Button
:type="timeDimension === 'year' ? 'primary' : 'default'"
@click="timeDimension = 'year'"
>
</Button>
<Button
:type="timeDimension === 'all' ? 'primary' : 'default'"
@click="timeDimension = 'all'"
>
全部
</Button>
</div>
<EchartsUI ref="chartRef" />
</div>
</template>

View File

@@ -0,0 +1,255 @@
<script lang="ts" setup>
import type { AnalysisOverviewItem } from '@vben/common-ui';
import type { TabOption } from '@vben/types';
import {
AnalysisChartCard,
AnalysisChartsTabs,
AnalysisOverview,
} from '@vben/common-ui';
import {
SvgBellIcon,
SvgCakeIcon,
SvgCardIcon,
SvgDownloadIcon,
} from '@vben/icons';
import { onMounted, ref } from 'vue';
import AnalyticsTrends from './analytics-trends.vue';
import AnalyticsVisitsData from './analytics-visits-data.vue';
import AnalyticsVisitsSales from './analytics-visits-sales.vue';
import AnalyticsVisitsSource from './analytics-visits-source.vue';
import AnalyticsVisits from './analytics-visits.vue';
import { getAgentStatistics, getWithdrawalStatistics, getAgentOrderStatistics } from '#/api/agent';
import { getOrderList, getRefundStatistics, getIncomeStatistics } from '#/api/order/order';
import { getPlatformUserList } from '#/api/platform-user';
// 初始化概览数据
const overviewItems = ref<AnalysisOverviewItem[]>([
{
icon: SvgCardIcon,
title: '总用户数',
value: 0,
todaytitle: '今日新增用户数',
todayValue: 0,
Subtitle: '总代理数',
SubValue: 0,
todaySubtitle: '今日新增代理数',
todaySubValue: 0,
},
{
icon: SvgCakeIcon,
title: '总订单数',
value: 0,
todaytitle: '今日新增订单数',
todayValue: 0,
Subtitle: '代理总订单量',
SubValue: 0,
todaySubtitle: '今日新增代理订单量',
todaySubValue: 0,
},
{
icon: SvgDownloadIcon,
title: '总收入流水金额',
value: 0,
todaytitle: '今日新增收入流水金额',
todayValue: 0,
Subtitle: '总利润',
SubValue: 0,
todaySubtitle: '今日新增利润',
todaySubValue: 0,
},
{
icon: SvgBellIcon,
title: '总提现金额',
value: 0,
SubValue: 0,
todaySubtitle: '总实际到账金额',
todaySubValue: 0,
extraTitle: '总扣税金额',
extraValue: 0,
todaytitle: '今日新增提现金额',
todayValue: 0,
Subtitle: '总退款金额',
extra2Title: '今日新增退款金额',
extra2Value: 0,
},
]);
const chartTabs: TabOption[] = [
{
label: '订单趋势',
value: 'visits',
},
{
label: '推广访问趋势',
value: 'trends',
},
];
// 获取统计数据
async function fetchStatistics() {
try {
// 获取今日的开始和结束时间
const today = new Date();
// 将时间格式化为后端期望的格式 (YYYY-MM-DD HH:MM:SS)
const startTime = new Date(today.getFullYear(), today.getMonth(), today.getDate()).toISOString().replace('T', ' ').substring(0, 19);
const endTime = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1).toISOString().replace('T', ' ').substring(0, 19);
// 获取平台用户数据(总数)
const platformUserResponse = await getPlatformUserList({ page: 1, pageSize: 1 });
const platformUserTotal = platformUserResponse.total || 0;
// 获取今日新增用户数
// 由于平台用户API不支持时间过滤我们需要获取更多数据并在前端过滤
const newUserResponse = await getPlatformUserList({ page: 1, pageSize: 1000 });
const newUserCount = newUserResponse.items?.filter(user => {
const userCreateTime = new Date(user.create_time);
return userCreateTime >= new Date(startTime) && userCreateTime < new Date(endTime);
}).length || 0;
// 获取订单数据
const orderResponse = await getOrderList({ page: 1, pageSize: 1 });
const orderTotal = orderResponse.total || 0;
// 获取代理订单数据
const agentOrderResponse = await getAgentOrderStatistics();
const agentOrderTotal = agentOrderResponse.total_agent_order_count || 0;
// 获取今日新增订单数
const todayOrderResponse = await getOrderList({
page: 1,
pageSize: 1000,
create_time_start: startTime,
create_time_end: endTime
});
const todayOrderTotal = todayOrderResponse.total || 0;
// 获取今日新增代理订单数
const todayAgentOrderResponse = await getAgentOrderStatistics();
const todayAgentOrderTotal = todayAgentOrderResponse.today_agent_order_count || 0;
// Product data is no longer needed for order statistics
// 获取代理统计数据
const agentStatsResponse = await getAgentStatistics();
const agentTotal = agentStatsResponse.total_agent_count || 0;
const newAgentCount = agentStatsResponse.today_agent_count || 0;
// 获取提现统计数据
const withdrawalStatsResponse = await getWithdrawalStatistics();
const totalWithdrawalAmount = withdrawalStatsResponse.total_withdrawal_amount || 0;
const todayWithdrawalAmount = withdrawalStatsResponse.today_withdrawal_amount || 0;
const totalActualAmount = withdrawalStatsResponse.total_actual_amount || 0;
const totalTaxAmount = withdrawalStatsResponse.total_tax_amount || 0;
// 获取退款统计数据
const refundStatsResponse = await getRefundStatistics();
const totalRefundAmount = refundStatsResponse.total_refund_amount || 0;
const todayRefundAmount = refundStatsResponse.today_refund_amount || 0;
// 获取收入统计数据
const incomeStatsResponse = await getIncomeStatistics();
const totalIncome = incomeStatsResponse.total_revenue_amount || 0;
const todayIncome = incomeStatsResponse.today_revenue_amount || 0;
const totalProfit = incomeStatsResponse.total_profit_amount || 0;
const todayProfit = incomeStatsResponse.today_profit_amount || 0;
// 更新概览数据
overviewItems.value = [
{
icon: SvgCardIcon,
title: '总用户数',
value: platformUserTotal,
todaytitle: '今日新增用户数',
todayValue: newUserCount,
Subtitle: '总代理数',
SubValue: agentTotal,
todaySubtitle: '今日新增代理数',
todaySubValue: newAgentCount,
},
{
icon: SvgCakeIcon,
title: '总订单数',
value: orderTotal,
todaytitle: '今日新增订单数',
todayValue: todayOrderTotal,
Subtitle: '总代理订单量',
SubValue: agentOrderTotal,
todaySubtitle: '今日新增代理订单量',
todaySubValue: todayAgentOrderTotal,
},
{
icon: SvgDownloadIcon,
title: '总收入流水金额',
value: totalIncome,
todaytitle: '今日新增收入流水金额',
todayValue: todayIncome,
Subtitle: '总利润',
SubValue: totalProfit,
todaySubtitle: '今日新增利润',
todaySubValue: todayProfit,
},
{
icon: SvgBellIcon,
title: '总提现金额',
value: totalWithdrawalAmount,
todaytitle: '今日新增提现金额',
todayValue: todayWithdrawalAmount,
extra2Title: '今日新增退款金额',
extra2Value: todayRefundAmount,
Subtitle: '总退款金额',
SubValue: totalRefundAmount,
todaySubtitle: '总实际到账金额',
todaySubValue: totalActualAmount,
extraTitle: '总扣税金额',
extraValue: totalTaxAmount,
},
];
} catch (error) {
console.error('获取统计数据失败:', error);
}
}
// 组件挂载时获取数据
onMounted(() => {
fetchStatistics();
});
</script>
<template>
<div class="p-5">
<AnalysisOverview :items="overviewItems" />
<AnalysisChartsTabs :tabs="chartTabs" class="mt-5">
<template #visits>
<AnalyticsVisits />
</template>
<template #trends>
<AnalyticsTrends />
</template>
</AnalysisChartsTabs>
<div class="mt-5 w-full md:flex">
<AnalysisChartCard
class="mt-5 md:mr-4 md:mt-0 md:w-1/3"
title="推广数据分析"
>
<AnalyticsVisitsData />
</AnalysisChartCard>
<AnalysisChartCard
class="mt-5 md:mr-4 md:mt-0 md:w-1/3"
title="订单来源分析"
>
<AnalyticsVisitsSource />
</AnalysisChartCard>
<AnalysisChartCard
class="mt-5 md:mt-0 md:w-1/3"
title="佣金/奖励/提现统计"
>
<AnalyticsVisitsSales />
</AnalysisChartCard>
</div>
</div>
</template>

View File

@@ -0,0 +1,266 @@
<script lang="ts" setup>
import type {
WorkbenchProjectItem,
WorkbenchQuickNavItem,
WorkbenchTodoItem,
WorkbenchTrendItem,
} from '@vben/common-ui';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import {
AnalysisChartCard,
WorkbenchHeader,
WorkbenchProject,
WorkbenchQuickNav,
WorkbenchTodo,
WorkbenchTrends,
} from '@vben/common-ui';
import { preferences } from '@vben/preferences';
import { useUserStore } from '@vben/stores';
import { openWindow } from '@vben/utils';
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
const userStore = useUserStore();
// 这是一个示例数据,实际项目中需要根据实际情况进行调整
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
// 例如url: /dashboard/workspace
const projectItems: WorkbenchProjectItem[] = [
{
color: '',
content: '不要等待机会,而要创造机会。',
date: '2021-04-01',
group: '开源组',
icon: 'carbon:logo-github',
title: 'Github',
url: 'https://github.com',
},
{
color: '#3fb27f',
content: '现在的你决定将来的你。',
date: '2021-04-01',
group: '算法组',
icon: 'ion:logo-vue',
title: 'Vue',
url: 'https://vuejs.org',
},
{
color: '#e18525',
content: '没有什么才能比努力更重要。',
date: '2021-04-01',
group: '上班摸鱼',
icon: 'ion:logo-html5',
title: 'Html5',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
},
{
color: '#bf0c2c',
content: '热情和欲望可以突破一切难关。',
date: '2021-04-01',
group: 'UI',
icon: 'ion:logo-angular',
title: 'Angular',
url: 'https://angular.io',
},
{
color: '#00d8ff',
content: '健康的身体是实现目标的基石。',
date: '2021-04-01',
group: '技术牛',
icon: 'bx:bxl-react',
title: 'React',
url: 'https://reactjs.org',
},
{
color: '#EBD94E',
content: '路是走出来的,而不是空想出来的。',
date: '2021-04-01',
group: '架构组',
icon: 'ion:logo-javascript',
title: 'Js',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
},
];
// 同样,这里的 url 也可以使用以 http 开头的外部链接
const quickNavItems: WorkbenchQuickNavItem[] = [
{
color: '#1fdaca',
icon: 'ion:home-outline',
title: '首页',
url: '/',
},
{
color: '#bf0c2c',
icon: 'ion:grid-outline',
title: '仪表盘',
url: '/dashboard',
},
{
color: '#e18525',
icon: 'ion:layers-outline',
title: '组件',
url: '/demos/features/icons',
},
{
color: '#3fb27f',
icon: 'ion:settings-outline',
title: '系统管理',
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
},
{
color: '#4daf1bc9',
icon: 'ion:key-outline',
title: '权限管理',
url: '/demos/access/page-control',
},
{
color: '#00d8ff',
icon: 'ion:bar-chart-outline',
title: '图表',
url: '/analytics',
},
];
const todoItems = ref<WorkbenchTodoItem[]>([
{
completed: false,
content: `审查最近提交到Git仓库的前端代码确保代码质量和规范。`,
date: '2024-07-30 11:00:00',
title: '审查前端代码提交',
},
{
completed: true,
content: `检查并优化系统性能降低CPU使用率。`,
date: '2024-07-30 11:00:00',
title: '系统性能优化',
},
{
completed: false,
content: `进行系统安全检查,确保没有安全漏洞或未授权的访问。 `,
date: '2024-07-30 11:00:00',
title: '安全检查',
},
{
completed: false,
content: `更新项目中的所有npm依赖包确保使用最新版本。`,
date: '2024-07-30 11:00:00',
title: '更新项目依赖',
},
{
completed: false,
content: `修复用户报告的页面UI显示问题确保在不同浏览器中显示一致。 `,
date: '2024-07-30 11:00:00',
title: '修复UI显示问题',
},
]);
const trendItems: WorkbenchTrendItem[] = [
{
avatar: 'svg:avatar-1',
content: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`,
date: '刚刚',
title: '威廉',
},
{
avatar: 'svg:avatar-2',
content: `关注了 <a>威廉</a> `,
date: '1个小时前',
title: '艾文',
},
{
avatar: 'svg:avatar-3',
content: `发布了 <a>个人动态</a> `,
date: '1天前',
title: '克里斯',
},
{
avatar: 'svg:avatar-4',
content: `发表文章 <a>如何编写一个Vite插件</a> `,
date: '2天前',
title: 'Vben',
},
{
avatar: 'svg:avatar-1',
content: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化?</a>`,
date: '3天前',
title: '皮特',
},
{
avatar: 'svg:avatar-2',
content: `关闭了问题 <a>如何运行项目</a> `,
date: '1周前',
title: '杰克',
},
{
avatar: 'svg:avatar-3',
content: `发布了 <a>个人动态</a> `,
date: '1周前',
title: '威廉',
},
{
avatar: 'svg:avatar-4',
content: `推送了代码到 <a>Github</a>`,
date: '2021-04-01 20:00',
title: '威廉',
},
{
avatar: 'svg:avatar-4',
content: `发表文章 <a>如何编写使用 Admin Vben</a> `,
date: '2021-03-01 20:00',
title: 'Vben',
},
];
const router = useRouter();
// 这是一个示例方法,实际项目中需要根据实际情况进行调整
// This is a sample method, adjust according to the actual project requirements
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
if (nav.url?.startsWith('http')) {
openWindow(nav.url);
return;
}
if (nav.url?.startsWith('/')) {
router.push(nav.url).catch((error) => {
console.error('Navigation failed:', error);
});
} else {
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
}
}
</script>
<template>
<div class="p-5">
<WorkbenchHeader
:avatar="userStore.userInfo?.avatar || preferences.app.defaultAvatar"
>
<template #title>
早安, {{ userStore.userInfo?.realName }}, 开始您一天的工作吧
</template>
<template #description> 今日晴20 - 32 </template>
</WorkbenchHeader>
<div class="mt-5 flex flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-3/5">
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
</div>
<div class="w-full lg:w-2/5">
<WorkbenchQuickNav
:items="quickNavItems"
class="mt-5 lg:mt-0"
title="快捷导航"
@click="navTo"
/>
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
<AnalysisChartCard class="mt-5" title="访问来源">
<AnalyticsVisitsSource />
</AnalysisChartCard>
</div>
</div>
</div>
</template>