feat: 添加首页数据展示
This commit is contained in:
parent
d8c2d1db4b
commit
f039e020e1
@ -123,3 +123,34 @@ export const postUploadAvatarAPI = (id: string, data: { file: Blob }) => {
|
||||
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");
|
||||
};
|
||||
|
@ -3,11 +3,15 @@ import { useDark, useECharts } from "@pureadmin/utils";
|
||||
import { type PropType, ref, computed, watch, nextTick } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
requireData: {
|
||||
dates: {
|
||||
type: Array as PropType<Array<string>>,
|
||||
default: () => []
|
||||
},
|
||||
successData: {
|
||||
type: Array as PropType<Array<number>>,
|
||||
default: () => []
|
||||
},
|
||||
questionData: {
|
||||
failData: {
|
||||
type: Array as PropType<Array<number>>,
|
||||
default: () => []
|
||||
}
|
||||
@ -28,70 +32,89 @@ watch(
|
||||
await nextTick(); // 确保DOM更新完成后再执行
|
||||
setOptions({
|
||||
container: ".bar-card",
|
||||
color: ["#41b6ff", "#e85f33"],
|
||||
color: ["#28a745", "#dc3545"], // 绿色成功,红色失败
|
||||
title: {
|
||||
text: "近14天内查询统计",
|
||||
left: "center", // 标题居中
|
||||
top: 10, // 标题放在顶部
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: "bold",
|
||||
color: "#333"
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "none"
|
||||
type: "line"
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: "20px",
|
||||
top: "60px", // 预留空间给标题
|
||||
left: "50px",
|
||||
right: 0
|
||||
right: "20px",
|
||||
bottom: "50px"
|
||||
},
|
||||
legend: {
|
||||
data: ["查询数量", "匹配数量"],
|
||||
data: ["成功查询", "失败查询"],
|
||||
textStyle: {
|
||||
color: "#606266",
|
||||
fontSize: "0.875rem"
|
||||
},
|
||||
bottom: 0
|
||||
top: 10, // 放置在右上角
|
||||
right: 20 // 靠右
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
|
||||
data: props.dates, // 横坐标,日期数据
|
||||
axisLabel: {
|
||||
fontSize: "0.875rem"
|
||||
fontSize: "0.875rem",
|
||||
rotate: 30 // 避免重叠
|
||||
},
|
||||
axisPointer: {
|
||||
type: "shadow"
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
axisLabel: {
|
||||
fontSize: "0.875rem"
|
||||
},
|
||||
splitLine: {
|
||||
show: false // 去网格线
|
||||
show: true,
|
||||
lineStyle: {
|
||||
type: "dashed"
|
||||
}
|
||||
// name: "单位: 个"
|
||||
}
|
||||
],
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "查询数量",
|
||||
type: "bar",
|
||||
barWidth: 45,
|
||||
name: "成功查询",
|
||||
type: "line",
|
||||
smooth: true,
|
||||
itemStyle: {
|
||||
color: "#41b6ff",
|
||||
borderRadius: [10, 10, 0, 0]
|
||||
color: "#28a745" // 绿色
|
||||
},
|
||||
data: props.requireData
|
||||
label: {
|
||||
show: true, // 在折线上显示数值
|
||||
position: "top",
|
||||
color: "#28a745"
|
||||
},
|
||||
data: props.successData
|
||||
},
|
||||
{
|
||||
name: "匹配数量",
|
||||
type: "bar",
|
||||
barWidth: 45,
|
||||
name: "失败查询",
|
||||
type: "line",
|
||||
smooth: true,
|
||||
itemStyle: {
|
||||
color: "#e86033ce",
|
||||
borderRadius: [10, 10, 0, 0]
|
||||
color: "#dc3545" // 红色
|
||||
},
|
||||
data: props.questionData
|
||||
label: {
|
||||
show: true, // 在折线上显示数值
|
||||
position: "top",
|
||||
color: "#dc3545"
|
||||
},
|
||||
data: props.failData
|
||||
}
|
||||
]
|
||||
});
|
||||
@ -104,5 +127,5 @@ watch(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="chartRef" style="width: 100%; height: 550px" />
|
||||
<div ref="chartRef" style="width: 100%; height: 560px" />
|
||||
</template>
|
||||
|
@ -2,88 +2,28 @@
|
||||
defineOptions({
|
||||
name: "Welcome"
|
||||
});
|
||||
import { ref, markRaw } from "vue";
|
||||
import { ref, markRaw, reactive, onMounted } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import ReCol from "@/components/ReCol";
|
||||
import { useRouter } from "vue-router";
|
||||
import ChartBar from "./components/ChartBar.vue";
|
||||
import { useRenderFlicker } from "@/components/ReFlicker";
|
||||
import Segmented, { type OptionsType } from "@/components/ReSegmented";
|
||||
import { useDark, cloneDeep, randomGradient } from "@pureadmin/utils";
|
||||
import { useDark, cloneDeep, randomGradient, isString } 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 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 getRandomIntBetween = (min: number, max: number) => {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
};
|
||||
let curWeek = ref(1); // 0上周、1本周
|
||||
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) => {
|
||||
return {
|
||||
@ -95,16 +35,218 @@ const tableData = Array.from({ length: 30 }).map((_, index) => {
|
||||
date: dayjs().subtract(index, "day").format("YYYY-MM-DD")
|
||||
};
|
||||
});
|
||||
/** 最新动态 */
|
||||
const latestNewsData = cloneDeep(tableData)
|
||||
.slice(0, 14)
|
||||
.map((item, index) => {
|
||||
|
||||
/**用户统计 */
|
||||
const userStatisticsData = reactive({
|
||||
/**今日统计 */
|
||||
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, {
|
||||
date: `${dayjs().subtract(index, "day").format("YYYY-MM-DD")} ${
|
||||
days[dayjs().subtract(index, "day").day()]
|
||||
}`
|
||||
operation_time: `${dayjs(item.operation_time).format("YYYY年MM月DD日 HH:mm:ss")}`
|
||||
});
|
||||
})
|
||||
.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>
|
||||
|
||||
<template>
|
||||
@ -147,7 +289,9 @@ const latestNewsData = cloneDeep(tableData)
|
||||
</div>
|
||||
<div class="flex justify-center items-start mt-3">
|
||||
<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>
|
||||
</el-card>
|
||||
@ -185,7 +329,9 @@ const latestNewsData = cloneDeep(tableData)
|
||||
</div>
|
||||
<div class="flex justify-center items-start mt-3">
|
||||
<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>
|
||||
</el-card>
|
||||
@ -227,7 +373,9 @@ const latestNewsData = cloneDeep(tableData)
|
||||
</div>
|
||||
<div class="flex justify-center items-start mt-3">
|
||||
<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>
|
||||
</el-card>
|
||||
@ -253,12 +401,12 @@ const latestNewsData = cloneDeep(tableData)
|
||||
<el-card class="bar-card" shadow="never">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-md font-medium">数据概览</span>
|
||||
<Segmented v-model="curWeek" :options="optionsBasis" />
|
||||
</div>
|
||||
<div class="flex justify-between items-start mt-3">
|
||||
<ChartBar
|
||||
:requireData="barChartData[curWeek].requireData"
|
||||
:questionData="barChartData[curWeek].questionData"
|
||||
:dates="dates"
|
||||
:successData="successData"
|
||||
:failData="failData"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
@ -281,14 +429,29 @@ const latestNewsData = cloneDeep(tableData)
|
||||
}"
|
||||
>
|
||||
<el-card shadow="never">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-md font-medium">查询动态</span>
|
||||
<div class="flex justify-between flex-wrap">
|
||||
<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>
|
||||
<el-scrollbar max-height="558" class="mt-3">
|
||||
<el-timeline>
|
||||
</div>
|
||||
<el-scrollbar max-height="500" class="mt-3 !h-[518px]">
|
||||
<el-timeline v-if="codeLogList.length">
|
||||
<el-timeline-item
|
||||
v-for="(item, index) in latestNewsData"
|
||||
v-for="(item, index) in codeLogList"
|
||||
:key="index"
|
||||
class="ml-2"
|
||||
center
|
||||
placement="top"
|
||||
: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">
|
||||
<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>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
<el-empty v-else description="暂无查询动态" class="!h-[540px]" />
|
||||
</el-scrollbar>
|
||||
</el-card>
|
||||
</re-col>
|
||||
|
Loading…
x
Reference in New Issue
Block a user