diff --git a/src/api/user.ts b/src/api/user.ts index a946bb0..a3a5a82 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -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("get", "/api/user/statistics"); +}; diff --git a/src/views/welcome/components/ChartBar.vue b/src/views/welcome/components/ChartBar.vue index 1983fd7..ace164b 100644 --- a/src/views/welcome/components/ChartBar.vue +++ b/src/views/welcome/components/ChartBar.vue @@ -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>, + default: () => [] + }, + successData: { type: Array as PropType>, default: () => [] }, - questionData: { + failData: { type: Array as PropType>, 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: [ - { - type: "category", - data: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"], - axisLabel: { - fontSize: "0.875rem" - }, - axisPointer: { - type: "shadow" + xAxis: { + type: "category", + data: props.dates, // 横坐标,日期数据 + axisLabel: { + fontSize: "0.875rem", + rotate: 30 // 避免重叠 + }, + axisPointer: { + type: "shadow" + } + }, + yAxis: { + type: "value", + axisLabel: { + fontSize: "0.875rem" + }, + splitLine: { + show: true, + lineStyle: { + type: "dashed" } } - ], - yAxis: [ - { - type: "value", - axisLabel: { - fontSize: "0.875rem" - }, - splitLine: { - show: false // 去网格线 - } - // 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( diff --git a/src/views/welcome/index.vue b/src/views/welcome/index.vue index 1a80c6e..69ec931 100644 --- a/src/views/welcome/index.vue +++ b/src/views/welcome/index.vue @@ -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 = [ - { - 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) => { - return Object.assign(item, { - date: `${dayjs().subtract(index, "day").format("YYYY-MM-DD")} ${ - days[dayjs().subtract(index, "day").day()] - }` - }); + +/**用户统计 */ +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([]); +/**成功查询列表 */ +const successData = ref([]); +/**失败查询列表 */ +const failData = ref([]); +/**获取用户统计信息 */ +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([]); + +/**获取日历活动 */ +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, { + 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(); +});