diff --git a/src/app/components/Report/ReportFilter/index.tsx b/src/app/components/Report/ReportFilter/index.tsx
index 5143f2f2..4f36cac4 100644
--- a/src/app/components/Report/ReportFilter/index.tsx
+++ b/src/app/components/Report/ReportFilter/index.tsx
@@ -6,7 +6,6 @@
*/
import type { ElementDatePickerShortcut } from "@src/element-ui/date"
-import type { CalendarMessage } from "@i18n/message/common/calendar"
import DownloadFile from "./DownloadFile"
import RemoteClient from "./RemoteClient"
@@ -21,18 +20,17 @@ import { ElButton } from "element-plus"
import { DeleteFilled } from "@element-plus/icons-vue"
import { useState } from "@hooks"
-function datePickerShortcut(msg: keyof CalendarMessage['range'], agoOfStart?: number, agoOfEnd?: number): ElementDatePickerShortcut {
- const text = t(messages => messages.calendar.range[msg])
+function datePickerShortcut(text: string, agoOfStart?: number, agoOfEnd?: number): ElementDatePickerShortcut {
const value = daysAgo(agoOfStart || 0, agoOfEnd || 0)
return { text, value }
}
const dateShortcuts: ElementDatePickerShortcut[] = [
- datePickerShortcut('today'),
- datePickerShortcut('yesterday', 1, 1),
- datePickerShortcut('last7Days', 7),
- datePickerShortcut('last30Days', 30),
- datePickerShortcut('last60Days', 60),
+ datePickerShortcut(t(msg => msg.calendar.range.today)),
+ datePickerShortcut(t(msg => msg.calendar.range.yesterday), 1, 1),
+ datePickerShortcut(t(msg => msg.calendar.range.lastDays, { n: 7 }), 7),
+ datePickerShortcut(t(msg => msg.calendar.range.lastDays, { n: 30 }), 30),
+ datePickerShortcut(t(msg => msg.calendar.range.lastDays, { n: 60 }), 60),
]
const _default = defineComponent({
diff --git a/src/app/components/common/DateRangeFilterItem.tsx b/src/app/components/common/DateRangeFilterItem.tsx
index 4d32e99c..49333afe 100644
--- a/src/app/components/common/DateRangeFilterItem.tsx
+++ b/src/app/components/common/DateRangeFilterItem.tsx
@@ -9,6 +9,7 @@ import { ElDatePicker } from "element-plus"
import { defineComponent, PropType, ref, Ref } from "vue"
import { ElementDatePickerShortcut } from "@src/element-ui/date"
import { t } from "@app/locale"
+import { EL_DATE_FORMAT } from "@i18n/element"
const _default = defineComponent({
props: {
@@ -30,7 +31,7 @@ const _default = defineComponent({
return () =>
msg.calendar.dateFormat, { y: 'YYYY', m: 'MM', d: 'DD' })}
+ format={EL_DATE_FORMAT}
type="daterange"
rangeSeparator="-"
disabledDate={props.disabledDate}
diff --git a/src/app/styles/echarts.sass b/src/app/styles/echarts.sass
new file mode 100644
index 00000000..22881641
--- /dev/null
+++ b/src/app/styles/echarts.sass
@@ -0,0 +1,18 @@
+\:root
+ --echarts-series-color-1: #00C5C9
+ --echarts-series-color-2: #F72585
+ --echarts-series-color-3: #FFD600
+ --echarts-series-color-4: #3A0CA3
+
+ --echarts-step-color-1: #4361EE
+ --echarts-step-color-2: var(--echarts-series-color-2)
+
+ --echarts-compare-color-1: var(--echarts-series-color-2)
+ --echarts-compare-color-2: #7209B7
+
+ --echarts-increase-color: #00FF29
+ --echarts-decrease-color: #E80054
+ --echarts-pie-border-color: #ffffff
+
+html[data-theme='dark']:root
+ --echarts-pie-border-color: var(--el-border-color-light)
diff --git a/src/app/styles/index.sass b/src/app/styles/index.sass
index 044e93a2..4ff37a00 100644
--- a/src/app/styles/index.sass
+++ b/src/app/styles/index.sass
@@ -7,6 +7,7 @@
@import "../../element-ui/dark-theme"
@import "./compatible"
+@import "./echarts"
body
height: 100vh
@@ -171,4 +172,4 @@ a
html[data-theme='dark']:root
// timer
- --timer-app-container-bg-color: var(--el-fill-color-dark)
+ --timer-app-container-bg-color: var(--el-fill-color-lighter)
diff --git a/src/app/util/echarts.ts b/src/app/util/echarts.ts
new file mode 100644
index 00000000..b9bde7bc
--- /dev/null
+++ b/src/app/util/echarts.ts
@@ -0,0 +1,103 @@
+import { range } from "@util/array"
+import { getCssVariable } from "@util/style"
+import { addVector, multiTuple, subVector } from "@util/tuple"
+import { LinearGradientObject } from "echarts"
+
+const splitVectors = (vectorRange: Tuple, 2>, count: number, gradientFactor?: number): Vector[] => {
+ gradientFactor = gradientFactor ?? 1.3
+ const segmentCount = count - 1
+ const [v1, v2] = vectorRange
+ const delta = subVector(v2, v1)
+ const allVectors = range(segmentCount).map(idx => {
+ const growth = Math.pow(idx / count, gradientFactor)
+ return addVector(v1, multiTuple(delta, growth))
+ })
+ allVectors.push(v2)
+ return allVectors
+}
+
+export const getStepColors = (count: number, gradientFactor?: number): string[] => {
+ const p1 = getCssVariable('--echarts-step-color-1')
+ const p2 = getCssVariable('--echarts-step-color-2')
+ if (count <= 0) return []
+ if (count === 1) return [p1]
+ if (count === 2) return [p1, p2]
+
+ const c1 = cvtColor2Vector(p1)
+ const c2 = cvtColor2Vector(p2)
+ const allVectors = splitVectors([c1, c2], count, gradientFactor)
+ return allVectors.map(([r, g, b]) => `rgb(${r.toFixed(1)}, ${g.toFixed(1)}, ${b.toFixed(1)})`)
+}
+
+/**
+ * #ffffff => [255,255,255]
+ */
+const cvtColor2Vector = (color: string): Vector<3> => {
+ return [color.substring(1, 3), color.substring(3, 5), color.substring(5, 7)]
+ .map(c => parseInt('0x' + c)) as [number, number, number]
+}
+
+export const getSeriesPalette = (): string[] => {
+ return range(4)
+ .map(idx => `--echarts-series-color-${idx + 1}`)
+ .map(val => getCssVariable(val))
+}
+
+const linearGradientColor = (color1: string, color2: string): LinearGradientObject => ({
+ type: "linear",
+ x: 0, y: 0,
+ x2: 0, y2: 1,
+ colorStops: [
+ { offset: 0, color: color1 },
+ { offset: 1, color: color2 },
+ ],
+})
+
+export const getLineSeriesPalette = (): Tuple => {
+ return [
+ linearGradientColor('#37A2FF', '#7415DB'),
+ linearGradientColor('#FF0087', '#87009D'),
+ linearGradientColor('#FFD600', '#DEAD00'),
+ ]
+}
+
+export const getCompareColor = (): [string, string] => {
+ return [
+ getCssVariable('--echarts-compare-color-1'),
+ getCssVariable('--echarts-compare-color-2'),
+ ]
+}
+
+export const getDiffColor = (): [incColor: string, decColor: string] => {
+ return [
+ getCssVariable('--echarts-increase-color'),
+ getCssVariable('--echarts-decrease-color'),
+ ]
+}
+
+export const tooltipDot = (color: string) => {
+ return ``
+}
+
+export const tooltipFlexLine = (left: string, right: string, gap?: number): string => {
+ gap = gap ?? 20
+ return `
+
+
+ ${left}
+
+
+ ${right}
+
+
+ `
+}
+
+export const tooltipSpaceLine = (height?: number): string => {
+ height = height ?? 4
+ return ``
+}
+
+export const getPieBorderColor = (): string => {
+ return getCssVariable('--echarts-pie-border-color')
+}
\ No newline at end of file
diff --git a/src/database/limit-database.ts b/src/database/limit-database.ts
index ba1848b7..8e20adc4 100644
--- a/src/database/limit-database.ts
+++ b/src/database/limit-database.ts
@@ -34,7 +34,7 @@ type ItemValue = {
/**
* Forbidden periods
*/
- p?: [number, number][]
+ p?: Vector<2>[]
/**
* Enabled flag
*/
diff --git a/src/database/stat-database/filter.ts b/src/database/stat-database/filter.ts
index d15b7071..8639d596 100644
--- a/src/database/stat-database/filter.ts
+++ b/src/database/stat-database/filter.ts
@@ -95,13 +95,13 @@ function processDateCondition(cond: _StatCondition, paramDate: Date | [Date, Dat
}
}
-function processParamTimeCondition(cond: _StatCondition, paramTime: number[]) {
+function processParamTimeCondition(cond: _StatCondition, paramTime: Vector<2>) {
if (!paramTime) return
paramTime.length >= 2 && (cond.timeEnd = paramTime[1])
paramTime.length >= 1 && (cond.timeStart = paramTime[0])
}
-function processParamFocusCondition(cond: _StatCondition, paramFocus: number[]) {
+function processParamFocusCondition(cond: _StatCondition, paramFocus: Vector<2>) {
if (!paramFocus) return
paramFocus.length >= 2 && (cond.focusEnd = paramFocus[1])
paramFocus.length >= 1 && (cond.focusStart = paramFocus[0])
diff --git a/src/database/stat-database/index.ts b/src/database/stat-database/index.ts
index b7f468da..0f4f75d4 100644
--- a/src/database/stat-database/index.ts
+++ b/src/database/stat-database/index.ts
@@ -28,13 +28,13 @@ export type StatCondition = {
*
* @since 0.0.9
*/
- focusRange?: number[]
+ focusRange?: Vector<2>
/**
* Time range
*
* @since 0.0.9
*/
- timeRange?: number[]
+ timeRange?: Vector<2>
/**
* Whether to enable full host search, default is false
*
diff --git a/src/element-ui/dark-theme.sass b/src/element-ui/dark-theme.sass
index d1ca71b0..995a58e3 100644
--- a/src/element-ui/dark-theme.sass
+++ b/src/element-ui/dark-theme.sass
@@ -65,7 +65,9 @@ html[data-theme='dark']
--el-fill-color-light: #262727
--el-fill-color-lighter: #1D1D1D
--el-fill-color-extra-light: #191919
- --el-fill-color-blank: var(--el-fill-color-darker)
+ --el-fill-color-blank: var(--el-fill-color-light)
--el-mask-color: rgba(0 0 0 .8)
- --el-mask-color-extra-light: rgba(0 0 0 .3)
- --el-menu-bg-color: var(--el-fill-color-light)
+ --el-mask-color-extra-light: rgba(0 0 0 .05)
+ --el-menu-bg-color: var(--el-fill-color-extra-light)
+ .el-card
+ border: none
diff --git a/src/hooks/useEcharts.ts b/src/hooks/useEcharts.ts
index 3215d886..ef957e2c 100644
--- a/src/hooks/useEcharts.ts
+++ b/src/hooks/useEcharts.ts
@@ -21,7 +21,8 @@ export abstract class EchartsWrapper {
async render(biz: BizOption) {
if (!this.instance) return
const option = await this.generateOption(biz)
- option && this.instance.setOption(option, { notMerge: false })
+ if (!option) return
+ this.instance.setOption(option, { notMerge: false })
}
protected getDom(): HTMLElement {
diff --git a/src/i18n/element.ts b/src/i18n/element.ts
index 0a9193e6..803c4be6 100644
--- a/src/i18n/element.ts
+++ b/src/i18n/element.ts
@@ -1,7 +1,8 @@
import type { Language } from "element-plus/lib/locale"
import type { App } from "vue"
import ElementPlus from 'element-plus'
-import { FEEDBACK_LOCALE, locale } from "."
+import { FEEDBACK_LOCALE, locale, t } from "."
+import calendarMessages from "./message/common/calendar"
const LOCALES: { [locale in timer.Locale]: () => Promise<{ default: Language }> } = {
zh_CN: () => import('element-plus/lib/locale/lang/zh-cn'),
@@ -25,9 +26,4 @@ export const initElementLocale = async (app: App) => {
EL_LOCALE_FALLBACK = (await LOCALES[FEEDBACK_LOCALE]?.())?.default
}
-type I18nKey = (lang: Language['el']) => string
-
-export const tEl = (key: I18nKey): string => {
- const locale = EL_LOCALE || EL_LOCALE_FALLBACK
- return key?.(locale?.el)
-}
\ No newline at end of file
+export const EL_DATE_FORMAT = t(calendarMessages, { key: msg => msg.dateFormat, param: { y: 'YYYY', m: 'MM', d: 'DD' } })
diff --git a/src/i18n/message/app/dashboard-resource.json b/src/i18n/message/app/dashboard-resource.json
index a364f331..ae47e862 100644
--- a/src/i18n/message/app/dashboard-resource.json
+++ b/src/i18n/message/app/dashboard-resource.json
@@ -13,13 +13,8 @@
"browsingTime": "浏览时长超过 {minute} 分钟",
"mostUse": "最喜欢在 {start} 点至 {end} 点之间打开浏览器"
},
- "weekOnWeek": {
- "title": "近一周浏览时长环比变化 TOP {k}",
- "lastBrowse": "上周浏览 {time}",
- "thisBrowse": "本周浏览 {time}",
- "wow": "环比{state} {delta}",
- "increase": "增长",
- "decline": "减少"
+ "monthOnMonth": {
+ "title": "浏览时间环比趋势"
}
},
"zh_TW": {
@@ -38,13 +33,8 @@
"browsingTime": "瀏覽網頁超過 {minute} 分鐘",
"mostUse": "最喜歡在 {start} 點至 {end} 點之間上網"
},
- "weekOnWeek": {
- "title": "瀏覽時間週環比變化 TOP {k}",
- "lastBrowse": "上週瀏覽 {time}",
- "thisBrowse": "本週瀏覽 {time}",
- "wow": "環比{state} {delta}",
- "increase": "增長",
- "decline": "減少"
+ "monthOnMonth": {
+ "title": "瀏覽時間環比趨勢"
}
},
"en": {
@@ -61,13 +51,8 @@
"browsingTime": "Browsed over {minute} minutes",
"mostUse": "Favorite browsing between {start} and {end} o'clock"
},
- "weekOnWeek": {
- "title": "TOP {k} week-on-week change of browsing time",
- "lastBrowse": "Browsed {time} last week",
- "thisBrowse": "Browsed {time} this week",
- "wow": "{delta} {state}",
- "increase": "increased",
- "decline": "decreased"
+ "monthOnMonth": {
+ "title": "Browsing time month-on-month trend"
}
},
"ja": {
@@ -83,14 +68,6 @@
"visitCount": "{site} つのサイトへの合計 {visit} 回の拜訪",
"browsingTime": "{minute} 分以上ウェブを閲覧する",
"mostUse": "{start}:00 から {end}:00 までのお気に入りのインターネットアクセス"
- },
- "weekOnWeek": {
- "title": "週ごとの変更 TOP {k}",
- "lastBrowse": "先週 {time} 閲覧",
- "thisBrowse": "今週は {time} で閲覧",
- "wow": "毎週 {delta} の {state}",
- "increase": "増加",
- "decline": "減らす"
}
},
"pt_PT": {
@@ -106,14 +83,6 @@
"visitCount": "Visite {site} sites {visit} vezes",
"browsingTime": "Navegado por {minute} minutos",
"mostUse": "Navegação favorita entre {start} e {end} horas"
- },
- "weekOnWeek": {
- "title": "TOP {k} semana após semana mudança no tempo de navegação",
- "lastBrowse": "Navegou {time} na última semana",
- "thisBrowse": "Navegou {time} nesta semana",
- "wow": "{delta} {state}",
- "increase": "aumentou",
- "decline": "diminuiu"
}
},
"uk": {
@@ -129,14 +98,6 @@
"visitCount": "Відвідано {site} вебсайтів {visit} разів",
"browsingTime": "Перегляд понад {minute} хвилин",
"mostUse": "Улюблений перегляд між {start} і {end} годинами"
- },
- "weekOnWeek": {
- "title": "Найбільші {k} щотижневих змін часу перегляду",
- "lastBrowse": "Перегляд {time} минулого тижня",
- "thisBrowse": "Перегляд {time} цього тижня",
- "wow": "{delta} {state}",
- "increase": "збільшилося",
- "decline": "зменшилося"
}
},
"es": {
@@ -152,14 +113,6 @@
"visitCount": "Visitó {site} sitios web {visit} veces",
"browsingTime": "Navegó más de {minute} minutos",
"mostUse": "Prefiere navegar entre las {start} y las {end} horas"
- },
- "weekOnWeek": {
- "title": "TOP {k} cambios semanales del tiempo de navegación",
- "lastBrowse": "Navegó {time} la semana pasada",
- "thisBrowse": "Navegó {time} esta semana",
- "wow": "{delta} {state}",
- "increase": "más",
- "decline": "menos"
}
},
"de": {
@@ -175,14 +128,6 @@
"visitCount": "Besuchte {site} Websites {visit} mal",
"browsingTime": "Durchsucht über {minute} Minuten",
"mostUse": "Favoriten zwischen {start} und {end} Uhr"
- },
- "weekOnWeek": {
- "title": "Top {k} der wöchentlichen Veränderungen der Surfzeit",
- "lastBrowse": "Letzte Woche {time} lang durchsucht",
- "thisBrowse": "Letzte Woche {time} durchsucht",
- "wow": "{delta} {state}",
- "increase": "erhöht",
- "decline": "verringert"
}
},
"fr": {
diff --git a/src/i18n/message/app/dashboard.ts b/src/i18n/message/app/dashboard.ts
index 435af01a..8f9a99e3 100644
--- a/src/i18n/message/app/dashboard.ts
+++ b/src/i18n/message/app/dashboard.ts
@@ -21,13 +21,8 @@ export type DashboardMessage = {
browsingTime: string
mostUse: string
}
- weekOnWeek: {
+ monthOnMonth: {
title: string
- lastBrowse: string
- thisBrowse: string
- wow: string
- increase: string
- decline: string
}
}
diff --git a/src/i18n/message/app/habit-resource.json b/src/i18n/message/app/habit-resource.json
index 478dec6d..5fd0d47c 100644
--- a/src/i18n/message/app/habit-resource.json
+++ b/src/i18n/message/app/habit-resource.json
@@ -7,9 +7,11 @@
"title": "不同时段的访问习惯",
"busiest": "每天最繁忙时段",
"idle": "最长空闲时段",
- "yAxisMin": "浏览时长 / 分钟",
- "yAxisHour": "浏览时长 / 小时",
- "averageLabel": "平均每天",
+ "chartType": {
+ "average": "日均",
+ "trend": "趋势",
+ "stack": "累积"
+ },
"sizes": {
"fifteen": "每十五分钟统计一次",
"halfHour": "每半小时统计一次",
@@ -19,20 +21,24 @@
},
"site": {
"title": "网站访问习惯",
- "focusPieTitle": "浏览时间占比",
- "visitPieTitle": "访问次数占比",
- "otherLabel": "其他{count}个网站",
"histogramTitle": "最长访问 TOP {n}",
"exclusiveToday": "今天的数据不计入平均值",
"countTotal": "总计访问次数/网站数",
- "siteAverage": "平均每天访问 {value} 个网站"
+ "siteAverage": "平均每天访问 {value} 个网站",
+ "distribution": {
+ "title": "日均访问频率分布",
+ "aveTime": "日平均浏览时间",
+ "aveVisit": "日平均访问次数",
+ "tooltip": "共 {value} 个网站"
+ },
+ "trend": {
+ "title": "访问日趋势",
+ "siteCount": "网站数"
+ }
}
},
"zh_TW": {
"period": {
- "yAxisMin": "瀏覽時長 / 分鐘",
- "yAxisHour": "瀏覽時長 / 小時",
- "averageLabel": "平均每天",
"sizes": {
"fifteen": "按十五分鐘統計",
"halfHour": "按半小時統計",
@@ -48,9 +54,6 @@
},
"site": {
"title": "網站造訪習慣",
- "focusPieTitle": "瀏覽時間佔比",
- "visitPieTitle": "造訪次數佔比",
- "otherLabel": "其他 {count} 個網站",
"histogramTitle": "最長造訪 TOP {n}",
"exclusiveToday": "今天的數據不計入平均值",
"countTotal": "總計造訪次數/網站數",
@@ -65,9 +68,11 @@
"title": "Habit of time periods",
"busiest": "Busiest time of day",
"idle": "Longest idle period",
- "yAxisMin": "Browsing Time / minute",
- "yAxisHour": "Browsing Time / hour",
- "averageLabel": "Daily average",
+ "chartType": {
+ "average": "Daily average",
+ "trend": "Trend",
+ "stack": "Stack"
+ },
"sizes": {
"fifteen": "Per 15 minutes",
"halfHour": "Per half hour",
@@ -77,20 +82,24 @@
},
"site": {
"title": "Habit of websites",
- "focusPieTitle": "Percentage of browsing time",
- "visitPieTitle": "Percentage of visits",
- "otherLabel": "Other {count} sites",
"histogramTitle": "TOP {n} most visited",
"exclusiveToday": "Today's data is not included in the average",
"countTotal": "Total visits/sites",
- "siteAverage": "Average visits to {value} websites per day"
+ "siteAverage": "Average visits to {value} websites per day",
+ "distribution": {
+ "title": "Daily average frequency distribution",
+ "aveTime": "Average daily browsing time",
+ "aveVisit": "Average daily visit count",
+ "tooltip": "Total {value} websites"
+ },
+ "trend": {
+ "title": "Daily trends",
+ "siteCount": "Website count"
+ }
}
},
"ja": {
"period": {
- "yAxisMin": "閲覧時間/分",
- "yAxisHour": "閲覧時間/時間",
- "averageLabel": "1日平均",
"sizes": {
"fifteen": "15分で統計",
"halfHour": "30分で統計",
@@ -106,9 +115,6 @@
},
"site": {
"title": "ウェブサイトの習慣について",
- "focusPieTitle": "閲覧時間の割合",
- "visitPieTitle": "訪問の割合",
- "otherLabel": "他 {count} サイト",
"histogramTitle": "トップ {n} が最も多く訪れた",
"exclusiveToday": "今日のデータは平均に含まれていません",
"countTotal": "総訪問数/サイト",
@@ -117,9 +123,6 @@
},
"pt_PT": {
"period": {
- "yAxisMin": "Tempo de Navegação / Minutos",
- "yAxisHour": "Tempo de Navegação / Hora",
- "averageLabel": "Média diária",
"sizes": {
"fifteen": "Por 15 minutos",
"halfHour": "Por meia hora",
@@ -135,9 +138,6 @@
},
"site": {
"title": "Hábito de sites",
- "focusPieTitle": "Percentagem de navegação",
- "visitPieTitle": "Porcentagem de visitas",
- "otherLabel": "Outros {count} sites",
"histogramTitle": "TOP {n} mais visitados",
"exclusiveToday": "Os dados de hoje não estão incluídos na média",
"countTotal": "Total de visitas/sites",
@@ -152,9 +152,6 @@
"hour": "Кожну годину",
"twoHour": "Кожні 2 години"
},
- "averageLabel": "Середнє за день",
- "yAxisMin": "Час перегляду за хвилину",
- "yAxisHour": "Час перегляду за годину",
"title": "Проміжки часу",
"busiest": "Найзавантаженіший час доби",
"idle": "Найдовший період бездіяльності"
@@ -164,9 +161,6 @@
},
"site": {
"title": "Вебсайти",
- "focusPieTitle": "Відсоток часу перегляду",
- "visitPieTitle": "Відсоток відвідувань",
- "otherLabel": "Інші сайти: {count}",
"histogramTitle": "{n} найвідвідуваніших сайтів",
"exclusiveToday": "Сьогоднішні дані не включені в середній показник",
"countTotal": "Всього відвідувань/сайтів",
@@ -181,9 +175,6 @@
"title": "Hábito de períodos de tiempo",
"busiest": "Hora más ocupada del día",
"idle": "Periodo inactivo más largo",
- "yAxisMin": "Tiempo de navegación / minuto",
- "yAxisHour": "Tiempo de navegación / hora",
- "averageLabel": "Promedio diario",
"sizes": {
"fifteen": "Por 15 minutos",
"halfHour": "Por media hora",
@@ -193,9 +184,6 @@
},
"site": {
"title": "Hábito de sitios web",
- "focusPieTitle": "Porcentaje de tiempo de navegación",
- "visitPieTitle": "Porcentaje de visitas",
- "otherLabel": "Otros {count} sitios",
"histogramTitle": "TOP {n} más visitados",
"exclusiveToday": "Los datos de hoy no están incluidos en el promedio",
"countTotal": "Total de visitas/sitios",
@@ -210,9 +198,6 @@
"title": "Gewohnheiten jeden Augenblick",
"busiest": "Die geschäftigste Zeit des Tages",
"idle": "Längste Leerlaufzeit",
- "yAxisMin": "Browsing-Zeit / Minute",
- "yAxisHour": "Browsing-Zeit / Stunde",
- "averageLabel": "Täglicher Durchschnitt",
"sizes": {
"fifteen": "Pro 15 Minuten",
"halfHour": "Pro halbe Stunde",
@@ -222,9 +207,6 @@
},
"site": {
"title": "Gewohnheit von Websites",
- "focusPieTitle": "Prozentsatz der Anzeigezeit",
- "visitPieTitle": "Prozentsatz der Besuche",
- "otherLabel": "{count} andere Websites",
"histogramTitle": "TOP {n} der meistbesuchten",
"exclusiveToday": "Der Durchschnitt berücksichtigt nicht die heutigen Daten",
"countTotal": "Gesamtzahl der Besuche/Websites",
@@ -239,9 +221,6 @@
"title": "Habitude des périodes de temps",
"busiest": "Période de la journée la plus chargée",
"idle": "La plus longue période d'inactivité",
- "yAxisMin": "Temps / minute de navigation",
- "yAxisHour": "Temps / heure de navigation",
- "averageLabel": "Moyenne quotidienne",
"sizes": {
"fifteen": "Par tranche de 15 minutes",
"halfHour": "Par demi-heure",
@@ -251,9 +230,6 @@
},
"site": {
"title": "Habitude des sites web",
- "focusPieTitle": "Pourcentage de temps de navigation",
- "visitPieTitle": "Pourcentage des visites",
- "otherLabel": "{count} autres sites",
"histogramTitle": "Top {n} des plus visités",
"exclusiveToday": "Les données d'aujourd'hui ne sont pas incluses dans la moyenne",
"countTotal": "Nombre total de visites/sites",
diff --git a/src/i18n/message/app/habit.ts b/src/i18n/message/app/habit.ts
index 19e03e08..25282083 100644
--- a/src/i18n/message/app/habit.ts
+++ b/src/i18n/message/app/habit.ts
@@ -15,9 +15,11 @@ export type HabitMessage = {
title: string
busiest: string
idle: string
- yAxisMin: string
- yAxisHour: string
- averageLabel: string
+ chartType: {
+ average: string
+ trend: string
+ stack: string
+ }
sizes: {
fifteen: string
halfHour: string
@@ -27,13 +29,20 @@ export type HabitMessage = {
}
site: {
title: string
- focusPieTitle: string
- visitPieTitle: string
- otherLabel: string
histogramTitle: string
exclusiveToday: string
countTotal: string
siteAverage: string
+ distribution: {
+ title: string
+ aveTime: string
+ aveVisit: string
+ tooltip: string
+ }
+ trend: {
+ siteCount: string
+ title: string
+ }
}
}
diff --git a/src/i18n/message/common/calendar-resource.json b/src/i18n/message/common/calendar-resource.json
index 5e396200..6736cad7 100644
--- a/src/i18n/message/common/calendar-resource.json
+++ b/src/i18n/message/common/calendar-resource.json
@@ -13,13 +13,7 @@
"today": "今天",
"yesterday": "昨天",
"everyday": "每天",
- "last24Hours": "最近 24 小时",
- "last3Days": "最近 3 天",
- "last7Days": "最近 7 天",
- "last15Days": "最近 15 天",
- "last30Days": "最近 30 天",
- "last60Days": "最近 60 天",
- "last90Days": "最近 90 天"
+ "lastDays": "最近 {n} 天"
}
},
"zh_TW": {
@@ -35,14 +29,8 @@
"range": {
"today": "今天",
"yesterday": "昨天",
- "last24Hours": "最近 24 小時",
- "last3Days": "最近 3 天",
- "last7Days": "最近 7 天",
- "last15Days": "最近 15 天",
- "last30Days": "最近 30 天",
- "last60Days": "最近 60 天",
- "last90Days": "最近 90 天",
- "everyday": "每天"
+ "everyday": "每天",
+ "lastDays": "最近 {n} 天"
}
},
"en": {
@@ -59,13 +47,7 @@
"today": "Today",
"yesterday": "Yesterday",
"everyday": "Everyday",
- "last24Hours": "Last 24 hours",
- "last3Days": "Last 3 days",
- "last7Days": "Last 7 days",
- "last15Days": "Last 15 days",
- "last30Days": "Last 30 days",
- "last60Days": "Last 60 days",
- "last90Days": "Last 90 days"
+ "lastDays": "Last {n} days"
}
},
"ja": {
@@ -81,14 +63,8 @@
"range": {
"today": "今日",
"yesterday": "昨日",
- "last24Hours": "過去 24 時間",
- "last3Days": "過去 3 日間",
- "last7Days": "過去 7 日間",
- "last15Days": "過去 15 日間",
- "last30Days": "過去 30 日間",
- "last60Days": "過去 60 日間",
- "last90Days": "過去 90 日間",
- "everyday": "毎日"
+ "everyday": "毎日",
+ "lastDays": "過去 {n} 日間"
}
},
"pt_PT": {
@@ -104,14 +80,8 @@
"range": {
"today": "Hoje",
"yesterday": "Ontem",
- "last3Days": "Últimos 3 dias",
- "last7Days": "Últimos 7 dias",
- "last15Days": "Últimos 15 dias",
- "last30Days": "Últimos 30 dias",
- "last60Days": "Últimos 60 dias",
- "last90Days": "Últimos 90 dias",
- "last24Hours": "Últimas 24 horas",
- "everyday": "Diariamente"
+ "everyday": "Diariamente",
+ "lastDays": "Últimos {n} dias"
}
},
"uk": {
@@ -126,14 +96,8 @@
"range": {
"today": "Сьогодні",
"yesterday": "Учора",
- "last24Hours": "Минулі 24 години",
- "last3Days": "Минулі 3 дні",
- "last7Days": "Минулих 7 днів",
- "last15Days": "Минулих 15 днів",
- "last30Days": "Минулих 30 днів",
- "last60Days": "Минулих 60 днів",
- "last90Days": "Минулих 90 днів",
- "everyday": "Щодня"
+ "everyday": "Щодня",
+ "lastDays": "Минулі {n} дні"
},
"simpleTimeFormat": "{d}.{m} {h}:{i}"
},
@@ -149,14 +113,8 @@
"range": {
"today": "Hoy",
"yesterday": "Ayer",
- "last24Hours": "Últimas 24 horas",
- "last3Days": "Últimos 3 días",
- "last7Days": "Últimos 7 días",
- "last15Days": "Últimos 15 días",
- "last30Days": "Últimos 30 días",
- "last60Days": "Últimos 60 días",
- "last90Days": "Últimos 90 días",
- "everyday": "Cada día"
+ "everyday": "Cada día",
+ "lastDays": "Últimos {n} días"
},
"simpleTimeFormat": "{m}/{d} {h}:{i}"
},
@@ -171,14 +129,8 @@
"range": {
"today": "Heute",
"yesterday": "Gestern",
- "last24Hours": "Letzte 24 Stunden",
- "last3Days": "Letzte 3 Tage",
- "last7Days": "Letzte 7 Tage",
- "last15Days": "Letzte 15 Tage",
- "last30Days": "Letzte 30 Tage",
- "last60Days": "Letzte 60 Tage",
- "last90Days": "Letzte 90 Tage",
- "everyday": "Täglich"
+ "everyday": "Täglich",
+ "lastDays": "Letzte {n} Tage"
},
"timeFormat": "{d}/{m}/{y} {h}:{i}:{s}",
"simpleTimeFormat": "{d}/{m} {h}:{i}"
diff --git a/src/i18n/message/common/calendar.ts b/src/i18n/message/common/calendar.ts
index da7d4875..af797ace 100644
--- a/src/i18n/message/common/calendar.ts
+++ b/src/i18n/message/common/calendar.ts
@@ -18,16 +18,10 @@ export type CalendarMessage = {
endDate: string
}
range: {
+ everyday: string
today: string
yesterday: string
- everyday: string
- last24Hours: string
- last3Days: string
- last7Days: string
- last15Days: string
- last30Days: string
- last60Days: string
- last90Days: string
+ lastDays: string
}
}
diff --git a/src/service/components/period-calculator.ts b/src/service/components/period-calculator.ts
index 1f554645..cc072227 100644
--- a/src/service/components/period-calculator.ts
+++ b/src/service/components/period-calculator.ts
@@ -66,6 +66,7 @@ export type MergeConfig = {
}
export function merge(periods: timer.period.Result[], config: MergeConfig): timer.period.Row[] {
+ if (!periods?.length) return []
const result: timer.period.Row[] = []
let { start, end, periodSize } = config
const map: Map = new Map()
diff --git a/src/service/period-service.ts b/src/service/period-service.ts
index 3cd886b6..4a6d26b6 100644
--- a/src/service/period-service.ts
+++ b/src/service/period-service.ts
@@ -33,15 +33,18 @@ function dateStrBetween(startDate: timer.period.Key, endDate: timer.period.Key):
}
-async function list(param?: PeriodQueryParam): Promise {
+async function listBetween(param?: PeriodQueryParam): Promise {
const [start, end] = param?.periodRange || []
const allDates = dateStrBetween(start, end)
return periodDatabase.getBatch(allDates)
}
+
+
class PeriodService {
add = add
- list = list
+ listBetween = listBetween
+ listAll = () => periodDatabase.getAll()
}
export default new PeriodService()
diff --git a/src/util/constant/limit.ts b/src/util/constant/limit.ts
new file mode 100644
index 00000000..c9709502
--- /dev/null
+++ b/src/util/constant/limit.ts
@@ -0,0 +1 @@
+export const DELAY_MILL = 5 * 60 * 1000
\ No newline at end of file
diff --git a/src/util/date-iterator.ts b/src/util/date-iterator.ts
index 44ec747d..8934fe81 100644
--- a/src/util/date-iterator.ts
+++ b/src/util/date-iterator.ts
@@ -45,7 +45,7 @@ export default class DateIterator {
}
}
- forEach(callback: (yearMonth: string) => void) {
+ forEach(callback: (yearMonthDate: string) => void) {
while (this.hasNext()) {
callback(this.next().value)
}
diff --git a/src/util/echarts.ts b/src/util/echarts.ts
deleted file mode 100644
index fd567f02..00000000
--- a/src/util/echarts.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { getCssVariable } from "@util/style"
-import { ZRColor } from "echarts/types/dist/shared"
-
-export const echartsPalette: () => ZRColor[] = () => [
- getCssVariable("--el-color-primary"),
- getCssVariable("--el-color-success"),
- getCssVariable("--el-color-warning"),
- getCssVariable("--el-color-danger"),
-]
diff --git a/src/util/period.ts b/src/util/period.ts
index d1be5513..02322fbe 100644
--- a/src/util/period.ts
+++ b/src/util/period.ts
@@ -30,6 +30,7 @@ export function copyKeyWith(old: timer.period.Key, newOrder: number): timer.peri
}
export function indexOf(key: timer.period.Key): number {
+ if (!key) return 0
const { year, month, date, order } = key
return (year << 18)
| (month << 14)
@@ -118,4 +119,36 @@ export function calcMostPeriodOf2Hours(rows: timer.period.Result[]): number {
.reverse()[0]?.[0]
)
return most2Hour
-}
\ No newline at end of file
+}
+
+function generateOrderMap(data: timer.period.Row[], periodSize: number): Map {
+ const map: Map = new Map()
+ data.forEach(item => {
+ const key = Math.floor(startOrderOfRow(item) / periodSize)
+ const val = map.get(key) || 0
+ map.set(key, val + item.milliseconds)
+ })
+ return map
+}
+
+function cvt2AverageResult(map: Map, periodSize: number, dateNum: number): timer.period.Row[] {
+ const result = []
+ let period = keyOf(new Date(), 0)
+ for (let i = 0; i < PERIOD_PER_DATE / periodSize; i++) {
+ const key = period.order / periodSize
+ const val = map.get(key) ?? 0
+ const averageMill = Math.round(val / dateNum)
+ result.push(rowOf(after(period, periodSize - 1), periodSize, averageMill))
+ period = after(period, periodSize)
+ }
+ return result
+}
+
+export function averageByDay(data: timer.period.Row[], periodSize: number): timer.period.Row[] {
+ if (!data?.length) return []
+ const rangeStart = data[0]?.startTime
+ const rangeEnd = data[data.length - 1]?.endTime
+ const dateNum = (rangeEnd.getTime() - rangeStart.getTime()) / MILL_PER_DAY
+ const map = generateOrderMap(data, periodSize)
+ return cvt2AverageResult(map, periodSize, dateNum)
+}
diff --git a/src/util/style.ts b/src/util/style.ts
index 4cd02a6d..890750b8 100644
--- a/src/util/style.ts
+++ b/src/util/style.ts
@@ -17,6 +17,10 @@ export function getPrimaryTextColor(): string {
return getCssVariable("--el-text-color-primary")
}
+export function getRegularTextColor(): string {
+ return getCssVariable("--el-text-color-regular")
+}
+
export function getSecondaryTextColor(): string {
return getCssVariable("--el-text-color-secondary")
}
diff --git a/src/util/time.ts b/src/util/time.ts
index 9d3c824b..3ccc2193 100644
--- a/src/util/time.ts
+++ b/src/util/time.ts
@@ -95,16 +95,18 @@ export function formatPeriodCommon(milliseconds: number): string {
return formatPeriod(milliseconds, defaultMessage)
}
+export const MILL_PER_SECOND = 1000
+
+export const MILL_PER_MINUTE = MILL_PER_SECOND * 60
+
+export const MILL_PER_HOUR = MILL_PER_MINUTE * 60
+
/**
* Milliseconds per day
*
* @since 0.0.8
*/
-export const MILL_PER_DAY = 3600 * 1000 * 24
-
-export const MILL_PER_MINUTE = 1000 * 60
-
-export const MILL_PER_HOUR = MILL_PER_MINUTE * 60
+export const MILL_PER_DAY = MILL_PER_HOUR * 24
/**
* Date range between {start} days ago and {end} days ago
diff --git a/src/util/tuple.ts b/src/util/tuple.ts
new file mode 100644
index 00000000..6de10731
--- /dev/null
+++ b/src/util/tuple.ts
@@ -0,0 +1,47 @@
+import { range } from "./array"
+
+const isTuple = (arg: unknown): arg is Tuple => {
+ if (Array.isArray(arg)) return true
+ if (!arg.hasOwnProperty?.("get")) return false
+ const predicate = arg as Tuple
+ const len = predicate?.length
+ return typeof len === 'number' && !isNaN(len) && isFinite(len) && len >= 0 && Number.isInteger(len)
+}
+
+/**
+ * Add tuple
+ */
+export const addVector = (a: Vector, toAdd: Vector | number): Vector => {
+ const l: L = a.length ?? 0 as L
+ if (isTuple(toAdd)) {
+ return range(l).map(idx => (a?.[idx] ?? 0) + (toAdd?.[idx] ?? 0)) as unknown as Vector
+ } else {
+ return a?.map(v => (v ?? 0) + ((toAdd as number) ?? 0)) as unknown as Vector
+ }
+}
+
+/**
+ * Subtract tuple
+ */
+export const subVector = (a: Vector, toSub: Vector | number): Vector => {
+ const l: L = a.length ?? 0 as L
+ if (isTuple(toSub)) {
+ return range(l).map(idx => (a?.[idx] ?? 0) - (toSub?.[idx] ?? 0)) as unknown as Vector
+ } else {
+ return a?.map(v => (v ?? 0) + ((toSub as number) ?? 0)) as unknown as Vector
+ }
+}
+
+/**
+ * Multiple tuple
+ */
+export const multiTuple = (a: Vector, multiFactor: number): Vector => {
+ return a?.map(v => (v ?? 0) * (multiFactor ?? 0)) as unknown as Vector
+}
+
+/**
+ * Divide tuple
+ */
+export const divideTuple = (a: Vector, divideFactor: number): Vector => {
+ return a?.map(v => (v ?? 0) / (divideFactor ?? 0)) as unknown as Vector
+}
diff --git a/test/database/stat-database.test.ts b/test/database/stat-database.test.ts
index 60299019..917ae75c 100644
--- a/test/database/stat-database.test.ts
+++ b/test/database/stat-database.test.ts
@@ -97,7 +97,7 @@ describe('stat-database', () => {
// time [2, 3]
cond.timeRange = [2, 3]
- cond.focusRange = []
+ cond.focusRange = [, null]
expect((await db.select(cond)).length).toEqual(2)
})
diff --git a/types/common.d.ts b/types/common.d.ts
index 7e6640f4..9b41484b 100644
--- a/types/common.d.ts
+++ b/types/common.d.ts
@@ -6,3 +6,23 @@ declare type EmbeddedPartial = {
? ReadonlyArray>
: EmbeddedPartial;
}
+
+/**
+ * Tuple with length
+ *
+ * @param E element
+ * @param L length of tuple
+ */
+declare type Tuple]> =
+ Pick>
+ & {
+ readonly length: L
+ [I: number]: E
+ }
+
+/**
+ * Vector
+ *
+ * @param D dimension of vector
+ */
+declare type Vector = Tuple
\ No newline at end of file
diff --git a/types/timer/limit.d.ts b/types/timer/limit.d.ts
index 62e5e8e0..7c3996b7 100644
--- a/types/timer/limit.d.ts
+++ b/types/timer/limit.d.ts
@@ -5,7 +5,7 @@ declare namespace timer.limit {
* [0, 120] means from 00:00 to 02:00
* @since 2.0.0
*/
- type Period = [number, number]
+ type Period = Vector<2>
/**
* Limit rule in runtime
*