feat: 添加首页数据展示

This commit is contained in:
皓月归尘 2025-02-18 04:11:56 +08:00
parent d8c2d1db4b
commit f039e020e1
3 changed files with 363 additions and 135 deletions

View File

@ -123,3 +123,34 @@ export const postUploadAvatarAPI = (id: string, data: { file: Blob }) => {
data data
}); });
}; };
/**用户查询统计结果 */
interface UserQueryStatistics {
/**今日统计 */
today_count: number;
/**当月统计 */
this_month_count: number;
/**上月统计 */
last_month_count: number;
/**最近14天内成功查询统计 */
before_14day_count_success: {
/**日期 */
date: string;
/**查询次数 */
count: number;
}[];
/**最近14天内失败查询统计 */
before_14day_count_fail: {
/**日期 */
date: string;
/**查询次数 */
count: number;
}[];
}
/**
*
*/
export const getUserQueryStatisticsAPI = () => {
return http.request<UserQueryStatistics>("get", "/api/user/statistics");
};

View File

@ -3,11 +3,15 @@ import { useDark, useECharts } from "@pureadmin/utils";
import { type PropType, ref, computed, watch, nextTick } from "vue"; import { type PropType, ref, computed, watch, nextTick } from "vue";
const props = defineProps({ const props = defineProps({
requireData: { dates: {
type: Array as PropType<Array<string>>,
default: () => []
},
successData: {
type: Array as PropType<Array<number>>, type: Array as PropType<Array<number>>,
default: () => [] default: () => []
}, },
questionData: { failData: {
type: Array as PropType<Array<number>>, type: Array as PropType<Array<number>>,
default: () => [] default: () => []
} }
@ -28,70 +32,89 @@ watch(
await nextTick(); // DOM await nextTick(); // DOM
setOptions({ setOptions({
container: ".bar-card", container: ".bar-card",
color: ["#41b6ff", "#e85f33"], color: ["#28a745", "#dc3545"], // 绿
title: {
text: "近14天内查询统计",
left: "center", //
top: 10, //
textStyle: {
fontSize: 16,
fontWeight: "bold",
color: "#333"
}
},
tooltip: { tooltip: {
trigger: "axis", trigger: "axis",
axisPointer: { axisPointer: {
type: "none" type: "line"
} }
}, },
grid: { grid: {
top: "20px", top: "60px", //
left: "50px", left: "50px",
right: 0 right: "20px",
bottom: "50px"
}, },
legend: { legend: {
data: ["查询数量", "匹配数量"], data: ["成功查询", "失败查询"],
textStyle: { textStyle: {
color: "#606266", color: "#606266",
fontSize: "0.875rem" fontSize: "0.875rem"
}, },
bottom: 0 top: 10, //
right: 20 //
}, },
xAxis: [ xAxis: {
{
type: "category", type: "category",
data: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"], data: props.dates, //
axisLabel: { axisLabel: {
fontSize: "0.875rem" fontSize: "0.875rem",
rotate: 30 //
}, },
axisPointer: { axisPointer: {
type: "shadow" type: "shadow"
} }
} },
], yAxis: {
yAxis: [
{
type: "value", type: "value",
axisLabel: { axisLabel: {
fontSize: "0.875rem" fontSize: "0.875rem"
}, },
splitLine: { splitLine: {
show: false // 线 show: true,
lineStyle: {
type: "dashed"
} }
// name: ": "
} }
], },
series: [ series: [
{ {
name: "查询数量", name: "成功查询",
type: "bar", type: "line",
barWidth: 45, smooth: true,
itemStyle: { itemStyle: {
color: "#41b6ff", color: "#28a745" // 绿
borderRadius: [10, 10, 0, 0]
}, },
data: props.requireData label: {
show: true, // 线
position: "top",
color: "#28a745"
},
data: props.successData
}, },
{ {
name: "匹配数量", name: "失败查询",
type: "bar", type: "line",
barWidth: 45, smooth: true,
itemStyle: { itemStyle: {
color: "#e86033ce", color: "#dc3545" //
borderRadius: [10, 10, 0, 0]
}, },
data: props.questionData label: {
show: true, // 线
position: "top",
color: "#dc3545"
},
data: props.failData
} }
] ]
}); });
@ -104,5 +127,5 @@ watch(
</script> </script>
<template> <template>
<div ref="chartRef" style="width: 100%; height: 550px" /> <div ref="chartRef" style="width: 100%; height: 560px" />
</template> </template>

View File

@ -2,88 +2,28 @@
defineOptions({ defineOptions({
name: "Welcome" name: "Welcome"
}); });
import { ref, markRaw } from "vue"; import { ref, markRaw, reactive, onMounted } from "vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
import ReCol from "@/components/ReCol"; import ReCol from "@/components/ReCol";
import { useRouter } from "vue-router";
import ChartBar from "./components/ChartBar.vue"; import ChartBar from "./components/ChartBar.vue";
import { useRenderFlicker } from "@/components/ReFlicker"; import { useRenderFlicker } from "@/components/ReFlicker";
import Segmented, { type OptionsType } from "@/components/ReSegmented"; import { useDark, cloneDeep, randomGradient, isString } from "@pureadmin/utils";
import { useDark, cloneDeep, randomGradient } from "@pureadmin/utils";
import GroupLine from "@iconify-icons/ri/group-line"; import GroupLine from "@iconify-icons/ri/stack-line";
import Question from "@iconify-icons/ri/question-answer-line"; import Question from "@iconify-icons/ri/question-answer-line";
import CheckLine from "@iconify-icons/ri/chat-check-line"; import CheckLine from "@iconify-icons/ri/chat-check-line";
import Smile from "@iconify-icons/ri/star-smile-line"; import { getUserQueryStatisticsAPI } from "@/api/user";
import { QueryCodeLogInfo } from "types/code";
import { getCodeLogListAPI } from "@/api/code";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { $t } from "@/plugins/i18n";
const router = useRouter();
const { isDark } = useDark(); const { isDark } = useDark();
const getRandomIntBetween = (min: number, max: number) => { const getRandomIntBetween = (min: number, max: number) => {
return Math.floor(Math.random() * (max - min + 1)) + min; return Math.floor(Math.random() * (max - min + 1)) + min;
}; };
let curWeek = ref(1); // 01
const optionsBasis: Array<OptionsType> = [
{
label: "上周"
},
{
label: "本周"
}
];
const days = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
/** 需求人数、提问数量、解决数量、用户满意度 */
const chartData = [
{
icon: GroupLine,
bgColor: "#effaff",
color: "#41b6ff",
duration: 2200,
name: "需求人数",
value: 36000,
percent: "+88%",
data: [2101, 5288, 4239, 4962, 6752, 5208, 7450] // 线
},
{
icon: Question,
bgColor: "#fff5f4",
color: "#e85f33",
duration: 1600,
name: "提问数量",
value: 16580,
percent: "+70%",
data: [2216, 1148, 1255, 788, 4821, 1973, 4379]
},
{
icon: CheckLine,
bgColor: "#eff8f4",
color: "#26ce83",
duration: 1500,
name: "解决数量",
value: 16499,
percent: "+99%",
data: [861, 1002, 3195, 1715, 3666, 2415, 3645]
},
{
icon: Smile,
bgColor: "#f6f4fe",
color: "#7846e5",
duration: 100,
name: "用户满意度",
value: 100,
percent: "+100%",
data: [100]
}
];
/** 分析概览 */
const barChartData = [
{
requireData: [2101, 5288, 4239, 4962, 6752, 5208, 7450],
questionData: [2216, 1148, 1255, 1788, 4821, 1973, 4379]
},
{
requireData: [2101, 3280, 4400, 4962, 5752, 6889, 7600],
questionData: [2116, 3148, 3255, 3788, 4821, 4970, 5390]
}
];
/** 数据统计 */ /** 数据统计 */
const tableData = Array.from({ length: 30 }).map((_, index) => { const tableData = Array.from({ length: 30 }).map((_, index) => {
return { return {
@ -95,16 +35,218 @@ const tableData = Array.from({ length: 30 }).map((_, index) => {
date: dayjs().subtract(index, "day").format("YYYY-MM-DD") date: dayjs().subtract(index, "day").format("YYYY-MM-DD")
}; };
}); });
/** 最新动态 */
const latestNewsData = cloneDeep(tableData) /**用户统计 */
.slice(0, 14) const userStatisticsData = reactive({
.map((item, index) => { /**今日统计 */
today_count: 0,
/**当月统计 */
this_month_count: 0,
/**上月统计 */
last_month_count: 0,
/**最近14天内成功查询统计 */
before_14day_count_success: [
{
/**日期 */
date: "",
/**查询次数 */
count: 0
}
],
/**最近14天内失败查询统计 */
before_14day_count_fail: [
{
/**日期 */
date: "",
/**查询次数 */
count: 0
}
]
});
/**日期列表 */
const dates = ref<string[]>([]);
/**成功查询列表 */
const successData = ref<number[]>([]);
/**失败查询列表 */
const failData = ref<number[]>([]);
/**获取用户统计信息 */
const getUserStatistics = async () => {
const res = await getUserQueryStatisticsAPI();
if (res.success) {
Object.assign(userStatisticsData, res.data);
dates.value = res.data.before_14day_count_success.map(item => item.date);
successData.value = res.data.before_14day_count_success.map(
item => item.count
);
failData.value = res.data.before_14day_count_fail.map(item => item.count);
}
};
/**活动列表 */
const codeLogList = ref<QueryCodeLogInfo[]>([]);
/**获取日历活动 */
const getActivityCalendar = async () => {
let [startTime, endTime] = date.value;
const res = await getCodeLogListAPI({
page: 1,
pageSize: 99999999999,
startTime: startTime,
endTime: endTime
});
if (res.success) {
codeLogList.value = res.data.result;
codeLogList.value = cloneDeep(codeLogList.value)
.map((item: QueryCodeLogInfo) => {
return Object.assign(item, { return Object.assign(item, {
date: `${dayjs().subtract(index, "day").format("YYYY-MM-DD")} ${ operation_time: `${dayjs(item.operation_time).format("YYYY年MM月DD日 HH:mm:ss")}`
days[dayjs().subtract(index, "day").day()]
}`
}); });
})
.sort((a: QueryCodeLogInfo, b: QueryCodeLogInfo) => {
return b.operation_time < a.operation_time ? -1 : 1;
}); });
}
};
/**最近时间选择 */
const shortcuts = [
{
text: "上周",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
return [start, end];
}
},
{
text: "上个月",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
return [start, end];
}
},
{
text: "三个月前",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
return [start, end];
}
},
{
text: "半年前",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 180);
return [start, end];
}
},
{
text: "一年前",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 365);
return [start, end];
}
},
{
text: "最近一周",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 3);
end.setTime(end.getTime() + 3600 * 1000 * 24 * 3);
return [start, end];
}
},
{
text: "最近一个月",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 15);
end.setTime(end.getTime() + 3600 * 1000 * 24 * 15);
return [start, end];
}
},
{
text: "最近三个月",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 45);
end.setTime(end.getTime() + 3600 * 1000 * 24 * 45);
return [start, end];
}
},
{
text: "最近半年",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
end.setTime(end.getTime() + 3600 * 1000 * 24 * 90);
return [start, end];
}
},
{
text: "最近一年",
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 180);
end.setTime(end.getTime() + 3600 * 1000 * 24 * 180);
return [start, end];
}
}
];
/**日期选择器时间 */
const date = ref();
/**日期选择器变化 */
const onChange = async (e: any) => {
await getActivityCalendar();
};
/**处理详情 */
const onClickDetails = (row: QueryCodeLogInfo) => {
let params = { detailsId: row.id };
Object.keys(params).forEach(param => {
if (!isString(params[param])) {
params[param] = params[param].toString();
}
});
useMultiTagsStoreHook().handleTags("push", {
path: "/code/log/details/:detailsId",
name: "QueryCodedetails",
params: params,
meta: {
title: $t("menus:QueryCodedetails")
},
dynamicLevel: 1
});
router.push({
name: "QueryCodedetails",
params: params
});
};
/**挂载执行 */
onMounted(async () => {
//
const currentDate = new Date();
//
let threeDaysAgo = new Date(currentDate);
threeDaysAgo.setDate(currentDate.getDate() - 1);
let threeDaysAgoTimestamp = threeDaysAgo.getTime();
date.value = [threeDaysAgoTimestamp, currentDate.getTime()];
await getUserStatistics();
await getActivityCalendar();
});
</script> </script>
<template> <template>
@ -147,7 +289,9 @@ const latestNewsData = cloneDeep(tableData)
</div> </div>
<div class="flex justify-center items-start mt-3"> <div class="flex justify-center items-start mt-3">
<div class="w-1/2 text-center"> <div class="w-1/2 text-center">
<h2 class="font-bold text-blue-500">688780</h2> <h2 class="font-bold text-blue-500">
{{ userStatisticsData.today_count }}
</h2>
</div> </div>
</div> </div>
</el-card> </el-card>
@ -185,7 +329,9 @@ const latestNewsData = cloneDeep(tableData)
</div> </div>
<div class="flex justify-center items-start mt-3"> <div class="flex justify-center items-start mt-3">
<div class="w-1/2 text-center"> <div class="w-1/2 text-center">
<h2 class="font-bold text-blue-500">79999897098</h2> <h2 class="font-bold text-blue-500">
{{ userStatisticsData.this_month_count }}
</h2>
</div> </div>
</div> </div>
</el-card> </el-card>
@ -227,7 +373,9 @@ const latestNewsData = cloneDeep(tableData)
</div> </div>
<div class="flex justify-center items-start mt-3"> <div class="flex justify-center items-start mt-3">
<div class="w-1/2 text-center"> <div class="w-1/2 text-center">
<h2 class="font-bold text-blue-500">7898067690</h2> <h2 class="font-bold text-blue-500">
{{ userStatisticsData.last_month_count }}
</h2>
</div> </div>
</div> </div>
</el-card> </el-card>
@ -253,12 +401,12 @@ const latestNewsData = cloneDeep(tableData)
<el-card class="bar-card" shadow="never"> <el-card class="bar-card" shadow="never">
<div class="flex justify-between"> <div class="flex justify-between">
<span class="text-md font-medium">数据概览</span> <span class="text-md font-medium">数据概览</span>
<Segmented v-model="curWeek" :options="optionsBasis" />
</div> </div>
<div class="flex justify-between items-start mt-3"> <div class="flex justify-between items-start mt-3">
<ChartBar <ChartBar
:requireData="barChartData[curWeek].requireData" :dates="dates"
:questionData="barChartData[curWeek].questionData" :successData="successData"
:failData="failData"
/> />
</div> </div>
</el-card> </el-card>
@ -281,14 +429,29 @@ const latestNewsData = cloneDeep(tableData)
}" }"
> >
<el-card shadow="never"> <el-card shadow="never">
<div class="flex justify-between"> <div class="flex justify-between flex-wrap">
<span class="text-md font-medium">查询动态</span> <el-date-picker
v-model="date"
type="daterange"
unlink-panels
format="YYYY年MM月DD日"
range-separator="到"
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="x"
:shortcuts="shortcuts"
@change="onChange"
/>
<div class="text-md font-medium mt-3 text-center w-full">
查询动态
</div> </div>
<el-scrollbar max-height="558" class="mt-3"> </div>
<el-timeline> <el-scrollbar max-height="500" class="mt-3 !h-[518px]">
<el-timeline v-if="codeLogList.length">
<el-timeline-item <el-timeline-item
v-for="(item, index) in latestNewsData" v-for="(item, index) in codeLogList"
:key="index" :key="index"
class="ml-2"
center center
placement="top" placement="top"
:icon=" :icon="
@ -300,15 +463,26 @@ const latestNewsData = cloneDeep(tableData)
}) })
) )
" "
:timestamp="item.date" :timestamp="
item.operation_time as unknown as unknown as unknown as string
"
@click="onClickDetails(item)"
> >
<p class="text-text_color_regular text-sm"> <p class="text-text_color_regular text-sm">
<el-tag type="primary" effect="plain">
{{ item.department_name }}
</el-tag>
<el-tag type="warning" effect="plain">
{{ item.operator_nickname }}
</el-tag>
{{ {{
`查询 ${item.requiredNumber} 条数据,${item.resolveNumber} 条已匹配` `查询${item.query_count}条数据,成功匹配${item.result_count}条,耗时${item.cost_time.toFixed(2)}ms`
}} }}
</p> </p>
</el-timeline-item> </el-timeline-item>
</el-timeline> </el-timeline>
<el-empty v-else description="暂无查询动态" class="!h-[540px]" />
</el-scrollbar> </el-scrollbar>
</el-card> </el-card>
</re-col> </re-col>