From a2cb7fdccfeb3019337e5cb44a28e5457e68dce9 Mon Sep 17 00:00:00 2001 From: sheepzh Date: Sat, 17 Aug 2024 23:01:35 +0800 Subject: [PATCH] Semi --- .../components/Summary/Calendar/Wrapper.ts | 13 -- .../components/Trend/Dimension/Wrapper.ts | 12 -- src/app/components/Dashboard/common.ts | 14 -- .../Dashboard/components/Calendar/Wrapper.ts | 47 ++--- .../Dashboard/components/Calendar/index.tsx | 27 ++- .../Dashboard/components/MonthOnMonth.tsx | 167 ------------------ .../components/MonthOnMonth/Wrapper.ts | 101 +++++++++++ .../components/MonthOnMonth/index.tsx | 56 ++++++ .../Dashboard/components/TopKVisit/Wrapper.ts | 133 +++++++++----- .../Dashboard/components/TopKVisit/index.tsx | 17 +- src/app/components/Dashboard/index.tsx | 12 +- src/app/components/Dashboard/style.sass | 3 + .../components/Period/Average/Wrapper.tsx | 8 +- .../Habit/components/Period/Stack/Wrapper.ts | 9 +- .../Habit/components/Period/Trend/Wrapper.ts | 11 +- .../components/Site/DailyTrend/Wrapper.ts | 11 +- .../Habit/components/Site/TopK/Wrapper.ts | 14 +- .../Report/ReportFilter/DownloadFile.tsx | 2 +- .../Report/ReportFilter/RemoteClient.tsx | 2 +- src/app/components/Report/index.tsx | 14 +- src/app/components/Report/styles/element.sass | 11 +- .../components/common/SwitchFilterItem.tsx | 10 +- src/app/echarts.ts | 18 ++ src/app/index.ts | 2 + src/app/styles/index.sass | 60 ++++--- src/hooks/useEcharts.ts | 18 +- 26 files changed, 407 insertions(+), 385 deletions(-) delete mode 100644 src/app/components/Dashboard/common.ts delete mode 100644 src/app/components/Dashboard/components/MonthOnMonth.tsx create mode 100644 src/app/components/Dashboard/components/MonthOnMonth/Wrapper.ts create mode 100644 src/app/components/Dashboard/components/MonthOnMonth/index.tsx create mode 100644 src/app/echarts.ts diff --git a/src/app/components/Analysis/components/Summary/Calendar/Wrapper.ts b/src/app/components/Analysis/components/Summary/Calendar/Wrapper.ts index 49962485..180aa57b 100644 --- a/src/app/components/Analysis/components/Summary/Calendar/Wrapper.ts +++ b/src/app/components/Analysis/components/Summary/Calendar/Wrapper.ts @@ -14,10 +14,6 @@ import { EffectScatterSeriesOption } from "echarts" import { formatTime, getAllDatesBetween, getWeeksAgo, parseTime } from "@util/time" -import { EffectScatterChart } from "echarts/charts" -import { SVGRenderer } from "echarts/renderers" -import { use } from "echarts/core" -import { GridComponent, TitleComponent, TooltipComponent, VisualMapComponent } from "echarts/components" import { groupBy, rotate } from "@util/array" import { t } from "@app/locale" import { getRegularTextColor, getSecondaryTextColor } from "@util/style" @@ -43,15 +39,6 @@ type _Value = [ string, ] -use([ - SVGRenderer, - EffectScatterChart, - TooltipComponent, - GridComponent, - VisualMapComponent, - TitleComponent, -]) - const WEEK_NUM = 26 type EffectScatterItem = EffectScatterSeriesOption["data"][number] diff --git a/src/app/components/Analysis/components/Trend/Dimension/Wrapper.ts b/src/app/components/Analysis/components/Trend/Dimension/Wrapper.ts index 81da30d0..fb13e739 100644 --- a/src/app/components/Analysis/components/Trend/Dimension/Wrapper.ts +++ b/src/app/components/Analysis/components/Trend/Dimension/Wrapper.ts @@ -14,24 +14,12 @@ import type { GridComponentOption, } from "echarts/components" -import { use } from "echarts/core" -import { LineChart } from "echarts/charts" -import { SVGRenderer } from "echarts/renderers" -import { TitleComponent, TooltipComponent, GridComponent } from "echarts/components" import { ValueFormatter } from "@app/components/Analysis/util" import { getRegularTextColor } from "@util/style" import { EchartsWrapper } from "@hooks" import { getLineSeriesPalette, tooltipDot, tooltipFlexLine } from "@app/util/echarts" import { TopLevelFormatterParams } from "echarts/types/dist/shared" -use([ - LineChart, - TitleComponent, - TooltipComponent, - GridComponent, - SVGRenderer, -]) - type EcOption = ComposeOption< | LineSeriesOption | TitleComponentOption diff --git a/src/app/components/Dashboard/common.ts b/src/app/components/Dashboard/common.ts deleted file mode 100644 index d71aee8f..00000000 --- a/src/app/components/Dashboard/common.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) 2022 Hengyang Zhang - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -import type { TitleComponentOption } from 'echarts/components' - -export const BASE_TITLE_OPTION: TitleComponentOption = { - show: true, - left: '1%', - top: '0%', -} \ No newline at end of file diff --git a/src/app/components/Dashboard/components/Calendar/Wrapper.ts b/src/app/components/Dashboard/components/Calendar/Wrapper.ts index ebfc2608..f8466c3f 100644 --- a/src/app/components/Dashboard/components/Calendar/Wrapper.ts +++ b/src/app/components/Dashboard/components/Calendar/Wrapper.ts @@ -4,28 +4,17 @@ * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ -import type { TitleComponentOption, TooltipComponentOption, GridComponentOption, VisualMapComponentOption } from "echarts/components" -import { TitleComponent, TooltipComponent, GridComponent, VisualMapComponent } from "echarts/components" -import { ScatterChart, ScatterSeriesOption, type HeatmapSeriesOption } from "echarts/charts" -import { use, type ComposeOption } from "echarts/core" -import { SVGRenderer } from "echarts/renderers" - -// Register echarts -use([ - SVGRenderer, - ScatterChart, - TooltipComponent, - GridComponent, - VisualMapComponent, - TitleComponent, -]) +import type { + ComposeOption, + TooltipComponentOption, GridComponentOption, VisualMapComponentOption, + ScatterSeriesOption, HeatmapSeriesOption, +} from "echarts" import { EchartsWrapper } from "@hooks" import { formatPeriodCommon, getAllDatesBetween, MILL_PER_HOUR, MILL_PER_MINUTE } from "@util/time" -import { groupBy, rotate, sum } from "@util/array" +import { groupBy, rotate } from "@util/array" import { t } from "@app/locale" import { getPrimaryTextColor } from "@util/style" -import { BASE_TITLE_OPTION } from "../../common" import { getAppPageUrl } from "@util/constant/url" import { REPORT_ROUTE } from "@app/router/constants" import { createTabAfterCurrent } from "@api/chrome/tab" @@ -41,7 +30,6 @@ type _Value = [ type EcOption = ComposeOption< | ScatterSeriesOption - | TitleComponentOption | TooltipComponentOption | GridComponentOption | VisualMapComponentOption @@ -84,12 +72,6 @@ function getXAxisLabelMap(data: _Value[]): { [x: string]: string } { return result } -const titleText = (totalHours: number) => t(msg => totalHours - ? msg.dashboard.heatMap.title0 - : msg.dashboard.heatMap.title1, - { hour: totalHours } -) - type HeatmapItem = HeatmapSeriesOption["data"][number] const cvtHeatmapItem = (d: _Value): HeatmapItem => { @@ -133,8 +115,6 @@ const computePieces = (min: number, max: number): Piece[] => { } function optionOf(data: _Value[], weekDays: string[], dom: HTMLElement): EcOption { - const totalMills = sum(data?.map(d => d[2] ?? 0)) - const totalHours = Math.floor(totalMills / MILL_PER_HOUR) const xAxisLabelMap = getXAxisLabelMap(data) const textColor = getPrimaryTextColor() const w = dom?.getBoundingClientRect?.()?.width @@ -145,11 +125,6 @@ function optionOf(data: _Value[], weekDays: string[], dom: HTMLElement): EcOptio const maxVal = Math.max(...data.map(a => a[2])) const minVal = Math.min(...data.map(a => a[2]).filter(v => v)) return { - title: { - ...BASE_TITLE_OPTION, - text: titleText(totalHours), - textStyle: { fontSize: '14px', color: textColor } - }, tooltip: { borderWidth: 0, formatter: (params: any) => { @@ -222,7 +197,12 @@ function handleClick(value: _Value): void { } class Wrapper extends EchartsWrapper { - protected generateOption({ startTime, endTime, value }: BizOption): EcOption | Promise { + protected isSizeSensitize: boolean = true + + protected generateOption(option: BizOption): EcOption | Promise { + if (!option) return {} + + const { startTime, endTime, value } = option const allDates = getAllDatesBetween(startTime, endTime) const data: _Value[] = [] allDates.forEach((date, index) => { @@ -242,7 +222,8 @@ class Wrapper extends EchartsWrapper { } protected afterInit(): void { - this.instance.on("click", (params: { value: _Value }) => handleClick(params.value as _Value)) + const supportClick = !window.matchMedia("(any-pointer:coarse)").matches + supportClick && this.instance.on("click", (params: any) => handleClick(params.value as _Value)) } } diff --git a/src/app/components/Dashboard/components/Calendar/index.tsx b/src/app/components/Dashboard/components/Calendar/index.tsx index 8318b62c..417135d4 100644 --- a/src/app/components/Dashboard/components/Calendar/index.tsx +++ b/src/app/components/Dashboard/components/Calendar/index.tsx @@ -4,16 +4,29 @@ * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ -import { useEcharts } from "@hooks" +import { useEcharts, useRequest } from "@hooks" import { locale } from "@i18n" import statService from "@service/stat-service" -import { getWeeksAgo } from "@util/time" +import { getWeeksAgo, MILL_PER_HOUR } from "@util/time" import { defineComponent } from "vue" import Wrapper, { BizOption } from "./Wrapper" import { groupBy, sum } from "@util/array" +import { t } from "@app/locale" +import ChartTitle from "../../ChartTitle" const WEEK_NUM = 53 +const titleText = (option: BizOption) => { + const { value } = option || {} + const totalMills = sum(Object.values(value || {})) + const totalHours = Math.floor(totalMills / MILL_PER_HOUR) + return t(msg => totalHours + ? msg.dashboard.heatMap.title0 + : msg.dashboard.heatMap.title1, + { hour: totalHours } + ) +} + const fetchData = async (): Promise => { const endTime = new Date() const startTime: Date = getWeeksAgo(endTime, locale === "zh_CN", WEEK_NUM) @@ -27,9 +40,15 @@ const fetchData = async (): Promise => { } const _default = defineComponent(() => { - const { elRef } = useEcharts(Wrapper, fetchData) + const { data } = useRequest(fetchData) + const { elRef } = useEcharts(Wrapper, data) - return () =>
+ return () => ( +
+ +
+
+ ) }) export default _default \ No newline at end of file diff --git a/src/app/components/Dashboard/components/MonthOnMonth.tsx b/src/app/components/Dashboard/components/MonthOnMonth.tsx deleted file mode 100644 index f84e3294..00000000 --- a/src/app/components/Dashboard/components/MonthOnMonth.tsx +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Copyright (c) 2022 Hengyang Zhang - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -import type { ComposeOption } from "echarts/core" -import type { TopLevelFormatterParams } from "echarts/types/dist/shared" - -import { use } from "echarts/core" -import { BarChart, BarSeriesOption } from "echarts/charts" -import { - GridComponent, type GridComponentOption, - TitleComponent, type TitleComponentOption, - TooltipComponent, type TooltipComponentOption, - LegendComponent, type LegendComponentOption, -} from "echarts/components" - -use([BarChart, GridComponent, TitleComponent, TooltipComponent, LegendComponent]) - -import { formatPeriodCommon, MILL_PER_DAY } from "@util/time" -import { defineComponent } from "vue" -import statService from "@service/stat-service" -import { groupBy, sum } from "@util/array" -import { BASE_TITLE_OPTION } from "../common" -import { t } from "@app/locale" -import { getPrimaryTextColor } from "@util/style" -import DateIterator from "@util/date-iterator" -import { cvt2LocaleTime } from "@app/util/time" -import { getCompareColor, getDiffColor, tooltipDot } from "@app/util/echarts" -import { EchartsWrapper, useEcharts } from "@hooks" -import DashboardCard from "../DashboardCard" - -type EcOption = ComposeOption< - | BarSeriesOption - | GridComponentOption - | TitleComponentOption - | TooltipComponentOption - | LegendComponentOption -> - -const PERIOD_WIDTH = 30 -const TOP_NUM = 15 - -type _Value = { - value: number - row: Row -} - -function optionOf(lastPeriodItems: Row[], thisPeriodItems: Row[]): EcOption { - const textColor = getPrimaryTextColor() - - const [color1, color2] = getCompareColor() - const [incColor, decColor] = getDiffColor() - - return { - title: { - ...BASE_TITLE_OPTION, - text: t(msg => msg.dashboard.monthOnMonth.title, { k: TOP_NUM }), - textStyle: { - color: textColor, - fontSize: '14px', - } - }, - tooltip: { - trigger: 'axis', - axisPointer: { type: 'shadow' }, - formatter(params: TopLevelFormatterParams) { - if (!Array.isArray(params)) return '' - const [thisItem, lastItem] = params.map(v => v.data as _Value).map(v => v.row) || [] - const [thisColor, lastColor] = params.map(v => v.color) - const { date: thisDate, total: thisVal } = thisItem || {} - const { date: lastDate, total: lastVal } = lastItem || {} - const lastStr = `${tooltipDot(lastColor as string)} ${cvt2LocaleTime(lastDate)} ${formatPeriodCommon(lastVal)}` - let thisStr = `${tooltipDot(thisColor as string)} ${cvt2LocaleTime(thisDate)} ${formatPeriodCommon(thisVal)}` - if (lastVal) { - const delta = (thisVal - lastVal) / lastVal * 100 - let deltaStr = delta.toFixed(1) + '%' - if (delta >= 0) deltaStr = '+' + deltaStr - const fontColor = delta >= 0 ? incColor : decColor - thisStr += ` ${deltaStr}` - } - return `${thisStr}
${lastStr}` - }, - }, - grid: { - left: '5%', - right: '5%', - bottom: '3%', - top: '11%', - }, - xAxis: { - type: 'category', - splitLine: { show: false }, - axisTick: { show: false }, - axisLine: { show: false }, - axisLabel: { show: false }, - }, - yAxis: [ - { - type: 'value', - axisLabel: { show: false }, - splitLine: { show: false }, - } - ], - series: [ - { - name: "This Month", - stack: "one", - type: 'bar', - barCategoryGap: '55%', - itemStyle: { color: color1, borderRadius: [10, 10, 0, 0] }, - data: thisPeriodItems.map(row => ({ value: row.total, row })), - }, { - name: "Last Month", - stack: "one", - type: 'bar', - itemStyle: { color: color2, borderRadius: [0, 0, 10, 10] }, - data: lastPeriodItems.map(row => ({ value: -row.total, row })), - } - ], - } -} - -class ChartWrapper extends EchartsWrapper<[Row[], Row[]], EcOption> { - generateOption = ([lastPeriodItems, thisPeriodItems]) => optionOf(lastPeriodItems, thisPeriodItems) -} - -type Row = { - date: string - total: number -} - -const cvtRow = (rows: timer.stat.Row[], start: Date, end: Date): Row[] => { - const groupByDate = groupBy(rows, r => r.date, l => sum(l.map(e => e.focus ?? 0))) - const iterator = new DateIterator(start, end) - const result: Row[] = [] - iterator.forEach(yearMonthDate => { - const total = groupByDate[yearMonthDate] ?? 0 - result.push({ total, date: yearMonthDate }) - }) - return result -} - -const fetchData = async (): Promise<[thisMonth: Row[], lastMonth: Row[]]> => { - const now = new Date() - const lastPeriodStart = new Date(now.getTime() - MILL_PER_DAY * (PERIOD_WIDTH * 2 - 1)) - const lastPeriodEnd = new Date(now.getTime() - MILL_PER_DAY * PERIOD_WIDTH) - const thisPeriodStart = new Date(now.getTime() - MILL_PER_DAY * (PERIOD_WIDTH - 1)) - const thisPeriodEnd = now - - // Query with alias - // @since 1.1.8 - const lastPeriodItems: timer.stat.Row[] = await statService.select({ date: [lastPeriodStart, lastPeriodEnd] }, true) - const lastRows = cvtRow(lastPeriodItems, lastPeriodStart, lastPeriodEnd) - const thisPeriodItems: timer.stat.Row[] = await statService.select({ date: [thisPeriodStart, thisPeriodEnd] }, true) - const thisRows = cvtRow(thisPeriodItems, thisPeriodStart, thisPeriodEnd) - return [lastRows, thisRows] -} - -const _default = defineComponent(() => { - const { elRef } = useEcharts(ChartWrapper, fetchData) - return () =>
-}) - -export default _default \ No newline at end of file diff --git a/src/app/components/Dashboard/components/MonthOnMonth/Wrapper.ts b/src/app/components/Dashboard/components/MonthOnMonth/Wrapper.ts new file mode 100644 index 00000000..1a017dc0 --- /dev/null +++ b/src/app/components/Dashboard/components/MonthOnMonth/Wrapper.ts @@ -0,0 +1,101 @@ +import type { BarSeriesOption, ComposeOption, GridComponentOption, LegendComponentOption, TooltipComponentOption } from "echarts" + +import { getCompareColor, getDiffColor, tooltipDot } from "@app/util/echarts" +import { cvt2LocaleTime } from "@app/util/time" +import { EchartsWrapper } from "@hooks" +import { formatPeriodCommon } from "@util/time" +import { TopLevelFormatterParams } from "echarts/types/dist/shared" + +type EcOption = ComposeOption< + | BarSeriesOption + | GridComponentOption + | TooltipComponentOption + | LegendComponentOption +> + +type _Value = { + value: number + row: Row +} + +function optionOf(lastPeriodItems: Row[], thisPeriodItems: Row[], domWidth: number): EcOption { + const [color1, color2] = getCompareColor() + const [incColor, decColor] = getDiffColor() + + return { + tooltip: { + trigger: 'axis', + axisPointer: { type: 'shadow' }, + formatter(params: TopLevelFormatterParams) { + if (!Array.isArray(params)) return '' + const [thisItem, lastItem] = params.map(v => v.data as _Value).map(v => v.row) || [] + const [thisColor, lastColor] = params.map(v => v.color) + const { date: thisDate, total: thisVal } = thisItem || {} + const { date: lastDate, total: lastVal } = lastItem || {} + const lastStr = `${tooltipDot(lastColor as string)} ${cvt2LocaleTime(lastDate)} ${formatPeriodCommon(lastVal)}` + let thisStr = `${tooltipDot(thisColor as string)} ${cvt2LocaleTime(thisDate)} ${formatPeriodCommon(thisVal)}` + if (lastVal) { + const delta = (thisVal - lastVal) / lastVal * 100 + let deltaStr = delta.toFixed(1) + '%' + if (delta >= 0) deltaStr = '+' + deltaStr + const fontColor = delta >= 0 ? incColor : decColor + thisStr += ` ${deltaStr}` + } + return `${thisStr}
${lastStr}` + }, + }, + grid: { + left: '5%', + right: '5%', + bottom: '3%', + top: '11%', + }, + xAxis: { + type: 'category', + splitLine: { show: false }, + axisTick: { show: false }, + axisLine: { show: false }, + axisLabel: { show: false }, + }, + yAxis: [ + { + type: 'value', + axisLabel: { show: false }, + splitLine: { show: false }, + } + ], + series: [ + { + name: "This Month", + stack: "one", + type: 'bar', + barCategoryGap: `${domWidth < 500 ? 30 : 55}%`, + itemStyle: { color: color1, borderRadius: [10, 10, 0, 0] }, + data: thisPeriodItems.map(row => ({ value: row.total, row })), + }, { + name: "Last Month", + stack: "one", + type: 'bar', + itemStyle: { color: color2, borderRadius: [0, 0, 10, 10] }, + data: lastPeriodItems.map(row => ({ value: -row.total, row })), + } + ], + } +} + +type Row = { + date: string + total: number +} + +class Wrapper extends EchartsWrapper<[Row[], Row[]], EcOption> { + protected isSizeSensitize: boolean = true + + generateOption = ([lastPeriodItems = [], thisPeriodItems = []] = []) => { + const domWidth = this.getDomWidth() + if (!domWidth) return {} + return optionOf(lastPeriodItems, thisPeriodItems, domWidth) + } +} + +export default Wrapper \ No newline at end of file diff --git a/src/app/components/Dashboard/components/MonthOnMonth/index.tsx b/src/app/components/Dashboard/components/MonthOnMonth/index.tsx new file mode 100644 index 00000000..221b18bb --- /dev/null +++ b/src/app/components/Dashboard/components/MonthOnMonth/index.tsx @@ -0,0 +1,56 @@ +import { defineComponent } from "vue" +import ChartTitle from "../../ChartTitle" +import { useEcharts } from "@hooks" +import Wrapper from "./Wrapper" +import { groupBy, sum } from "@util/array" +import DateIterator from "@util/date-iterator" +import { MILL_PER_DAY } from "@util/time" +import statService from "@service/stat-service" +import { t } from "@app/locale" + +const PERIOD_WIDTH = 30 +const TOP_NUM = 15 + +type Row = { + date: string + total: number +} + +const cvtRow = (rows: timer.stat.Row[], start: Date, end: Date): Row[] => { + const groupByDate = groupBy(rows, r => r.date, l => sum(l.map(e => e.focus ?? 0))) + const iterator = new DateIterator(start, end) + const result: Row[] = [] + iterator.forEach(yearMonthDate => { + const total = groupByDate[yearMonthDate] ?? 0 + result.push({ total, date: yearMonthDate }) + }) + return result +} + +const fetchData = async (): Promise<[thisMonth: Row[], lastMonth: Row[]]> => { + const now = new Date() + const lastPeriodStart = new Date(now.getTime() - MILL_PER_DAY * (PERIOD_WIDTH * 2 - 1)) + const lastPeriodEnd = new Date(now.getTime() - MILL_PER_DAY * PERIOD_WIDTH) + const thisPeriodStart = new Date(now.getTime() - MILL_PER_DAY * (PERIOD_WIDTH - 1)) + const thisPeriodEnd = now + + // Query with alias + // @since 1.1.8 + const lastPeriodItems: timer.stat.Row[] = await statService.select({ date: [lastPeriodStart, lastPeriodEnd] }, true) + const lastRows = cvtRow(lastPeriodItems, lastPeriodStart, lastPeriodEnd) + const thisPeriodItems: timer.stat.Row[] = await statService.select({ date: [thisPeriodStart, thisPeriodEnd] }, true) + const thisRows = cvtRow(thisPeriodItems, thisPeriodStart, thisPeriodEnd) + return [lastRows, thisRows] +} + +const _default = defineComponent(() => { + const { elRef } = useEcharts(Wrapper, fetchData) + return () => ( +
+ msg.dashboard.monthOnMonth.title, { k: TOP_NUM })} /> +
+
+ ) +}) + +export default _default \ No newline at end of file diff --git a/src/app/components/Dashboard/components/TopKVisit/Wrapper.ts b/src/app/components/Dashboard/components/TopKVisit/Wrapper.ts index 3267a210..0c73c5c8 100644 --- a/src/app/components/Dashboard/components/TopKVisit/Wrapper.ts +++ b/src/app/components/Dashboard/components/TopKVisit/Wrapper.ts @@ -5,27 +5,20 @@ * https://opensource.org/licenses/MIT */ import type { ComposeOption } from "echarts/core" -import type { PieSeriesOption } from "echarts/charts" -import type { TitleComponentOption, TooltipComponentOption } from "echarts/components" +import type { PieSeriesOption, BarSeriesOption } from "echarts/charts" +import type { TooltipComponentOption, GridComponentOption } from "echarts/components" -import { use } from "echarts/core" -import { PieChart } from "echarts/charts" -import { TitleComponent, TooltipComponent } from "echarts/components" -import { SVGRenderer } from "echarts/renderers" import { EchartsWrapper } from "@hooks" import { getPrimaryTextColor } from "@util/style" -import { BASE_TITLE_OPTION } from "../../common" -import { t } from "@app/locale" import { generateSiteLabel } from "@util/site" -import { getSeriesPalette } from "@app/util/echarts" - -use([PieChart, TitleComponent, TooltipComponent, SVGRenderer]) +import { getSeriesPalette, getStepColors } from "@app/util/echarts" export const TOP_NUM = 6, DAY_NUM = 30 type EcOption = ComposeOption< | PieSeriesOption - | TitleComponentOption + | BarSeriesOption + | GridComponentOption | TooltipComponentOption > @@ -37,50 +30,110 @@ export type BizOption = { alias?: string } -function generateOption(data: BizOption[]): EcOption { - const textColor = getPrimaryTextColor() - // console.log(dom.clientWidth) - // const titleText = t(msg => msg.dashboard.topK.title, { k: TOP_NUM, day: DAY_NUM }) +const tooltipOption = (): EcOption['tooltip'] => ( + { + show: true, + borderWidth: 0, + formatter(params: any) { + const visit = params.data?.value || 0 + const host = params.data?.host || '' + const alias = params.data?.alias || '' + const hostLabel = generateSiteLabel(host, alias) + return `${hostLabel}
${visit}` + } + } +) + +const xsOption = (data: BizOption[], domWidth: number): EcOption => { + data = data?.sort((a, b) => (b?.value ?? 0) - (a?.value ?? 0)) + const max = Math.max(...(data?.map(v => v.value) || [])) + const hosts = data?.sort((a, b) => (a.value ?? 0) - (b?.value ?? 0))?.map(v => v.host) || [] + const margin = 8 + const chartW = domWidth * (100 - margin * 2) / 100 return { - // title: { - // ...BASE_TITLE_OPTION, - // text: titleText, - // textStyle: { - // color: textColor, - // fontSize: '14px', - // overflow: 'breakAll', - // } - // }, - tooltip: { - show: true, - borderWidth: 0, - formatter(params: any) { - const visit = params.data?.value || 0 - const host = params.data?.host || '' - const alias = params.data?.alias || '' - const hostLabel = generateSiteLabel(host, alias) - return `${hostLabel}
${visit}` - } + tooltip: tooltipOption(), + grid: { left: '5%', right: '5%', bottom: '3%', top: '8%', }, + xAxis: { + type: "value", + minInterval: 1, + axisLabel: { show: false }, + splitLine: { show: false }, + min: 0, + max: max, + }, + yAxis: { + type: "category", + data: hosts, + axisLabel: { show: false }, + axisTick: { show: false }, + axisLine: { show: false }, + }, + series: { + type: "bar", + barWidth: '100%', + data: data.map((row, idx) => { + const isBottom = idx === 0 + const isTop = idx === data.length - 1 + const { value = 0 } = row || {} + const labelW = (value / max) * chartW - 8 + return { + ...row, + label: { show: labelW >= 50, width: labelW }, + itemStyle: { + borderRadius: [ + isTop ? 5 : 0, isTop ? 5 : 0, 5, isBottom ? 5 : 0 + ] + }, + } + }), + label: { + position: 'insideRight', + overflow: "truncate", + ellipsis: '...', + minMargin: 5, + padding: [0, 4, 0, 0], + formatter: (param: any) => { + const { host, name } = (param?.data || {}) + return name || host + }, + }, + colorBy: 'data', + color: getStepColors(data.length, 1.5), }, + } +} + +const normalOption = (data: BizOption[]): EcOption => { + const textColor = getPrimaryTextColor() + return { + tooltip: tooltipOption(), series: { - top: '20%', - height: '80%', + top: '10%', + height: '90%', type: 'pie', radius: [20, 80], center: ['50%', '50%'], roseType: 'area', color: getSeriesPalette(), itemStyle: { - borderRadius: 7 + borderRadius: 7, }, label: { color: textColor }, - data: data + data: data, } } } class Wrapper extends EchartsWrapper { - generateOption = generateOption + protected isSizeSensitize: boolean = true + + generateOption(option: BizOption[]) { + if (!option?.length) return {} + const domWidth = this.getDomWidth() + if (!domWidth) return {} + const isXsScreen = domWidth <= 340 + return isXsScreen ? xsOption(option, domWidth) : normalOption(option) + } } export default Wrapper diff --git a/src/app/components/Dashboard/components/TopKVisit/index.tsx b/src/app/components/Dashboard/components/TopKVisit/index.tsx index 8a7224e0..7ba5b0a3 100644 --- a/src/app/components/Dashboard/components/TopKVisit/index.tsx +++ b/src/app/components/Dashboard/components/TopKVisit/index.tsx @@ -6,13 +6,6 @@ */ import type { StatQueryParam } from "@service/stat-service" -import { use } from "echarts/core" -import { PieChart } from "echarts/charts" -import { TitleComponent, TooltipComponent } from "echarts/components" -import { SVGRenderer } from "echarts/renderers" - -use([PieChart, TitleComponent, TooltipComponent, SVGRenderer]) - import statService from "@service/stat-service" import { MILL_PER_DAY } from "@util/time" import { defineComponent } from "vue" @@ -41,10 +34,12 @@ const fetchData = async () => { const _default = defineComponent(() => { const { elRef } = useEcharts(Wrapper, fetchData) const title = t(msg => msg.dashboard.topK.title, { k: TOP_NUM, day: DAY_NUM }) - return () => <> - -
- + return () => ( +
+ +
+
+ ) }) export default _default \ No newline at end of file diff --git a/src/app/components/Dashboard/index.tsx b/src/app/components/Dashboard/index.tsx index 51b2b9bd..9707e7b3 100644 --- a/src/app/components/Dashboard/index.tsx +++ b/src/app/components/Dashboard/index.tsx @@ -5,7 +5,7 @@ * https://opensource.org/licenses/MIT */ -import { defineComponent } from "vue" +import { computed, defineComponent } from "vue" import ContentContainer from "../common/ContentContainer" import DashboardCard from './DashboardCard' import "./style" @@ -16,7 +16,7 @@ import MonthOnMonth from "./components/MonthOnMonth" import TopKVisit from "./components/TopKVisit" import Calendar from "./components/Calendar" import { useRouter } from "vue-router" -import { useRequest } from "@hooks" +import { useMediaSize, useRequest } from "@hooks" import metaService from "@service/meta-service" import { t } from "@app/locale" import { REVIEW_PAGE } from "@util/constant/url" @@ -37,6 +37,12 @@ const _default = defineComponent(() => { } const { width } = useWindowSize() + const mediaSize = useMediaSize() + const showCalendar = computed(() => { + const ms = mediaSize.value + // Not supported screens equals medium size or smaller + return ms === 'lg' || ms === 'xl' + }) return () => ( @@ -64,7 +70,7 @@ const _default = defineComponent(() => { } - + diff --git a/src/app/components/Dashboard/style.sass b/src/app/components/Dashboard/style.sass index b3c1e8fe..77fa8e5f 100644 --- a/src/app/components/Dashboard/style.sass +++ b/src/app/components/Dashboard/style.sass @@ -24,6 +24,9 @@ padding-bottom: 20px .indicator-container,.mom-container,.top-visit-container,.calendar-container min-height: 280px + display: flex + flex-direction: column + gap: 4px .help-us-link color: var(--el-text-color-primary) text-align: center diff --git a/src/app/components/Habit/components/Period/Average/Wrapper.tsx b/src/app/components/Habit/components/Period/Average/Wrapper.tsx index 93a1dddb..8da06689 100644 --- a/src/app/components/Habit/components/Period/Average/Wrapper.tsx +++ b/src/app/components/Habit/components/Period/Average/Wrapper.tsx @@ -4,6 +4,7 @@ * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ +import type { BarSeriesOption, ComposeOption, GridComponentOption, TooltipComponentOption } from "echarts" import { t } from "@app/locale" import { getCompareColor, tooltipDot, tooltipFlexLine, tooltipSpaceLine } from "@app/util/echarts" @@ -11,16 +12,9 @@ import { EchartsWrapper } from "@hooks" import { averageByDay, MINUTE_PER_PERIOD } from "@util/period" import { getPrimaryTextColor } from "@util/style" import { formatPeriodCommon, MILL_PER_MINUTE } from "@util/time" -import { BarSeriesOption, ComposeOption, GridComponentOption, TooltipComponentOption } from "echarts" -import { BarChart } from "echarts/charts" -import { GridComponent, TooltipComponent } from "echarts/components" -import { use } from "echarts/core" -import { SVGRenderer } from "echarts/renderers" import { TopLevelFormatterParams } from "echarts/types/dist/shared" import { generateGridOption } from "../common" -use([SVGRenderer, BarChart, GridComponent, TooltipComponent]) - type EcOption = ComposeOption< | BarSeriesOption | GridComponentOption diff --git a/src/app/components/Habit/components/Period/Stack/Wrapper.ts b/src/app/components/Habit/components/Period/Stack/Wrapper.ts index 5cdbbdf9..6f9e140d 100644 --- a/src/app/components/Habit/components/Period/Stack/Wrapper.ts +++ b/src/app/components/Habit/components/Period/Stack/Wrapper.ts @@ -1,18 +1,13 @@ +import type { ComposeOption, GridComponentOption, LineSeriesOption, TooltipComponentOption } from "echarts" + import { getLineSeriesPalette } from "@app/util/echarts" import { EchartsWrapper } from "@hooks" -import { ComposeOption, GridComponentOption, LineSeriesOption, TooltipComponentOption } from "echarts" -import { LineChart } from "echarts/charts" -import { GridComponent, TooltipComponent } from "echarts/components" -import { use } from "echarts/core" -import { SVGRenderer } from "echarts/renderers" import { formatXAxisTime, generateGridOption } from "../common" import { TopLevelFormatterParams } from "echarts/types/dist/shared" import { formatTime } from "@util/time" import { t } from "@app/locale" import { periodFormatter } from "@app/util/time" -use([LineChart, SVGRenderer, TooltipComponent, GridComponent]) - type EcOption = ComposeOption< | LineSeriesOption | TooltipComponentOption diff --git a/src/app/components/Habit/components/Period/Trend/Wrapper.ts b/src/app/components/Habit/components/Period/Trend/Wrapper.ts index 6e6e8b7d..96a03d7b 100644 --- a/src/app/components/Habit/components/Period/Trend/Wrapper.ts +++ b/src/app/components/Habit/components/Period/Trend/Wrapper.ts @@ -4,15 +4,8 @@ * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ +import type { ComposeOption, GridComponentOption, TooltipComponentOption, BarSeriesOption } from "echarts" -import type { ComposeOption } from "echarts/core" -import type { BarSeriesOption } from "echarts/charts" -import type { GridComponentOption, TooltipComponentOption } from "echarts/components" - -import { use } from "echarts/core" -import { BarChart } from "echarts/charts" -import { SVGRenderer } from "echarts/renderers" -import { TooltipComponent, GridComponent } from "echarts/components" import { formatTime } from "@util/time" import { getPrimaryTextColor } from "@util/style" import { EchartsWrapper } from "@hooks" @@ -20,8 +13,6 @@ import { getSeriesPalette } from "@app/util/echarts" import { formatXAxisTime, generateGridOption } from "../common" import { periodFormatter } from "@app/util/time" -use([BarChart, SVGRenderer, TooltipComponent, GridComponent]) - type EcOption = ComposeOption< | BarSeriesOption | GridComponentOption diff --git a/src/app/components/Habit/components/Site/DailyTrend/Wrapper.ts b/src/app/components/Habit/components/Site/DailyTrend/Wrapper.ts index 9bd415cf..d0d88cba 100644 --- a/src/app/components/Habit/components/Site/DailyTrend/Wrapper.ts +++ b/src/app/components/Habit/components/Site/DailyTrend/Wrapper.ts @@ -1,5 +1,4 @@ -import { EchartsWrapper } from "@hooks" -import { +import type { ComposeOption, GridComponentOption, TitleComponentOption, @@ -8,20 +7,16 @@ import { LineSeriesOption, LinearGradientObject, } from "echarts" + +import { EchartsWrapper } from "@hooks" import { getAllDatesBetween } from "@util/time" import { groupBy, sum } from "@util/array" import { t } from "@app/locale" -import { GridComponent, LegendComponent, TitleComponent, TooltipComponent } from "echarts/components" -import { BarChart, LineChart, PieChart } from "echarts/charts" -import { use } from "echarts/core" -import { SVGRenderer } from "echarts/renderers" import { TopLevelFormatterParams, YAXisOption } from "echarts/types/dist/shared" import { generateTitleOption } from "../common" import { getLineSeriesPalette, tooltipDot, tooltipFlexLine, tooltipSpaceLine } from "@app/util/echarts" import { cvt2LocaleTime, periodFormatter } from "@app/util/time" -use([SVGRenderer, GridComponent, LegendComponent, TooltipComponent, TitleComponent, LineChart, PieChart, BarChart]) - type EcOption = ComposeOption< | GridComponentOption | TooltipComponentOption diff --git a/src/app/components/Habit/components/Site/TopK/Wrapper.ts b/src/app/components/Habit/components/Site/TopK/Wrapper.ts index 84a0bb5b..e706ae50 100644 --- a/src/app/components/Habit/components/Site/TopK/Wrapper.ts +++ b/src/app/components/Habit/components/Site/TopK/Wrapper.ts @@ -5,14 +5,8 @@ * https://opensource.org/licenses/MIT */ -import type { ComposeOption } from "echarts/core" -import type { GridComponentOption, TooltipComponentOption, TitleComponentOption } from "echarts/components" -import type { BarSeriesOption } from "echarts/charts" +import type { ComposeOption, BarSeriesOption, GridComponentOption, TooltipComponentOption, TitleComponentOption } from "echarts" -import { use } from "echarts/core" -import { BarChart } from "echarts/charts" -import { SVGRenderer } from "echarts/renderers" -import { TooltipComponent, GridComponent, TitleComponent } from "echarts/components" import { mergeDate } from "@service/stat-service/merge" import { t } from "@app/locale" import { SeriesDataItem, generateTitleOption } from "../common" @@ -22,8 +16,6 @@ import { TopLevelFormatterParams } from "echarts/types/dist/shared" import { generateSiteLabel } from "@util/site" import { periodFormatter } from "@app/util/time" -use([BarChart, SVGRenderer, TooltipComponent, GridComponent, TitleComponent]) - type EcOption = ComposeOption< | BarSeriesOption | GridComponentOption @@ -101,7 +93,7 @@ async function generateOption(rows: timer.stat.Row[] = [], timeFormat: timer.app show: false, }, }, - series: [{ + series: { type: "bar", barWidth: '100%', data: topList.map((row, idx) => { @@ -133,7 +125,7 @@ async function generateOption(rows: timer.stat.Row[] = [], timeFormat: timer.app }, colorBy: 'data', color: getStepColors(topList.length, 1.5), - }], + }, } } diff --git a/src/app/components/Report/ReportFilter/DownloadFile.tsx b/src/app/components/Report/ReportFilter/DownloadFile.tsx index a0297b0c..b2504b20 100644 --- a/src/app/components/Report/ReportFilter/DownloadFile.tsx +++ b/src/app/components/Report/ReportFilter/DownloadFile.tsx @@ -31,7 +31,7 @@ const _default = defineComponent({ }} > - + diff --git a/src/app/components/Report/ReportFilter/RemoteClient.tsx b/src/app/components/Report/ReportFilter/RemoteClient.tsx index 254ef73b..f1d458a5 100644 --- a/src/app/components/Report/ReportFilter/RemoteClient.tsx +++ b/src/app/components/Report/ReportFilter/RemoteClient.tsx @@ -28,7 +28,7 @@ const _default = defineComponent({ size="small" style={{ display: visible.value ? 'inline-flex' : 'none' }} type={readRemote.value ? 'primary' : null} - class="export-dropdown-button" + class="record-filter-icon-button" onClick={() => readRemote.value = !readRemote.value} > diff --git a/src/app/components/Report/index.tsx b/src/app/components/Report/index.tsx index c7fdfb32..a7e09fb1 100644 --- a/src/app/components/Report/index.tsx +++ b/src/app/components/Report/index.tsx @@ -196,12 +196,14 @@ const _default = defineComponent(() => { } return () => handleBatchDelete(table.value, filterOption, refresh)} - />, + filter: () => ( + handleBatchDelete(table.value, filterOption, refresh)} + /> + ), content: () => <> ctx.emit("change", data.value)) - return () => - {props.label} - - + return () => ( + + {props.label} + + + ) } }) diff --git a/src/app/echarts.ts b/src/app/echarts.ts new file mode 100644 index 00000000..f4f89075 --- /dev/null +++ b/src/app/echarts.ts @@ -0,0 +1,18 @@ +import { BarChart, EffectScatterChart, HeatmapChart, LineChart, PieChart, ScatterChart } from "echarts/charts" +import { + GridComponent, + TitleComponent, + TooltipComponent, + VisualMapComponent, + LegendComponent, +} from "echarts/components" +import { use } from "echarts/core" +import { SVGRenderer } from "echarts/renderers" + +export const initEcharts = () => { + use([ + SVGRenderer, + GridComponent, TooltipComponent, TitleComponent, VisualMapComponent, LegendComponent, + BarChart, PieChart, LineChart, HeatmapChart, ScatterChart, EffectScatterChart, + ]) +} \ No newline at end of file diff --git a/src/app/index.ts b/src/app/index.ts index b8ffffee..29b7610f 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -17,6 +17,7 @@ import { initLocale } from "@i18n" import { toggle, init as initTheme } from "@util/dark-mode" import optionService from "@service/option-service" import { initElementLocale } from "@i18n/element" +import { initEcharts } from "./echarts" async function main() { // Init theme with cache first @@ -24,6 +25,7 @@ async function main() { // Calculate the latest mode optionService.isDarkMode().then(toggle) await initLocale() + initEcharts() const app: App = createApp(Main) installRouter(app) app.mount('#app') diff --git a/src/app/styles/index.sass b/src/app/styles/index.sass index 564e7f2e..45973ab1 100644 --- a/src/app/styles/index.sass +++ b/src/app/styles/index.sass @@ -36,6 +36,8 @@ a width: 100% background: var(--timer-app-container-bg-color) margin: auto + >.el-scrollbar + width: 100% .content-container margin-top: var(--timer-container-container-padding) @@ -60,12 +62,25 @@ a width: 100% padding-bottom: 10px padding-top: 18px - margin-bottom: 10px - + box-sizing: border-box display: flex + gap: 20px flex-wrap: wrap - >* - margin-bottom: 10px + align-items: center + .filter-item + display: inline-flex + + .el-input__suffix + width: 20px + + .el-picker-panel__sidebar + width: 130px !important + + .el-picker-panel [slot="sidebar"] + .el-picker-panel__body, + .el-picker-panel__sidebar + .el-picker-panel__body + margin-left: 130px !important + .el-range-input + width: 120px .el-input min-width: 180px .el-select @@ -76,30 +91,12 @@ a .filter-name margin-right: 4px -.filter-item - display: inline-flex - padding-right: 20px - - .el-input__suffix - width: 20px - - .el-picker-panel__sidebar - width: 130px !important - - .el-picker-panel [slot="sidebar"] + .el-picker-panel__body, - .el-picker-panel__sidebar + .el-picker-panel__body - margin-left: 130px !important - .el-range-input - width: 120px - -.filter-item.el-switch - display: inline-flex !important - .filter-switch display: inline-flex align-items: center + justify-content: center + gap: 5px .filter-name - display: inline-flex color: var(--el-text-color-secondary) font-weight: bold font-size: 14px @@ -108,6 +105,8 @@ a .filter-item-right,.filter-item-right-group display: inline-flex margin-left: auto +.filter-item-right-group + gap: 4px .filter-item.el-input width: 225px @@ -167,3 +166,16 @@ html[data-theme='dark']:root @media (max-width: 600px) \:root --timer-container-container-padding: 20px + .filter-container .el-card__body + flex-direction: column + gap: 10px + .filter-item + margin-bottom: 0px + width: 100% + margin-bottom: 0px !important + .el-date-editor + .el-range-input + width: 39% + .filter-item-right, + .filter-item-right-group + margin-left: unset diff --git a/src/hooks/useEcharts.ts b/src/hooks/useEcharts.ts index a23e671b..d572b505 100644 --- a/src/hooks/useEcharts.ts +++ b/src/hooks/useEcharts.ts @@ -13,6 +13,11 @@ import { useWindowSize } from "@vueuse/core" export abstract class EchartsWrapper { protected instance: ECharts + /** + * true if need to re-generate option while size changing, or false + */ + protected isSizeSensitize: boolean = false + private lastBizOption: BizOption init(container: HTMLDivElement) { this.instance = init(container) @@ -21,13 +26,20 @@ export abstract class EchartsWrapper { async render(biz: BizOption) { if (!this.instance) return + this.lastBizOption = biz + await this.innerRender() + } + + private async innerRender() { + const biz = this.lastBizOption const option = await this.generateOption(biz) if (!option) return this.instance.setOption(option, { notMerge: false }) } - resize() { + async resize() { if (!this.instance) return + this.isSizeSensitize && await this.innerRender() this.instance.resize() } @@ -39,6 +51,10 @@ export abstract class EchartsWrapper { protected afterInit() { } + + protected getDomWidth(): number { + return this.getDom()?.clientWidth ?? 0 + } }