wukong-gitlog-cli 1.0.42 → 1.0.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [1.0.44](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.43...v1.0.44) (2026-02-14)
6
+
7
+ ### [1.0.43](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.42...v1.0.43) (2026-02-13)
8
+
9
+
10
+ ### Features
11
+
12
+ * 🎸 autoCheckUpdate ([d377012](https://github.com/tomatobybike/wukong-gitlog-cli/commit/d3770123f10c574f0f5ea592ef58e71bfdd8edc1))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * 🐛 update version ([840216d](https://github.com/tomatobybike/wukong-gitlog-cli/commit/840216d4f553e5d78f84e42f76386056553d1282))
18
+
5
19
  ### [1.0.42](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.41...v1.0.42) (2026-02-13)
6
20
 
7
21
  ### [1.0.41](https://github.com/tomatobybike/wukong-gitlog-cli/compare/v1.0.40...v1.0.41) (2026-02-12)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wukong-gitlog-cli",
3
- "version": "1.0.42",
3
+ "version": "1.0.44",
4
4
  "description": "Advanced Git commit log exporter with Excel/JSON/TXT output, grouping, stats and CLI.",
5
5
  "keywords": [
6
6
  "git",
@@ -97,35 +97,37 @@
97
97
  "#utils/*": "./src/utils/*"
98
98
  },
99
99
  "dependencies": {
100
- "@inquirer/prompts": "^8.1.0",
100
+ "@inquirer/prompts": "8.2.0",
101
+ "boxen": "8.0.1",
101
102
  "chalk": "5.6.2",
102
- "commander": "12.1.0",
103
+ "commander": "14.0.3",
103
104
  "date-fns": "4.1.0",
104
- "date-holidays": "2.1.1",
105
+ "date-holidays": "3.26.8",
105
106
  "dayjs": "1.11.19",
106
- "dotenv": "17.2.2",
107
+ "dotenv": "17.3.1",
107
108
  "exceljs": "4.4.0",
108
109
  "fs-extra": "11.3.3",
109
- "ora": "9.0.0",
110
+ "is-online": "12.0.2",
111
+ "ora": "9.3.0",
110
112
  "os-locale": "^8.0.0",
111
- "string-width": "5.1.2",
113
+ "string-width": "8.1.1",
112
114
  "wukong-profiler": "^1.0.11",
113
- "wukong-progress": "^0.1.2"
115
+ "wukong-progress": "^0.1.3"
114
116
  },
115
117
  "devDependencies": {
116
- "@trivago/prettier-plugin-sort-imports": "5.2.2",
118
+ "@trivago/prettier-plugin-sort-imports": "6.0.2",
117
119
  "eslint": "8.57.1",
118
120
  "eslint-config-airbnb-base": "15.0.0",
119
121
  "eslint-config-prettier": "10.1.8",
120
122
  "eslint-import-resolver-alias": "1.1.2",
121
123
  "eslint-plugin-import": "2.32.0",
122
- "eslint-plugin-prettier": "5.5.4",
124
+ "eslint-plugin-prettier": "5.5.5",
123
125
  "eslint-plugin-simple-import-sort": "12.1.1",
124
126
  "husky": "^9.1.7",
125
127
  "lint-staged": "^16.2.7",
126
- "prettier": "3.6.2",
127
- "prettier-plugin-packagejson": "2.5.19",
128
- "sort-package-json": "3.4.0",
128
+ "prettier": "3.8.1",
129
+ "prettier-plugin-packagejson": "3.0.0",
130
+ "sort-package-json": "3.6.1",
129
131
  "standard-version": "9.5.0"
130
132
  },
131
133
  "packageManager": "yarn@1.22.22"
@@ -48,7 +48,12 @@ export async function serveAction(rawOpts = {}) {
48
48
  for (let i = 0; i < maxTries; i++) {
49
49
  try {
50
50
  // eslint-disable-next-line no-await-in-loop
51
- server = await startServer(port, dir, data)
51
+ server = await startServer({
52
+ port,
53
+ outputDir: dir,
54
+ data,
55
+ lang: rawOpts.lang
56
+ })
52
57
  break
53
58
  } catch (err) {
54
59
  // 端口被占用,尝试下一个端口;其它错误抛出
@@ -0,0 +1,31 @@
1
+ export function createCommand(program, {
2
+ name,
3
+ description,
4
+ optionsBuilder,
5
+ handler,
6
+ autoUpdate,
7
+ lang
8
+ }) {
9
+ const cmd = program.command(name).description(description)
10
+
11
+ // 注入 options
12
+ if (optionsBuilder) {
13
+ optionsBuilder(cmd)
14
+ }
15
+
16
+ cmd.action(async (cmdOpts, command) => {
17
+ const globalOpts = command.parent?.opts?.() || {}
18
+ const finalOpts = { ...globalOpts, ...cmdOpts,lang }
19
+
20
+ try {
21
+ await handler(finalOpts)
22
+ } finally {
23
+ if (autoUpdate) {
24
+ // 不阻塞 CLI
25
+ autoUpdate().catch(() => {})
26
+ }
27
+ }
28
+ })
29
+
30
+ return cmd
31
+ }
package/src/index.mjs CHANGED
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander'
3
+ import fs from 'fs'
4
+ import path from 'path'
5
+ import { fileURLToPath } from 'url'
3
6
 
4
7
  import { runGitPreflight, showGitInfo } from '#src/domain/git/index.mjs'
5
8
 
@@ -11,6 +14,7 @@ import { journalAction } from './app/journalAction.mjs'
11
14
  import { overtimeAction } from './app/overtimeAction.mjs'
12
15
  import { serveAction } from './app/serveAction.mjs'
13
16
  import { versionAction } from './app/versionAction.mjs'
17
+ import { createCommand } from './cli/createCommand.mjs'
14
18
  import {
15
19
  addAnalysisOptions,
16
20
  addGitSourceOptions,
@@ -21,9 +25,38 @@ import {
21
25
  } from './cli/defineOptions.mjs'
22
26
  import { initI18n, t } from './i18n/index.mjs'
23
27
  import { loadRcConfig } from './infra/configStore.mjs'
28
+ import { checkUpdateWithPatch } from './utils/checkUpdate.mjs'
24
29
 
25
30
  // 引入加载器
26
31
 
32
+ const __filename = fileURLToPath(import.meta.url)
33
+ const __dirname = path.dirname(__filename)
34
+
35
+ const pkgPath = path.resolve(__dirname, '../package.json')
36
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
37
+
38
+ const autoCheckUpdate = async () => {
39
+ // 避免 CI 打印更新提示
40
+ if (process.env.CI) return
41
+ // CLI 更新提示只在 global 安装时出现
42
+ if (process.env.npm_config_global !== 'true') return
43
+ if (!process.stdout.isTTY) return
44
+
45
+ // === CLI 主逻辑完成后提示更新 ===
46
+ // await checkUpdateWithPatch({
47
+ // pkg: {
48
+ // name: pkg.name,
49
+ // version: pkg.version
50
+ // },
51
+ // // force:true
52
+ // })
53
+
54
+ // 放到下一个 tick
55
+ setTimeout(() => {
56
+ checkUpdateWithPatch({ pkg }).catch(() => {})
57
+ }, 0)
58
+ }
59
+
27
60
  const main = async () => {
28
61
  // --- 第一步:参数预检 (Pre-flight) ---
29
62
  const args = process.argv.slice(2)
@@ -37,6 +70,8 @@ const main = async () => {
37
70
  // 这一步必须在定义子命令描述之前完成!
38
71
  const finalLang = await initI18n(userLang)
39
72
 
73
+
74
+
40
75
  // ---------------------------------------------------------
41
76
  // 2. 环境准备
42
77
  // ---------------------------------------------------------
@@ -67,85 +102,79 @@ const main = async () => {
67
102
 
68
103
  // === 命令: Init ===
69
104
 
70
- program
71
- .command('init')
72
- .description(t('cmds.init'))
73
- .option('-f, --force', t('options.force'))
74
- .action(async (options) => {
75
- // 如果你不想抽离到 app 层,也可以直接写这里
76
- // 但推荐抽离以保持架构一致性
77
- await initAction(options)
78
- })
105
+ createCommand(program, {
106
+ name: 'init',
107
+ description: t('cmds.init'),
108
+ optionsBuilder: (cmd) => {
109
+ cmd.option('-f, --force', t('options.force'))
110
+ },
111
+ handler: initAction,
112
+ autoUpdate: autoCheckUpdate
113
+ })
79
114
 
80
115
  // # 命令: Analyze 核心分析(默认) 分析 git 提交记录 (最全参数)
81
- const analyzeCmd = program
82
- .command('analyze')
83
- .description(t('cmds.analyze'))
84
- .action((cmdOpts) => {
85
- const globalOpts = program.opts()
86
- const finalOpts = { ...globalOpts, ...cmdOpts }
87
- analyzeAction(finalOpts)
88
- })
89
-
90
- // 挂载 analyze 需要的参数组
91
- addGitSourceOptions(analyzeCmd)
92
- addAnalysisOptions(analyzeCmd)
93
- addOutputOptions(analyzeCmd)
94
- addPerformanceOptions(analyzeCmd)
116
+ createCommand(program, {
117
+ name: 'analyze',
118
+ description: t('cmds.analyze'),
119
+ optionsBuilder: (cmd) => {
120
+ addGitSourceOptions(cmd)
121
+ addAnalysisOptions(cmd)
122
+ addOutputOptions(cmd)
123
+ addPerformanceOptions(cmd)
124
+ },
125
+ handler: analyzeAction,
126
+ autoUpdate: autoCheckUpdate
127
+ })
95
128
 
96
129
  // # 命令: Overtime 加班文化分析
97
- const overtimeCmd = program
98
- .command('overtime')
99
- .description(t('cmds.overtime'))
100
- .action((cmdOpts) => {
101
- const globalOpts = program.opts()
102
- const finalOpts = { ...globalOpts, ...cmdOpts }
103
- overtimeAction(finalOpts)
104
- })
105
- addGitSourceOptions(overtimeCmd)
106
- addAnalysisOptions(overtimeCmd) // 加班分析肯定需要上班时间配置
130
+ createCommand(program, {
131
+ name: 'overtime',
132
+ description: t('cmds.overtime'),
133
+ optionsBuilder: (cmd) => {
134
+ addGitSourceOptions(cmd)
135
+ addAnalysisOptions(cmd)
136
+ },
137
+ handler: overtimeAction,
138
+ autoUpdate: autoCheckUpdate
139
+ })
107
140
 
108
141
  // # 命令: Export (专注导出) 导出(excel / csv / json)
109
- const exportCmd = program
110
- .command('export')
111
- .description(t('cmds.export'))
112
- // .option('-f, --format <type>', '导出格式') // 局部参数
113
- .action((cmdOpts, command) => {
114
- // globalOpts 拿到 author, since 等
115
- const globalOpts = command.parent.opts()
116
- // 合并全局和局部参数
117
- const finalOpts = { ...globalOpts, ...cmdOpts }
118
- exportAction(finalOpts)
119
- })
120
- addGitSourceOptions(exportCmd)
121
- addOutputOptions(exportCmd)
142
+ createCommand(program, {
143
+ name: 'export',
144
+ description: t('cmds.export'),
145
+ optionsBuilder: (cmd) => {
146
+ addGitSourceOptions(cmd)
147
+ addOutputOptions(cmd)
148
+ },
149
+ handler: exportAction,
150
+ autoUpdate: autoCheckUpdate
151
+ })
122
152
 
123
153
  // === 命令: Journal (日报) ===
124
- const journalCmd = program
125
- .command('journal')
126
- .description(t('cmds.journal'))
127
- .action((cmdOpts) => {
128
- const globalOpts = program.opts()
129
- const finalOpts = { ...globalOpts, ...cmdOpts }
130
- journalAction(finalOpts)
131
- })
132
-
133
- addGitSourceOptions(journalCmd)
134
-
135
- addPerformanceOptions(journalCmd)
154
+ createCommand(program, {
155
+ name: 'journal',
156
+ description: t('cmds.journal'),
157
+ optionsBuilder: (cmd) => {
158
+ addGitSourceOptions(cmd)
159
+ addPerformanceOptions(cmd)
160
+ },
161
+ handler: journalAction,
162
+ autoUpdate: autoCheckUpdate
163
+ })
136
164
 
137
165
  // === 命令: Serve (Web服务) ===
138
- const serveCmd = program
139
- .command('serve')
140
- .description('Start web server')
141
- .action((cmdOpts) => {
142
- const globalOpts = program.opts()
143
- const finalOpts = { ...globalOpts, ...cmdOpts }
144
- serveAction(finalOpts)
145
- })
146
- // Serve 命令只需要端口,不需要 Git 作者之类的参数
147
- addServeOptions(serveCmd)
148
- program.parse(process.argv)
166
+ createCommand(program, {
167
+ name: 'serve',
168
+ description: 'Start web server',
169
+ optionsBuilder: (cmd) => {
170
+ addServeOptions(cmd)
171
+ },
172
+ handler: serveAction,
173
+ autoUpdate: autoCheckUpdate,
174
+ lang:finalLang
175
+ })
176
+
177
+ await program.parseAsync(process.argv)
149
178
 
150
179
  // const opts = program.opts()
151
180
  // console.log('✅ Cli Opts:', opts)
@@ -51,7 +51,7 @@ function findPkgRoot(currentDir) {
51
51
  }
52
52
 
53
53
  // eslint-disable-next-line default-param-last
54
- export function startServer(port = 3000, outputDir) {
54
+ export function startServer({ port = 3000, outputDir, data,lang }) {
55
55
  // 解析包根目录,确保 web 资源在全局安装后也能找到
56
56
  const __filename = fileURLToPath(import.meta.url)
57
57
  const __dirname = path.dirname(__filename)
@@ -64,7 +64,6 @@ export function startServer(port = 3000, outputDir) {
64
64
  ? path.resolve(outputDir)
65
65
  : path.resolve(process.cwd(), 'output-wukong')
66
66
 
67
-
68
67
  // warn if web directory or data directory doesn't exist
69
68
  if (!fs.existsSync(webRoot)) {
70
69
  console.warn(
@@ -113,6 +112,21 @@ export function startServer(port = 3000, outputDir) {
113
112
  'Content-Type',
114
113
  mime.get(ext) || 'application/octet-stream'
115
114
  )
115
+
116
+ // 👇 只对 index.html 注入
117
+ if (pathname === '/index.html') {
118
+ let html = fs.readFileSync(fileLocal, 'utf8')
119
+
120
+ html = html.replace(
121
+ `window.__LANG__ = '__LANG__'`,
122
+ `window.__LANG__ = "${lang}"`
123
+ )
124
+
125
+ res.setHeader('Content-Type', 'text/html; charset=utf-8')
126
+ res.end(html)
127
+ return
128
+ }
129
+
116
130
  const stream = fs.createReadStream(fileLocal)
117
131
  stream.pipe(res)
118
132
  return
@@ -0,0 +1,135 @@
1
+ import boxen from 'boxen'
2
+ import fs from 'fs'
3
+ import isOnline from 'is-online'
4
+ import os from 'os'
5
+ import path from 'path'
6
+ import semver from 'semver'
7
+
8
+ import { colors } from './colors.mjs'
9
+
10
+ const CONFIG_DIR = path.join(os.homedir(), '.config', 'configstore')
11
+
12
+ const getCacheFile = (pkg) =>
13
+ path.join(CONFIG_DIR, `update-notifier-${pkg.name}.json`)
14
+
15
+ async function fetchLatestVersion(pkgName, timeout = 1500) {
16
+ const url = `https://registry.npmjs.org/${pkgName}/latest`
17
+ const controller = new AbortController()
18
+ const timer = setTimeout(() => controller.abort(), timeout)
19
+
20
+ try {
21
+ const res = await fetch(url, { signal: controller.signal })
22
+ clearTimeout(timer)
23
+ if (!res.ok) throw new Error('fetch fail')
24
+ const data = await res.json()
25
+ return data.version
26
+ } catch (e) {
27
+ clearTimeout(timer)
28
+ // 超时或其他错误都返回 null,防止程序卡死
29
+ return null
30
+ }
31
+ }
32
+
33
+ /**
34
+ * 格式化升级提示信息
35
+ */
36
+ export function formatUpdateMessage(current, latest, name) {
37
+ const arrow = colors.dim('→')
38
+ const currentVer = colors.dim(current)
39
+ const latestVer = colors.green(latest)
40
+
41
+ const message =
42
+ `${colors.dim('Update available')}` +
43
+ ` ${currentVer} ${arrow} ${latestVer}\n` +
44
+ `${colors.dim('Run')} ${colors.cyanish(`npm i -g ${name}`)} ${colors.dim(' to update')}`
45
+
46
+ return boxen(message, {
47
+ padding: 1,
48
+ margin: 1,
49
+ borderStyle: 'round',
50
+ borderColor: 'yellow'
51
+ })
52
+ }
53
+
54
+ /**
55
+ * 检查更新(带缓存 + 支持 patch 更新)
56
+ */
57
+ export async function checkUpdateWithPatch({
58
+ pkg,
59
+ interval = 24 * 60 * 60 * 1000,
60
+ // interval = 6 * 1000,
61
+ force = false
62
+ } = {}) {
63
+ const online = await isOnline()
64
+ console.log('online', online)
65
+ if (!online) return null
66
+ const now = Date.now()
67
+ const CACHE_FILE = getCacheFile(pkg)
68
+
69
+ let cache = {}
70
+ if (fs.existsSync(CACHE_FILE)) {
71
+ try {
72
+ cache = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8')) || {}
73
+ // eslint-disable-next-line no-empty
74
+ } catch {}
75
+ }
76
+
77
+ // console.log('now - cache.lastCheck', now - cache.lastCheck, interval)
78
+ if (!force && cache.lastCheck && now - cache.lastCheck < interval) {
79
+ return cache.updateInfo || null
80
+ }
81
+
82
+ const latest = await fetchLatestVersion(pkg.name)
83
+ if (!latest) {
84
+ // console.log('无法获取最新版本')
85
+ return null
86
+ }
87
+
88
+ if (!semver.valid(pkg.version) || !semver.valid(latest)) {
89
+ return null
90
+ }
91
+
92
+
93
+ if (semver.lt(pkg.version, latest)) {
94
+ // 构造 update 对象,兼容 update-notifier
95
+ const updateInfo = {
96
+ current: pkg.version,
97
+ latest,
98
+ type: semver.diff(pkg.version, latest) || 'patch',
99
+ name: pkg.name,
100
+ // 这里官方还会有 message 字段
101
+ // 用官方的默认消息格式生成,方便notify打印
102
+ message: `\nUpdate available ${pkg.version} → ${latest}\nRun npm i -g ${pkg.name} to update\n`
103
+ }
104
+
105
+ // 缓存
106
+ try {
107
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
108
+ fs.writeFileSync(
109
+ CACHE_FILE,
110
+ JSON.stringify(
111
+ {
112
+ lastCheck: now,
113
+ updateInfo
114
+ },
115
+ null,
116
+ 2
117
+ )
118
+ )
119
+ } catch (e) {
120
+ console.error('缓存写入失败:', e)
121
+ }
122
+
123
+ const { current } = updateInfo
124
+ console.log(formatUpdateMessage(current, latest, pkg.name))
125
+ return updateInfo
126
+ }
127
+ // 无更新,刷新缓存时间
128
+ try {
129
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
130
+ fs.writeFileSync(CACHE_FILE, JSON.stringify({ lastCheck: now }, null, 2))
131
+ } catch (e) {
132
+ console.error('缓存写入失败:', e)
133
+ }
134
+ return null
135
+ }
package/web/app.js CHANGED
@@ -3,6 +3,45 @@
3
3
  /* eslint-disable no-restricted-globals */
4
4
  /* global echarts */
5
5
 
6
+ /* ---------------------- 动态加载语言 */
7
+
8
+ import zh from './static/i18n/zh.js'
9
+ import en from './static/i18n/en.js'
10
+
11
+ function resolveLang() {
12
+ // CLI 注入优先
13
+ if (window.__LANG__) return window.__LANG__
14
+
15
+ // 浏览器语言
16
+ if (navigator.language.startsWith('zh')) return 'zh'
17
+
18
+ return 'en'
19
+ }
20
+
21
+ const lang = resolveLang()
22
+
23
+ const messages = {
24
+ zh,
25
+ en
26
+ }[lang] || zh
27
+
28
+ function applyI18n() {
29
+ document.querySelectorAll('[data-i18n]').forEach(el => {
30
+ const key = el.dataset.i18n
31
+ // eslint-disable-next-line no-param-reassign
32
+ el.textContent = messages[key] || key
33
+ })
34
+
35
+ document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
36
+ const key = el.dataset.i18nPlaceholder
37
+ // eslint-disable-next-line no-param-reassign
38
+ el.placeholder = messages[key] || key
39
+ })
40
+ }
41
+
42
+ applyI18n()
43
+
44
+ /* ---------------------- */
6
45
  // 1. 定义一个存储所有实例的数组
7
46
  const chartInstances = []
8
47
  const formatDate = (d) => new Date(d).toLocaleString()
package/web/index.html CHANGED
@@ -3,29 +3,34 @@
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1" />
6
- <title>wukong-gitlog-cli Overtime Dashboard</title>
6
+ <title data-i18n="dashboardTitle">Wukong Gitlog 加班分析看板</title>
7
7
  <link rel="stylesheet" href="./static/style.css" />
8
+ <script>
9
+ window.__LANG__ = '__LANG__'
10
+ </script>
8
11
  <script src="./static/echarts.min.js"></script>
9
12
  </head>
10
13
  <body>
11
14
  <header>
12
- <h1>Wukong Gitlog Overtime Dashboard</h1>
15
+ <h1 data-i18n="dashboardTitle"></h1>
13
16
  <div id="samplingInfo" class="sampling-info"></div>
14
17
  </header>
15
18
  <main id="main">
16
19
  <section id="chartsHalf">
17
20
  <div class="chart-card">
18
- <h2>下班时间 vs 工作时间提交占比</h2>
21
+ <!-- <h2>下班时间 vs 工作时间提交占比</h2> -->
22
+ <h2 data-i18n="outsideVsInside"></h2>
23
+
19
24
  <div id="outsideVsInsideChart" class="echart"></div>
20
25
  </div>
21
26
  <div class="chart-card" id="kpiCard">
22
- <h2>关键指标</h2>
27
+ <h2 data-i18n="kpi">关键指标</h2>
23
28
  <div id="kpiContent"></div>
24
29
  </div>
25
30
  </section>
26
31
  <section id="charts">
27
32
  <div class="chart-card">
28
- <h2>每小时加班分布 (小时 -> 提交数)</h2>
33
+ <h2 data-i18n="hourlyOvertime">每小时加班分布(小时 -> 提交数)</h2>
29
34
  <div
30
35
  id="hourlyOvertimeChart"
31
36
  data-title="每小时加班分布"
@@ -34,7 +39,7 @@
34
39
  </div>
35
40
 
36
41
  <div class="chart-card">
37
- <h2>按日提交趋势(次数)</h2>
42
+ <h2 data-i18n="dailyTrend">按日提交趋势(次数)</h2>
38
43
  <div
39
44
  id="dailyTrendChart"
40
45
  data-title="按日提交趋势(次数)"
@@ -42,7 +47,7 @@
42
47
  ></div>
43
48
  </div>
44
49
  <div class="chart-card">
45
- <h2>按日提交趋势(次数)</h2>
50
+ <h2 data-i18n="dailyTrend">按日提交趋势(次数)</h2>
46
51
  <div
47
52
  id="dailyTrendChartDog"
48
53
  data-title="按日提交趋势"
@@ -50,7 +55,7 @@
50
55
  ></div>
51
56
  </div>
52
57
  <div class="chart-card">
53
- <h2>每日最晚提交时间(小时)</h2>
58
+ <h2 data-i18n="latestHourDaily">每日最晚提交时间(小时)</h2>
54
59
  <div
55
60
  id="latestHourDailyChart"
56
61
  data-title="每日最晚提交时间(小时)"
@@ -58,7 +63,7 @@
58
63
  ></div>
59
64
  </div>
60
65
  <div class="chart-card">
61
- <h2>每日超过下班的小时数</h2>
66
+ <h2 data-i18n="dailySeverity">每日超过下班的小时数</h2>
62
67
  <div
63
68
  id="dailySeverityChart"
64
69
  data-title="每日超过下班的小时数"
@@ -67,7 +72,7 @@
67
72
  </div>
68
73
 
69
74
  <div class="chart-card">
70
- <h2>每周趋势(加班占比)</h2>
75
+ <h2 data-i18n="weeklyTrend">每周趋势(加班占比)</h2>
71
76
  <div
72
77
  id="weeklyTrendChart"
73
78
  data-title="每周趋势(加班占比)"
@@ -75,7 +80,7 @@
75
80
  ></div>
76
81
  </div>
77
82
  <div class="chart-card">
78
- <h2>每月趋势(加班占比)</h2>
83
+ <h2 data-i18n="monthlyTrend">每月趋势(加班占比)</h2>
79
84
  <div
80
85
  id="monthlyTrendChart"
81
86
  data-title="每月趋势(加班占比)"
@@ -93,10 +98,8 @@
93
98
  </div>
94
99
  </section>
95
100
 
96
-
97
-
98
101
  <div class="chart-card">
99
- <h2>开发者 Changed 工作量趋势(行数)</h2>
102
+ <h2 data-i18n="authorChanges">开发者 Changed 工作量趋势(行数)</h2>
100
103
 
101
104
  <div id="tabs" class="tabs">
102
105
  <button data-type="daily" class="active">按日</button>
@@ -107,7 +110,7 @@
107
110
  </div>
108
111
 
109
112
  <div class="chart-card">
110
- <h2>开发者 加班最晚趋势(小时)</h2>
113
+ <h2 data-i18n="authorLatestOvertime">开发者 加班最晚趋势(小时)</h2>
111
114
  <div class="tabs" id="tabsLatestOvertime">
112
115
  <button data-type="daily" class="active">按日</button>
113
116
  <button data-type="weekly">按周</button>
@@ -117,7 +120,7 @@
117
120
  </div>
118
121
 
119
122
  <div class="chart-card">
120
- <h2>开发者 加班趋势(次数)</h2>
123
+ <h2 data-i18n="authorOvertime">开发者 加班趋势(次数)</h2>
121
124
  <div class="tabs" id="tabsOvertime">
122
125
  <button data-type="daily" class="active">按日</button>
123
126
  <button data-type="weekly">按周</button>
@@ -134,7 +137,7 @@
134
137
  </div>
135
138
 
136
139
  <div class="chart-card">
137
- <h2>开发者 午休最晚提交(小时)</h2>
140
+ <h2 data-i18n="authorLunch">开发者 午休最晚提交(小时)</h2>
138
141
  <div class="tabs" id="tabsLunch">
139
142
  <button data-type="daily" class="active">按日</button>
140
143
  <button data-type="weekly">按周</button>
@@ -149,7 +152,7 @@
149
152
 
150
153
  <!-- 午休 分析 -->
151
154
  <div class="chart-card">
152
- <h2>开发者 累计午休工作时长</h2>
155
+ <h2 data-i18n="totalLunchTime">开发者 累计午休工作时长</h2>
153
156
  <div class="tabs" id="tabsTotalLunchTime">
154
157
  <button data-type="daily" class="active">按日</button>
155
158
  <button data-type="weekly">按周</button>
@@ -161,18 +164,18 @@
161
164
 
162
165
  <section class="chartsHalf">
163
166
  <div class="chart-card">
164
- <h2>开发者 午休工作总时长</h2>
167
+ <h2 data-i18n="totalLunchTime">开发者 午休工作总时长</h2>
165
168
  <div id="authorTotalLunchTimeRank" class="rank-list"></div>
166
169
  </div>
167
170
  <div class="chart-card">
168
- <h2>开发者 午休工作总时长</h2>
171
+ <h2 data-i18n="totalLunchTimeAll">开发者 午休工作总时长</h2>
169
172
  <div id="authorTotalLunchTimeRankSummary" style="height: 600px"></div>
170
173
  </div>
171
174
  </section>
172
175
  <!-- 午休 分析 End -->
173
176
 
174
177
  <div class="chart-card">
175
- <h2>开发者 累计加班时长</h2>
178
+ <h2 data-i18n="totalOvertime">开发者 累计加班时长</h2>
176
179
  <div class="tabs" id="tabsTotalOvertime">
177
180
  <button data-type="daily" class="active">按日</button>
178
181
  <button data-type="weekly">按周</button>
@@ -184,18 +187,18 @@
184
187
 
185
188
  <section class="chartsHalf">
186
189
  <div class="chart-card">
187
- <h2>开发者 加班总时长</h2>
190
+ <h2 data-i18n="totalOvertimeAll">开发者 加班总时长</h2>
188
191
  <div id="authorTotalOvertimeRank" class="rank-list"></div>
189
192
  </div>
190
193
  <div class="chart-card">
191
- <h2>开发者 加班总时长</h2>
194
+ <h2 data-i18n="totalOvertimeAll">开发者 加班总时长</h2>
192
195
  <div id="authorTotalOvertimeRankSummary" style="height: 600px"></div>
193
196
  </div>
194
197
  </section>
195
198
 
196
199
  <!-- 开发者 累计提交次数 -->
197
200
  <div class="chart-card">
198
- <h2>开发者 累计提交次数</h2>
201
+ <h2 data-i18n="totalCommits">开发者 累计提交次数</h2>
199
202
  <div class="tabs" id="tabsTotalCommits">
200
203
  <button data-type="daily" class="active">按日</button>
201
204
  <button data-type="weekly">按周</button>
@@ -207,19 +210,19 @@
207
210
 
208
211
  <section class="chartsHalf">
209
212
  <div class="chart-card">
210
- <h2>开发者 提交总次数</h2>
213
+ <h2 data-i18n="totalCommitsCount">开发者 提交总次数</h2>
211
214
  <div id="authorTotalCommitsRank" class="rank-list"></div>
212
215
  </div>
213
216
  <div class="chart-card">
214
- <h2>开发者 提交总次数</h2>
217
+ <h2 data-i18n="totalCommitsCount">开发者 提交总次数</h2>
215
218
  <div id="authorTotalCommitsRankSummary" style="height: 600px"></div>
216
219
  </div>
217
220
  </section>
218
221
  <!-- 开发者 累计提交次数 End -->
219
222
 
220
- <!-- 开发者 累计提交Changed -->
223
+ <!-- 开发者 累计提交Changed -->
221
224
  <div class="chart-card" id="totalCommitsChangedCard">
222
- <h2>开发者 累计提交Changed </h2>
225
+ <h2 data-i18n="totalCommitsChanged">开发者 累计提交Changed</h2>
223
226
  <div class="tabs" id="tabsTotalCommitsChanged">
224
227
  <button data-type="daily" class="active">按日</button>
225
228
  <button data-type="weekly">按周</button>
@@ -231,12 +234,15 @@
231
234
 
232
235
  <section class="chartsHalf" id="totalCommitsChangedRankSection">
233
236
  <div class="chart-card">
234
- <h2>开发者 提交Changed 总次数</h2>
237
+ <h2 data-i18n="totalCommitsChangedAll">开发者 提交Changed 总次数</h2>
235
238
  <div id="authorTotalCommitsChangedRank" class="rank-list"></div>
236
239
  </div>
237
240
  <div class="chart-card">
238
- <h2>开发者 提交Changed 总次数</h2>
239
- <div id="authorTotalCommitsChangedRankSummary" style="height: 600px"></div>
241
+ <h2 data-i18n="totalCommitsChangedAll">开发者 提交Changed 总次数</h2>
242
+ <div
243
+ id="authorTotalCommitsChangedRankSummary"
244
+ style="height: 600px"
245
+ ></div>
240
246
  </div>
241
247
  </section>
242
248
  <!-- 开发者 累计提交Changed End -->
@@ -250,18 +256,20 @@
250
256
  </div> -->
251
257
 
252
258
  <section class="table-card">
253
- <h2>提交清单</h2>
259
+ <h2 data-i18n="commitsList">提交清单</h2>
254
260
  <div id="tableControls">
255
261
  <input
256
262
  id="searchInput"
257
263
  type="search"
258
264
  placeholder="搜索作者/信息/Hash/Date"
265
+ data-i18n-placeholder="searchPlaceholder"
266
+
259
267
  />
260
268
  <input type="date" id="startDate" />
261
269
  <span>~</span>
262
270
  <input type="date" id="endDate" />
263
- <button id="clearDate">清除</button>
264
- <label for="pageSize">每页显示</label>
271
+ <button id="clearDate" data-i18n="clear">清除</button>
272
+ <label for="pageSize" data-i18n="per">每页显示</label>
265
273
  <select id="pageSize">
266
274
  <option value="10">10</option>
267
275
  <option value="20">20</option>
@@ -270,9 +278,9 @@
270
278
  </select>
271
279
  <label id="commitsTotal"></label>
272
280
  <div class="pager">
273
- <button id="prevPage">上一页</button>
281
+ <button id="prevPage" data-i18n="prevPage">上一页</button>
274
282
  <span id="pageInfo"></span>
275
- <button id="nextPage">下一页</button>
283
+ <button id="nextPage" data-i18n="nextPage">下一页</button>
276
284
  </div>
277
285
  </div>
278
286
  <table id="commitsTable">
@@ -0,0 +1,159 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title data-i18n="dashboardTitle">Wukong Gitlog 加班分析看板</title>
6
+ </head>
7
+
8
+ <body>
9
+ <h1 data-i18n="dashboardTitle">
10
+ Wukong Gitlog 加班分析看板
11
+ </h1>
12
+
13
+ <!-- KPI -->
14
+ <section>
15
+ <h2 data-i18n="kpi">关键指标</h2>
16
+ <div id="kpi-container"></div>
17
+ </section>
18
+
19
+ <!-- 下班时间 vs 工作时间 -->
20
+ <section>
21
+ <h2 data-i18n="outsideVsInside">
22
+ 下班时间 vs 工作时间提交占比
23
+ </h2>
24
+ <div id="ratio-chart"></div>
25
+ </section>
26
+
27
+ <!-- 每小时加班 -->
28
+ <section>
29
+ <h2 data-i18n="hourlyOvertime">
30
+ 每小时加班分布 (小时 → 提交数)
31
+ </h2>
32
+ <div id="hourly-chart"></div>
33
+ </section>
34
+
35
+ <!-- 每日趋势 -->
36
+ <section>
37
+ <h2 data-i18n="dailyTrend">
38
+ 按日提交趋势(次数)
39
+ </h2>
40
+ <div id="daily-chart"></div>
41
+ </section>
42
+
43
+ <!-- 最晚提交时间 -->
44
+ <section>
45
+ <h2 data-i18n="latestHourDaily">
46
+ 每日最晚提交时间(小时)
47
+ </h2>
48
+ <div id="latest-hour-chart"></div>
49
+ </section>
50
+
51
+ <!-- 每日加班严重度 -->
52
+ <section>
53
+ <h2 data-i18n="dailySeverity">
54
+ 每日超过下班的小时数
55
+ </h2>
56
+ <div id="severity-chart"></div>
57
+ </section>
58
+
59
+ <!-- 每周趋势 -->
60
+ <section>
61
+ <h2 data-i18n="weeklyTrend">
62
+ 每周趋势(加班占比)
63
+ </h2>
64
+ <div id="weekly-chart"></div>
65
+ </section>
66
+
67
+ <!-- 每月趋势 -->
68
+ <section>
69
+ <h2 data-i18n="monthlyTrend">
70
+ 每月趋势(加班占比)
71
+ </h2>
72
+ <div id="monthly-chart"></div>
73
+ </section>
74
+
75
+ <!-- 作者工作量 -->
76
+ <section>
77
+ <h2 data-i18n="authorChanges">
78
+ 开发者 Changed 工作量趋势(行数)
79
+ </h2>
80
+ <div id="author-changes-chart"></div>
81
+ </section>
82
+
83
+ <!-- 作者最晚加班 -->
84
+ <section>
85
+ <h2 data-i18n="authorLatestOvertime">
86
+ 开发者 加班最晚趋势(小时)
87
+ </h2>
88
+ <div id="author-latest-hour-chart"></div>
89
+ </section>
90
+
91
+ <!-- 作者加班次数 -->
92
+ <section>
93
+ <h2 data-i18n="authorOvertime">
94
+ 开发者 加班趋势(次数)
95
+ </h2>
96
+ <div id="author-overtime-chart"></div>
97
+ </section>
98
+
99
+ <!-- 作者午休 -->
100
+ <section>
101
+ <h2 data-i18n="authorLunch">
102
+ 开发者 午休最晚提交(小时)
103
+ </h2>
104
+ <div id="author-lunch-chart"></div>
105
+ </section>
106
+
107
+ <!-- 累计统计 -->
108
+ <section>
109
+ <h2 data-i18n="totalLunchTime">
110
+ 开发者 累计午休工作时长
111
+ </h2>
112
+ <div id="total-lunch"></div>
113
+ </section>
114
+
115
+ <section>
116
+ <h2 data-i18n="totalOvertime">
117
+ 开发者 累计加班时长
118
+ </h2>
119
+ <div id="total-overtime"></div>
120
+ </section>
121
+
122
+ <section>
123
+ <h2 data-i18n="totalCommits">
124
+ 开发者 累计提交次数
125
+ </h2>
126
+ <div id="total-commits"></div>
127
+ </section>
128
+
129
+ <section>
130
+ <h2 data-i18n="totalCommitsChanged">
131
+ 开发者 累计提交Changed
132
+ </h2>
133
+ <div id="total-changed"></div>
134
+ </section>
135
+
136
+ <!-- 提交清单 -->
137
+ <section>
138
+ <h2 data-i18n="commitsList">提交清单</h2>
139
+
140
+ <input
141
+ type="text"
142
+ data-i18n-placeholder="searchPlaceholder"
143
+ placeholder="搜索作者/信息/Hash/Date"
144
+ />
145
+
146
+ <div id="commit-table"></div>
147
+
148
+ <div class="pagination">
149
+ <button data-i18n="prevPage">上一页</button>
150
+ <button data-i18n="nextPage">下一页</button>
151
+ </div>
152
+ </section>
153
+
154
+ <script>
155
+ window.__LANG__ = "__LANG__"
156
+ </script>
157
+ <script type="module" src="./app.js"></script>
158
+ </body>
159
+ </html>
@@ -0,0 +1,29 @@
1
+ export default {
2
+ dashboardTitle: 'Wukong Gitlog Overtime Dashboard',
3
+ outsideVsInside: 'After-hours vs Working-hours Commit Ratio',
4
+ kpi: 'Key Metrics',
5
+ hourlyOvertime: 'Hourly Overtime Distribution (Hours → Commits)',
6
+ dailyTrend: 'Daily Commit Trend (Times)',
7
+ latestHourDaily: 'Latest Commit Hour Per Day',
8
+ dailySeverity: 'Hours Exceeding Off-work Time Per Day',
9
+ weeklyTrend: 'Weekly Trend (Overtime Ratio)',
10
+ monthlyTrend: 'Monthly Trend (Overtime Ratio)',
11
+ authorChanges: 'Developer Changed Workload Trend (Lines)',
12
+ authorLatestOvertime: 'Developer Latest Overtime Trend (Hour)',
13
+ authorOvertime: 'Developer Overtime Trend (Times)',
14
+ authorLunch: 'Developer Latest Lunch Commit (Hour)',
15
+ totalLunchTime: 'Developer Total Lunch Working Time',
16
+ totalLunchTimeAll: "Total All Lunch Working Time",
17
+ totalOvertime: 'Developer Total Overtime',
18
+ totalOvertimeAll: "Total All Overtime",
19
+ totalCommits: 'Developer Total Commits',
20
+ totalCommitsCount: 'Total All Commits',
21
+ totalCommitsChanged: 'Developer Total Commits Changed',
22
+ totalCommitsChangedAll: 'Total Commits Changed All',
23
+ commitsList: 'Commit List',
24
+ searchPlaceholder: 'Author/Message/Hash/Date',
25
+ per: ' ',
26
+ clear: 'Clear',
27
+ prevPage: 'Previous',
28
+ nextPage: 'Next'
29
+ }
@@ -0,0 +1,29 @@
1
+ export default {
2
+ dashboardTitle: "Wukong Gitlog 加班分析看板",
3
+ outsideVsInside: "下班时间 vs 工作时间提交占比",
4
+ kpi: "关键指标",
5
+ hourlyOvertime: "每小时加班分布 (小时 → 提交数)",
6
+ dailyTrend: "按日提交趋势(次数)",
7
+ latestHourDaily: "每日最晚提交时间(小时)",
8
+ dailySeverity: "每日超过下班的小时数",
9
+ weeklyTrend: "每周趋势(加班占比)",
10
+ monthlyTrend: "每月趋势(加班占比)",
11
+ authorChanges: "开发者 Changed 工作量趋势(行数)",
12
+ authorLatestOvertime: "开发者 加班最晚趋势(小时)",
13
+ authorOvertime: "开发者 加班趋势(次数)",
14
+ authorLunch: "开发者 午休最晚提交(小时)",
15
+ totalLunchTime: "开发者 累计午休工作时长",
16
+ totalLunchTimeAll: "开发者 午休工作总时长",
17
+ totalOvertime: "开发者 累计加班时长",
18
+ totalOvertimeAll: "开发者 加班总时长",
19
+ totalCommits: "开发者 累计提交次数",
20
+ totalCommitsCount: "开发者 提交总次数",
21
+ totalCommitsChanged: "开发者 累计提交Changed",
22
+ totalCommitsChangedAll: '开发者 提交Changed 总次数',
23
+ commitsList: "提交清单",
24
+ searchPlaceholder: "搜索作者/信息/Hash/Date",
25
+ per: '每页显示',
26
+ clear: '清除',
27
+ prevPage: "上一页",
28
+ nextPage: "下一页"
29
+ }