diff --git a/src/components/Charts/ChartOne.tsx b/src/components/Charts/ChartOne.tsx deleted file mode 100644 index cff990b..0000000 --- a/src/components/Charts/ChartOne.tsx +++ /dev/null @@ -1,356 +0,0 @@ -import { ApexOptions } from 'apexcharts'; -import { useEffect, useState } from 'react'; -import ReactApexChart from 'react-apexcharts'; -import dayjs from 'dayjs' -import { MonthlySums, Result } from './chart'; - -interface ChartOneState { - series: { - name: string; - data: number[]; - }[]; -} - -const ChartOne = () => { - const [result, setResult] = useState({} as Result) - const [scope, setScope] = useState<"day" | "month" | "year">("day") - const [startDate, setStartDate] = useState(dayjs(new Date()).subtract(7, "day").format("YYYY/MM/DD")) - const [endDate, setEndDate] = useState(dayjs(new Date()).format("YYYY/MM/DD")) - - const [options, setOptions] = useState({ - legend: { - show: false, - position: 'top', - horizontalAlign: 'left', - }, - colors: ['#3C50E0', '#80CAEE'], - chart: { - fontFamily: 'Satoshi, sans-serif', - height: 335, - type: 'area', - dropShadow: { - enabled: true, - color: '#623CEA14', - top: 10, - blur: 4, - left: 0, - opacity: 0.1, - }, - - toolbar: { - show: false, - }, - }, - responsive: [ - { - breakpoint: 1024, - options: { - chart: { - height: 300, - }, - }, - }, - { - breakpoint: 1366, - options: { - chart: { - height: 350, - }, - }, - }, - ], - stroke: { - width: [2, 2], - curve: 'straight', - }, - grid: { - xaxis: { - lines: { - show: true, - }, - }, - yaxis: { - lines: { - show: true, - }, - }, - }, - dataLabels: { - enabled: false, - }, - markers: { - size: 4, - colors: '#fff', - strokeColors: ['#3056D3', '#80CAEE'], - strokeWidth: 3, - strokeOpacity: 0.9, - strokeDashArray: 0, - fillOpacity: 1, - discrete: [], - hover: { - size: undefined, - sizeOffset: 5, - }, - }, - xaxis: { - type: 'category', - categories: [], - axisBorder: { - show: false, - }, - axisTicks: { - show: false, - }, - }, - yaxis: { - title: { - style: { - fontSize: '0px', - }, - }, - }, - }) - - const [state, setState] = useState({ - series: [ - { - name: '访客数量', - data: [], - }, - { - name: 'IP数量', - data: [], - }, - ], - }); - - useEffect(() => { - const siteId = import.meta.env.VITE_BAIDU_TONGJI_SITE_ID - const token = import.meta.env.VITE_BAIDU_TONGJI_ACCESS_TOKEN - - fetch(`/api/rest/2.0/tongji/report/getData?access_token=${token}&site_id=${siteId}&start_date=${startDate}&end_date=${endDate}&metrics=pv_count%2Cip_count&method=overview%2FgetTimeTrendRpt`).then(async res => { - const { result } = await res.json() - console.log({ result }); - setResult(result) - }) - }, [scope]) - - useEffect(() => { - if (!result?.items?.length) return - - switch (scope) { - case "day": - const categories_weeks = result.items[0].map((item: string[]) => { - const year = new Date().getFullYear() + "/" - return item[0].replace(year, "") - }) - - setOptions((data) => ( - { - ...data, - xaxis: { ...options.xaxis, categories: categories_weeks } - } - )) - - const pvList_weeks = result.items[1].map((item: number[]) => item[0]) - const ipList_weeks = result.items[1].map((item: number[]) => item[1]) - setState((prevState) => ({ - ...prevState, - series: [ - { - name: '访客数量', - data: pvList_weeks, - }, - { - name: 'IP数量', - data: ipList_weeks, - }, - ], - })); - break - case "month": - const datesArray: string[][] = result.items[0]; - const valuesArray: (string | number)[][] = result.items[1]; - - const monthlySums: MonthlySums = {}; - - datesArray.forEach((dateArray, index) => { - const date: string = dateArray[0]; - const [year, month, day] = date.split('/'); - - if (!monthlySums[month]) { - monthlySums[month] = { pv: 0, ip: 0 }; - } - - const pair = valuesArray[index]; - - if (pair.length === 2) { - const firstValue = parseFloat(pair[0] as string); - const secondValue = parseFloat(pair[1] as string); - - if (!isNaN(firstValue) && !isNaN(secondValue)) { - monthlySums[month].pv += firstValue; - monthlySums[month].ip += secondValue; - } - } - }); - - setOptions((data) => ( - { - ...data, - xaxis: { ...options.xaxis, categories: Object.keys(monthlySums) } - } - )) - - const list = Object.values(monthlySums) - const pvList_month = list.map(item => item.pv) - const ipList_month = list.map(item => item.ip) - - setState((prevState) => ({ - ...prevState, - series: [ - { - name: '访客数量', - data: pvList_month, - }, - { - name: 'IP数量', - data: ipList_month, - }, - ], - })); - - break - case "year": - const yearlySums: { [year: string]: { pv: number, ip: number } } = {}; - - result.items[0].forEach((dateArray: string[], index: number) => { - const date: string = dateArray[0]; - const [year, month, day] = date.split('/'); - - if (!yearlySums[year]) { - yearlySums[year] = { pv: 0, ip: 0 }; - } - - const pair = result.items[1][index]; - console.log(pair); - - if (pair.length === 2) { - const firstValue = parseFloat(pair[0] as string); - const secondValue = parseFloat(pair[1] as string); - - if (!isNaN(firstValue) && !isNaN(secondValue)) { - yearlySums[year].pv += firstValue; - yearlySums[year].ip += secondValue; - } - } - }); - - // 删除没有数据的年份 - Object.keys(yearlySums).forEach(year => { - if (yearlySums[year].pv === 0 && yearlySums[year].ip === 0) { - delete yearlySums[year]; - } - }); - - setOptions((data) => ( - { - ...data, - xaxis: { ...options.xaxis, categories: Object.keys(yearlySums) } - } - )); - - const yearlyList = Object.values(yearlySums); - const pvList_year = yearlyList.map(item => item.pv); - const ipList_year = yearlyList.map(item => item.ip); - - setState((prevState) => ({ - ...prevState, - series: [ - { - name: '访客数量', - data: pvList_year, - }, - { - name: 'IP数量', - data: ipList_year, - }, - ], - })); - - break - } - }, [result]) - - const handleReset = () => { - setState((prevState) => ({ - ...prevState, - })); - }; - handleReset; - - return ( -
-
-
-
- - - - -
-

访客(UV)

-
-
- -
- - - - -
-

IP

-
-
-
- -
-
- - - - - -
-
-
- -
- -
-
- ); -}; - -export default ChartOne; \ No newline at end of file diff --git a/src/components/Charts/chart.d.ts b/src/components/Charts/chart.d.ts index 600ee9e..07e3ffb 100644 --- a/src/components/Charts/chart.d.ts +++ b/src/components/Charts/chart.d.ts @@ -14,4 +14,11 @@ export interface MonthlySums { pv: number; ip: number; }; +} + +export interface ChartOneState { + series: { + name: string; + data: number[]; + }[]; } \ No newline at end of file diff --git a/src/pages/Dashboard/ECommerce.tsx b/src/pages/Dashboard/ECommerce.tsx index 8630a50..c197af9 100644 --- a/src/pages/Dashboard/ECommerce.tsx +++ b/src/pages/Dashboard/ECommerce.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import ChartOne from '../../components/Charts/ChartOne'; +import VisitorsStatisChat from './components/VisitorsStatisChat'; import ChartThree from '../../components/Charts/ChartThree'; import ChartTwo from '../../components/Charts/ChartTwo'; import ChatCard from '../../components/Chat/ChatCard'; @@ -30,7 +30,7 @@ const ECommerce: React.FC = () => {
- + diff --git a/src/pages/Dashboard/components/VisitorsStatisChat/index.tsx b/src/pages/Dashboard/components/VisitorsStatisChat/index.tsx new file mode 100644 index 0000000..c05e80b --- /dev/null +++ b/src/pages/Dashboard/components/VisitorsStatisChat/index.tsx @@ -0,0 +1,326 @@ +import { useEffect, useState, useCallback, useMemo } from 'react'; +import { Spin } from 'antd'; +import { ApexOptions } from 'apexcharts'; +import ReactApexChart from 'react-apexcharts'; +import { MonthlySums, Result, ChartOneState } from './type'; +import dayjs from 'dayjs'; + +const VisitorsStatisChat = () => { + const [result, setResult] = useState(null); + const [scope, setScope] = useState<"day" | "month" | "year">("day"); + const [startDate, setStartDate] = useState(dayjs(new Date()).subtract(7, "day").format("YYYY/MM/DD")); + const [endDate, setEndDate] = useState(dayjs(new Date()).format("YYYY/MM/DD")); + const [loading, setLoading] = useState(false); + + // Echarts相关配置 + const [options, setOptions] = useState({ + legend: { + show: false, + position: 'top', + horizontalAlign: 'left', + }, + colors: ['#3C50E0', '#80CAEE'], + chart: { + fontFamily: 'Satoshi, sans-serif', + height: 335, + type: 'area', + dropShadow: { + enabled: true, + color: '#623CEA14', + top: 10, + blur: 4, + left: 0, + opacity: 0.1, + }, + toolbar: { + show: false, + }, + }, + responsive: [ + { + breakpoint: 1024, + options: { + chart: { + height: 300, + }, + }, + }, + { + breakpoint: 1366, + options: { + chart: { + height: 350, + }, + }, + }, + ], + stroke: { + width: [2, 2], + curve: 'straight', + }, + grid: { + xaxis: { + lines: { + show: true, + }, + }, + yaxis: { + lines: { + show: true, + }, + }, + }, + dataLabels: { + enabled: false, + }, + markers: { + size: 4, + colors: '#fff', + strokeColors: ['#3056D3', '#80CAEE'], + strokeWidth: 3, + strokeOpacity: 0.9, + strokeDashArray: 0, + fillOpacity: 1, + discrete: [], + hover: { + size: undefined, + sizeOffset: 5, + }, + }, + xaxis: { + type: 'category', + categories: [], + axisBorder: { + show: false, + }, + axisTicks: { + show: false, + }, + }, + yaxis: { + title: { + style: { + fontSize: '0px', + }, + }, + }, + }); + + // 核心数据 + const [state, setState] = useState({ + series: [ + { + name: '访客数量', + data: [], + }, + { + name: 'IP数量', + data: [], + }, + ], + }); + + // 获取统计数据 + const getDataList = useCallback(async () => { + setLoading(true); + + const siteId = import.meta.env.VITE_BAIDU_TONGJI_SITE_ID; + const token = import.meta.env.VITE_BAIDU_TONGJI_ACCESS_TOKEN; + + const response = await fetch(`/api/rest/2.0/tongji/report/getData?access_token=${token}&site_id=${siteId}&start_date=${startDate}&end_date=${endDate}&metrics=pv_count%2Cip_count&method=overview%2FgetTimeTrendRpt`); + const data = await response.json(); + const { result } = data; + setResult(result); + + setLoading(false); + }, [startDate, endDate]); + + useEffect(() => { + getDataList(); + }, [getDataList]); + + // 切换不同范围的数据 + const scopeData = useMemo(() => { + if (!result?.items?.length) return { categories: [], series: [[], []] }; + + let categories = []; + let pvList = []; + let ipList = []; + + switch (scope) { + case "day": + categories = result.items[0].map((item: string[]) => { + const year = new Date().getFullYear() + "/"; + return item[0].replace(year, ""); + }); + + pvList = result.items[1].map((item: number[]) => item[0]); + ipList = result.items[1].map((item: number[]) => item[1]); + break; + case "month": + const datesArray: string[][] = result.items[0]; + const valuesArray: (string | number)[][] = result.items[1]; + + const monthlySums: MonthlySums = {}; + + datesArray.forEach((dateArray, index) => { + const date: string = dateArray[0]; + const [year, month, day] = date.split('/'); + + if (!monthlySums[month]) { + monthlySums[month] = { pv: 0, ip: 0 }; + } + + const pair = valuesArray[index]; + + if (pair.length === 2) { + const firstValue = parseFloat(pair[0] as string); + const secondValue = parseFloat(pair[1] as string); + + if (!isNaN(firstValue) && !isNaN(secondValue)) { + monthlySums[month].pv += firstValue; + monthlySums[month].ip += secondValue; + } + } + }); + + categories = Object.keys(monthlySums); + pvList = Object.values(monthlySums).map(item => item.pv); + ipList = Object.values(monthlySums).map(item => item.ip); + break; + case "year": + const yearlySums: { [year: string]: { pv: number, ip: number } } = {}; + + result.items[0].forEach((dateArray: string[], index: number) => { + const date: string = dateArray[0]; + const [year, month, day] = date.split('/'); + + if (!yearlySums[year]) { + yearlySums[year] = { pv: 0, ip: 0 }; + } + + const pair = result.items[1][index]; + + if (pair.length === 2) { + const firstValue = parseFloat(pair[0] + ''); + const secondValue = parseFloat(pair[1] + ''); + + if (!isNaN(firstValue) && !isNaN(secondValue)) { + yearlySums[year].pv += firstValue; + yearlySums[year].ip += secondValue; + } + } + }); + + // 删除没有数据的年份 + Object.keys(yearlySums).forEach(year => { + if (yearlySums[year].pv === 0 && yearlySums[year].ip === 0) { + delete yearlySums[year]; + } + }); + + categories = Object.keys(yearlySums); + pvList = Object.values(yearlySums).map(item => item.pv); + ipList = Object.values(yearlySums).map(item => item.ip); + break; + } + + return { categories, series: [pvList, ipList] }; + }, [result, scope]); + + // 当数据发生变化时,更新图表选项和状态 + useEffect(() => { + setOptions((data) => ({ + ...data, + xaxis: { ...options.xaxis, categories: scopeData.categories } + })); + + setState((prevState) => ({ + ...prevState, + series: [ + { + name: '访客数量', + data: scopeData.series[0], + }, + { + name: 'IP数量', + data: scopeData.series[1], + }, + ], + })); + }, [scopeData]); + + // 处理范围变更并相应地更新日期范围 + const handleScopeChange = (newScope: "day" | "month" | "year") => { + setScope(newScope); + switch (newScope) { + case "day": + setStartDate(dayjs(new Date()).subtract(7, "day").format("YYYY/MM/DD")); + break; + case "month": + const year = new Date().getFullYear() + ""; + setStartDate(year + "/01/01"); + break; + case "year": + setStartDate(dayjs(new Date()).subtract(5, "year").format("YYYY/MM/DD")); + break; + } + }; + + return ( +
+
+
+
+ + + + +
+

访客(UV)

+
+
+ +
+ + + + +
+

IP

+
+
+
+ +
+
+ + + + + +
+
+
+ + +
+ +
+
+
+ ); +}; + +export default VisitorsStatisChat; \ No newline at end of file diff --git a/src/pages/Dashboard/components/VisitorsStatisChat/type.d.ts b/src/pages/Dashboard/components/VisitorsStatisChat/type.d.ts new file mode 100644 index 0000000..07e3ffb --- /dev/null +++ b/src/pages/Dashboard/components/VisitorsStatisChat/type.d.ts @@ -0,0 +1,24 @@ +export interface Result { + timeSpan: string[]; + fields: string[]; + items: [ + string[][], + number[][], + any[], + any[] + ]; +} + +export interface MonthlySums { + [key: string]: { + pv: number; + ip: number; + }; +} + +export interface ChartOneState { + series: { + name: string; + data: number[]; + }[]; +} \ No newline at end of file