zen-gitsync 2.0.4 → 2.0.6
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/README.md +31 -2
- package/package.json +3 -1
- package/src/ui/client/components.d.ts +1 -0
- package/src/ui/client/src/App.vue +626 -186
- package/src/ui/client/src/components/CommitForm.vue +508 -401
- package/src/ui/client/src/components/GitStatus.vue +590 -224
- package/src/ui/client/src/components/LogList.vue +809 -83
- package/src/ui/client/src/stores/gitLogStore.ts +440 -125
- package/src/ui/client/src/stores/gitStore.ts +86 -1
- package/src/ui/client/stats.html +1 -1
- package/src/ui/public/assets/index-DaPynzAr.js +10 -0
- package/src/ui/public/assets/index-qfIezZmd.css +1 -0
- package/src/ui/public/assets/vendor-BcSuWc8z.js +45 -0
- package/src/ui/public/index.html +3 -3
- package/src/ui/server/index.js +375 -44
- package/src/ui/public/assets/index-D5irnfho.css +0 -1
- package/src/ui/public/assets/index-DBck3u67.js +0 -8
- package/src/ui/public/assets/vendor-CdJ34PvS.js +0 -45
package/src/ui/public/index.html
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>Zen-GitSync - Git同步工具</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DaPynzAr.js"></script>
|
|
9
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-BcSuWc8z.js">
|
|
10
10
|
<link rel="stylesheet" crossorigin href="/assets/vendor-Dp0FkvMe.css">
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-qfIezZmd.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="app"></div>
|
package/src/ui/server/index.js
CHANGED
|
@@ -8,17 +8,25 @@ import config from '../../config.js';
|
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import fs from 'fs/promises';
|
|
10
10
|
import os from 'os';
|
|
11
|
-
|
|
11
|
+
import { Server } from 'socket.io';
|
|
12
|
+
import chokidar from 'chokidar';
|
|
12
13
|
// import { exec } from 'child_process';
|
|
13
14
|
|
|
14
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
16
|
const __dirname = path.dirname(__filename);
|
|
16
17
|
const configManager = config; // 确保 configManager 可用
|
|
17
18
|
|
|
19
|
+
// 文件系统变动监控器
|
|
20
|
+
let watcher = null;
|
|
21
|
+
// 防抖计时器
|
|
22
|
+
let debounceTimer = null;
|
|
23
|
+
// 防抖延迟时间 (毫秒)
|
|
24
|
+
const DEBOUNCE_DELAY = 1000;
|
|
25
|
+
|
|
18
26
|
async function startUIServer() {
|
|
19
27
|
const app = express();
|
|
20
28
|
const httpServer = createServer(app);
|
|
21
|
-
|
|
29
|
+
const io = new Server(httpServer);
|
|
22
30
|
|
|
23
31
|
// 添加全局中间件来解析JSON请求体
|
|
24
32
|
app.use(express.json());
|
|
@@ -30,24 +38,6 @@ async function startUIServer() {
|
|
|
30
38
|
next();
|
|
31
39
|
});
|
|
32
40
|
|
|
33
|
-
// // 启动前端Vue应用
|
|
34
|
-
// const clientPath = path.join(__dirname, '../client');
|
|
35
|
-
// console.log(`正在启动前端应用,路径: ${clientPath}`);
|
|
36
|
-
|
|
37
|
-
// const vueProcess = exec('npm run dev', { cwd: clientPath }, (error) => {
|
|
38
|
-
// if (error) {
|
|
39
|
-
// console.error('启动前端应用失败:', error);
|
|
40
|
-
// }
|
|
41
|
-
// });
|
|
42
|
-
|
|
43
|
-
// vueProcess.stdout.on('data', (data) => {
|
|
44
|
-
// console.log(`前端输出: ${data}`);
|
|
45
|
-
// });
|
|
46
|
-
|
|
47
|
-
// vueProcess.stderr.on('data', (data) => {
|
|
48
|
-
// console.error(`前端错误: ${data}`);
|
|
49
|
-
// });
|
|
50
|
-
|
|
51
41
|
// 静态文件服务
|
|
52
42
|
app.use(express.static(path.join(__dirname, '../public')));
|
|
53
43
|
|
|
@@ -156,10 +146,10 @@ async function startUIServer() {
|
|
|
156
146
|
// 获取Git用户配置信息
|
|
157
147
|
app.get('/api/user-info', async (req, res) => {
|
|
158
148
|
try {
|
|
159
|
-
//
|
|
160
|
-
const { stdout: userName } = await execGitCommand('git config user.name');
|
|
161
|
-
//
|
|
162
|
-
const { stdout: userEmail } = await execGitCommand('git config user.email');
|
|
149
|
+
// 获取全局用户名
|
|
150
|
+
const { stdout: userName } = await execGitCommand('git config --global user.name');
|
|
151
|
+
// 获取全局用户邮箱
|
|
152
|
+
const { stdout: userEmail } = await execGitCommand('git config --global user.email');
|
|
163
153
|
|
|
164
154
|
res.json({
|
|
165
155
|
name: userName.trim(),
|
|
@@ -198,7 +188,6 @@ async function startUIServer() {
|
|
|
198
188
|
// 新增切换工作目录接口
|
|
199
189
|
app.post('/api/change_directory', async (req, res) => {
|
|
200
190
|
try {
|
|
201
|
-
|
|
202
191
|
const { path } = req.body;
|
|
203
192
|
|
|
204
193
|
if (!path) {
|
|
@@ -212,12 +201,22 @@ async function startUIServer() {
|
|
|
212
201
|
// 检查新目录是否是Git仓库
|
|
213
202
|
try {
|
|
214
203
|
await execGitCommand('git rev-parse --is-inside-work-tree');
|
|
204
|
+
|
|
205
|
+
// 初始化文件监控
|
|
206
|
+
initFileSystemWatcher();
|
|
207
|
+
|
|
215
208
|
res.json({
|
|
216
209
|
success: true,
|
|
217
210
|
directory: newDirectory,
|
|
218
211
|
isGitRepo: true
|
|
219
212
|
});
|
|
220
213
|
} catch (error) {
|
|
214
|
+
// 不是Git仓库,停止监控
|
|
215
|
+
if (watcher) {
|
|
216
|
+
watcher.close().catch(err => console.error('关闭监控器失败:', err));
|
|
217
|
+
watcher = null;
|
|
218
|
+
}
|
|
219
|
+
|
|
221
220
|
res.json({
|
|
222
221
|
success: true,
|
|
223
222
|
directory: newDirectory,
|
|
@@ -482,6 +481,46 @@ async function startUIServer() {
|
|
|
482
481
|
}
|
|
483
482
|
});
|
|
484
483
|
|
|
484
|
+
// 添加单个文件到暂存区
|
|
485
|
+
app.post('/api/add-file', async (req, res) => {
|
|
486
|
+
try {
|
|
487
|
+
const { filePath } = req.body;
|
|
488
|
+
|
|
489
|
+
if (!filePath) {
|
|
490
|
+
return res.status(400).json({
|
|
491
|
+
success: false,
|
|
492
|
+
error: '缺少文件路径参数'
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// 执行 git add 命令添加特定文件
|
|
497
|
+
await execGitCommand(`git add "${filePath}"`);
|
|
498
|
+
res.json({ success: true });
|
|
499
|
+
} catch (error) {
|
|
500
|
+
res.status(500).json({ success: false, error: error.message });
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// 从暂存区移除单个文件
|
|
505
|
+
app.post('/api/unstage-file', async (req, res) => {
|
|
506
|
+
try {
|
|
507
|
+
const { filePath } = req.body;
|
|
508
|
+
|
|
509
|
+
if (!filePath) {
|
|
510
|
+
return res.status(400).json({
|
|
511
|
+
success: false,
|
|
512
|
+
error: '缺少文件路径参数'
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// 执行 git reset HEAD 命令移除特定文件的暂存
|
|
517
|
+
await execGitCommand(`git reset HEAD -- "${filePath}"`);
|
|
518
|
+
res.json({ success: true });
|
|
519
|
+
} catch (error) {
|
|
520
|
+
res.status(500).json({ success: false, error: error.message });
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
|
|
485
524
|
// 推送更改
|
|
486
525
|
app.post('/api/push', async (req, res) => {
|
|
487
526
|
try {
|
|
@@ -502,9 +541,40 @@ async function startUIServer() {
|
|
|
502
541
|
// 由前端统一使用该接口
|
|
503
542
|
|
|
504
543
|
// 修改 git log 命令,添加 %ae 参数来获取作者邮箱
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
544
|
+
// 使用 %H 获取完整哈希值(而不是短哈希 %h)
|
|
545
|
+
// 使用 %B 获取完整提交信息(包括正文)
|
|
546
|
+
const { stdout } = await execGitCommand(`git log --all --pretty=format:"%H|%an|%ae|%ad|%B|%D" --date=short ${limit}`);
|
|
547
|
+
|
|
548
|
+
// 分隔符改为使用特殊标记,因为提交信息中可能包含|字符
|
|
549
|
+
const recordSeparator = "\n<<<RECORD_SEPARATOR>>>\n";
|
|
550
|
+
const fieldSeparator = "<<<FIELD_SEPARATOR>>>";
|
|
551
|
+
|
|
552
|
+
// 预处理输出,替换提交记录之间的换行符
|
|
553
|
+
const processedOutput = stdout.replace(/\n(?=[a-f0-9]{40}\|)/g, recordSeparator);
|
|
554
|
+
|
|
555
|
+
// 按记录分隔符拆分日志条目
|
|
556
|
+
const logEntries = processedOutput.split(recordSeparator);
|
|
557
|
+
|
|
558
|
+
const logs = logEntries.map(entry => {
|
|
559
|
+
// 使用第一个|分隔哈希值,其余部分作为整体
|
|
560
|
+
const hashEndIndex = entry.indexOf('|');
|
|
561
|
+
const hash = entry.substring(0, hashEndIndex);
|
|
562
|
+
const restPart = entry.substring(hashEndIndex + 1);
|
|
563
|
+
|
|
564
|
+
// 使用最后一个|分隔引用信息,其余部分作为整体
|
|
565
|
+
const lastPipeIndex = restPart.lastIndexOf('|');
|
|
566
|
+
const refs = restPart.substring(lastPipeIndex + 1).trim();
|
|
567
|
+
const middlePart = restPart.substring(0, lastPipeIndex);
|
|
568
|
+
|
|
569
|
+
// 分隔作者、邮箱、日期和提交信息
|
|
570
|
+
const parts = middlePart.split('|');
|
|
571
|
+
|
|
572
|
+
// 确保即使分隔出的部分不足,也能提供默认值
|
|
573
|
+
const author = parts[0] || '';
|
|
574
|
+
const email = parts[1] || '';
|
|
575
|
+
const date = parts[2] || '';
|
|
576
|
+
// 提交信息可能包含多行
|
|
577
|
+
const message = parts.slice(3).join('|').trim();
|
|
508
578
|
|
|
509
579
|
// 从引用信息中提取分支名称
|
|
510
580
|
let branch = null;
|
|
@@ -515,6 +585,7 @@ async function startUIServer() {
|
|
|
515
585
|
|
|
516
586
|
return { hash, author, email, date, message, branch };
|
|
517
587
|
});
|
|
588
|
+
|
|
518
589
|
res.json(logs);
|
|
519
590
|
} catch (error) {
|
|
520
591
|
res.status(500).json({ error: error.message });
|
|
@@ -630,25 +701,277 @@ async function startUIServer() {
|
|
|
630
701
|
}
|
|
631
702
|
});
|
|
632
703
|
|
|
704
|
+
// 获取提交的文件列表
|
|
705
|
+
app.get('/api/commit-files', async (req, res) => {
|
|
706
|
+
try {
|
|
707
|
+
const hash = req.query.hash;
|
|
708
|
+
|
|
709
|
+
if (!hash) {
|
|
710
|
+
return res.status(400).json({
|
|
711
|
+
success: false,
|
|
712
|
+
error: '缺少提交哈希参数'
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
console.log(`获取提交文件列表: hash=${hash}`);
|
|
717
|
+
|
|
718
|
+
// 执行命令获取提交中修改的文件列表
|
|
719
|
+
const { stdout } = await execGitCommand(`git show --name-only --format="" ${hash}`);
|
|
720
|
+
|
|
721
|
+
// 将输出按行分割,并过滤掉空行
|
|
722
|
+
const files = stdout.split('\n').filter(line => line.trim());
|
|
723
|
+
console.log(`找到${files.length}个文件:`, files);
|
|
724
|
+
|
|
725
|
+
res.json({
|
|
726
|
+
success: true,
|
|
727
|
+
files
|
|
728
|
+
});
|
|
729
|
+
} catch (error) {
|
|
730
|
+
console.error('获取提交文件列表失败:', error);
|
|
731
|
+
res.status(500).json({
|
|
732
|
+
success: false,
|
|
733
|
+
error: `获取提交文件列表失败: ${error.message}`
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
// 获取提交中特定文件的差异
|
|
739
|
+
app.get('/api/commit-file-diff', async (req, res) => {
|
|
740
|
+
try {
|
|
741
|
+
const hash = req.query.hash;
|
|
742
|
+
const filePath = req.query.file;
|
|
743
|
+
|
|
744
|
+
if (!hash || !filePath) {
|
|
745
|
+
return res.status(400).json({
|
|
746
|
+
success: false,
|
|
747
|
+
error: '缺少必要参数'
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
console.log(`获取提交文件差异: hash=${hash}, file=${filePath}`);
|
|
752
|
+
|
|
753
|
+
// 执行命令获取文件差异,-p显示补丁,限定文件路径
|
|
754
|
+
const { stdout } = await execGitCommand(`git show ${hash} -- "${filePath}"`);
|
|
755
|
+
|
|
756
|
+
console.log(`获取到差异内容,长度: ${stdout.length}`);
|
|
757
|
+
// 如果差异内容太长,只打印前100个字符
|
|
758
|
+
if (stdout.length > 100) {
|
|
759
|
+
console.log(`差异内容预览: ${stdout.substring(0, 100)}...`);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
res.json({
|
|
763
|
+
success: true,
|
|
764
|
+
diff: stdout
|
|
765
|
+
});
|
|
766
|
+
} catch (error) {
|
|
767
|
+
console.error('获取提交文件差异失败:', error);
|
|
768
|
+
res.status(500).json({
|
|
769
|
+
success: false,
|
|
770
|
+
error: `获取提交文件差异失败: ${error.message}`
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
// 添加清理Git锁定文件的接口
|
|
776
|
+
app.post('/api/remove-lock', async (req, res) => {
|
|
777
|
+
try {
|
|
778
|
+
const gitDir = path.join(process.cwd(), '.git')
|
|
779
|
+
const indexLockFile = path.join(gitDir, 'index.lock')
|
|
780
|
+
|
|
781
|
+
// 检查文件是否存在
|
|
782
|
+
try {
|
|
783
|
+
await fs.access(indexLockFile)
|
|
784
|
+
// 如果文件存在,尝试删除它
|
|
785
|
+
await fs.unlink(indexLockFile)
|
|
786
|
+
res.json({ success: true, message: '已清理锁定文件' })
|
|
787
|
+
} catch (error) {
|
|
788
|
+
// 如果文件不存在,也返回成功
|
|
789
|
+
if (error.code === 'ENOENT') {
|
|
790
|
+
res.json({ success: true, message: '没有发现锁定文件' })
|
|
791
|
+
} else {
|
|
792
|
+
throw error
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
} catch (error) {
|
|
796
|
+
console.error('清理锁定文件失败:', error)
|
|
797
|
+
res.status(500).json({
|
|
798
|
+
success: false,
|
|
799
|
+
error: `清理锁定文件失败: ${error.message}`
|
|
800
|
+
})
|
|
801
|
+
}
|
|
802
|
+
})
|
|
803
|
+
|
|
804
|
+
// 清除Git用户配置
|
|
805
|
+
app.post('/api/clear-user-config', async (req, res) => {
|
|
806
|
+
try {
|
|
807
|
+
// 检查全局配置是否存在,如果存在才删除
|
|
808
|
+
try {
|
|
809
|
+
const { stdout: userName } = await execGitCommand('git config --global user.name');
|
|
810
|
+
if (userName.trim()) {
|
|
811
|
+
await execGitCommand('git config --global --unset user.name');
|
|
812
|
+
}
|
|
813
|
+
} catch (error) {
|
|
814
|
+
console.log('全局用户名配置检查失败,可能不存在:', error.message);
|
|
815
|
+
// 忽略错误继续执行
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
try {
|
|
819
|
+
const { stdout: userEmail } = await execGitCommand('git config --global user.email');
|
|
820
|
+
if (userEmail.trim()) {
|
|
821
|
+
await execGitCommand('git config --global --unset user.email');
|
|
822
|
+
}
|
|
823
|
+
} catch (error) {
|
|
824
|
+
console.log('全局邮箱配置检查失败,可能不存在:', error.message);
|
|
825
|
+
// 忽略错误继续执行
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
res.json({ success: true, message: '已清除全局Git用户配置' });
|
|
829
|
+
} catch (error) {
|
|
830
|
+
res.status(500).json({
|
|
831
|
+
success: false,
|
|
832
|
+
error: `清除全局Git用户配置失败: ${error.message}`
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
// 恢复Git用户配置
|
|
838
|
+
app.post('/api/restore-user-config', async (req, res) => {
|
|
839
|
+
try {
|
|
840
|
+
const { name, email } = req.body;
|
|
841
|
+
if (!name || !email) {
|
|
842
|
+
return res.status(400).json({
|
|
843
|
+
success: false,
|
|
844
|
+
error: '需要提供用户名和邮箱'
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
await execGitCommand(`git config --global user.name "${name}"`);
|
|
849
|
+
await execGitCommand(`git config --global user.email "${email}"`);
|
|
850
|
+
res.json({ success: true, message: '已更新全局Git用户配置' });
|
|
851
|
+
} catch (error) {
|
|
852
|
+
res.status(500).json({
|
|
853
|
+
success: false,
|
|
854
|
+
error: `更新全局Git用户配置失败: ${error.message}`
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
|
|
633
859
|
// Socket.io 实时更新
|
|
634
|
-
|
|
635
|
-
|
|
860
|
+
io.on('connection', (socket) => {
|
|
861
|
+
console.log('客户端已连接:', socket.id);
|
|
862
|
+
|
|
863
|
+
// 当客户端连接时,立即发送一次Git状态
|
|
864
|
+
getAndBroadcastStatus();
|
|
865
|
+
|
|
866
|
+
// 客户端可以请求开始/停止监控
|
|
867
|
+
socket.on('start_monitoring', () => {
|
|
868
|
+
if (!watcher) {
|
|
869
|
+
initFileSystemWatcher();
|
|
870
|
+
socket.emit('monitoring_status', { active: true });
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
socket.on('stop_monitoring', () => {
|
|
875
|
+
if (watcher) {
|
|
876
|
+
watcher.close().catch(err => console.error('关闭监控器失败:', err));
|
|
877
|
+
watcher = null;
|
|
878
|
+
socket.emit('monitoring_status', { active: false });
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
// 客户端断开连接
|
|
883
|
+
socket.on('disconnect', () => {
|
|
884
|
+
console.log('客户端已断开连接:', socket.id);
|
|
885
|
+
});
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
// 初始化文件系统监控
|
|
889
|
+
function initFileSystemWatcher() {
|
|
890
|
+
// 停止已有的监控器
|
|
891
|
+
if (watcher) {
|
|
892
|
+
watcher.close().catch(err => console.error('关闭旧监控器失败:', err));
|
|
893
|
+
}
|
|
636
894
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
895
|
+
try {
|
|
896
|
+
// 获取当前工作目录
|
|
897
|
+
const currentDir = process.cwd();
|
|
898
|
+
|
|
899
|
+
console.log(`初始化文件系统监控器,路径: ${currentDir}`);
|
|
900
|
+
|
|
901
|
+
// 检查是否是Git仓库
|
|
902
|
+
try {
|
|
903
|
+
execGitCommand('git rev-parse --is-inside-work-tree');
|
|
904
|
+
} catch (error) {
|
|
905
|
+
console.log('当前目录不是Git仓库,不启动监控');
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// 使用chokidar监控文件变动
|
|
910
|
+
watcher = chokidar.watch(currentDir, {
|
|
911
|
+
ignored: [
|
|
912
|
+
/(^|[\/\\])\../, // 忽略.开头的文件和目录
|
|
913
|
+
'**/node_modules/**', // 忽略node_modules
|
|
914
|
+
'**/.git/**', // 忽略.git目录
|
|
915
|
+
],
|
|
916
|
+
persistent: true,
|
|
917
|
+
ignoreInitial: true, // 忽略初始扫描时的文件
|
|
918
|
+
awaitWriteFinish: {
|
|
919
|
+
stabilityThreshold: 300, // 等待文件写入完成的时间
|
|
920
|
+
pollInterval: 100 // 轮询间隔
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
// 合并所有变动事件到一个处理程序
|
|
925
|
+
const events = ['add', 'change', 'unlink'];
|
|
926
|
+
events.forEach(event => {
|
|
927
|
+
watcher.on(event, path => {
|
|
928
|
+
console.log(`检测到文件变动 [${event}]: ${path}`);
|
|
929
|
+
debouncedNotifyChanges();
|
|
930
|
+
});
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
watcher.on('error', error => {
|
|
934
|
+
console.error('文件监控错误:', error);
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
console.log('文件系统监控器已启动');
|
|
938
|
+
} catch (error) {
|
|
939
|
+
console.error('启动文件监控失败:', error);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// 获取并广播Git状态
|
|
944
|
+
async function getAndBroadcastStatus() {
|
|
945
|
+
try {
|
|
946
|
+
// 获取常规状态
|
|
947
|
+
const { stdout: statusOutput } = await execGitCommand('git status');
|
|
948
|
+
|
|
949
|
+
// 获取porcelain格式状态
|
|
950
|
+
const { stdout: porcelainOutput } = await execGitCommand('git status --porcelain');
|
|
951
|
+
|
|
952
|
+
// 广播到所有连接的客户端
|
|
953
|
+
io.emit('git_status_update', {
|
|
954
|
+
status: statusOutput,
|
|
955
|
+
porcelain: porcelainOutput,
|
|
956
|
+
timestamp: new Date().toISOString()
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
console.log('已广播Git状态更新');
|
|
960
|
+
} catch (error) {
|
|
961
|
+
console.error('获取或广播Git状态失败:', error);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// 防抖处理函数
|
|
966
|
+
function debouncedNotifyChanges() {
|
|
967
|
+
if (debounceTimer) {
|
|
968
|
+
clearTimeout(debounceTimer);
|
|
969
|
+
}
|
|
646
970
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
// });
|
|
971
|
+
debounceTimer = setTimeout(() => {
|
|
972
|
+
getAndBroadcastStatus();
|
|
973
|
+
}, DEBOUNCE_DELAY);
|
|
974
|
+
}
|
|
652
975
|
|
|
653
976
|
// 启动服务器
|
|
654
977
|
const PORT = 3000;
|
|
@@ -658,6 +981,10 @@ async function startUIServer() {
|
|
|
658
981
|
console.log(chalk.green(` 访问地址: http://localhost:${PORT}`));
|
|
659
982
|
console.log(chalk.green(` 启动时间: ${new Date().toLocaleString()}`));
|
|
660
983
|
console.log(chalk.green('======================================'));
|
|
984
|
+
|
|
985
|
+
// 启动文件监控
|
|
986
|
+
initFileSystemWatcher();
|
|
987
|
+
|
|
661
988
|
open(`http://localhost:${PORT}`);
|
|
662
989
|
}).on('error', async (err) => {
|
|
663
990
|
if (err.code === 'EADDRINUSE') {
|
|
@@ -672,6 +999,10 @@ async function startUIServer() {
|
|
|
672
999
|
console.log(chalk.green(` 访问地址: http://localhost:${newPort}`));
|
|
673
1000
|
console.log(chalk.green(` 启动时间: ${new Date().toLocaleString()}`));
|
|
674
1001
|
console.log(chalk.green('======================================'));
|
|
1002
|
+
|
|
1003
|
+
// 启动文件监控
|
|
1004
|
+
initFileSystemWatcher();
|
|
1005
|
+
|
|
675
1006
|
open(`http://localhost:${newPort}`);
|
|
676
1007
|
resolve();
|
|
677
1008
|
}).on('error', (e) => {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.card[data-v-1eb3fdac]{background-color:#fff;border-radius:8px;box-shadow:0 2px 12px #0000001a;padding:20px;margin-bottom:20px}.status-header[data-v-1eb3fdac]{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}.status-header h2[data-v-1eb3fdac]{margin:0}.status-box[data-v-1eb3fdac]{white-space:pre-wrap;font-family:monospace;background-color:#f5f7fa;padding:15px;border-radius:4px;margin-bottom:15px;max-height:300px;overflow-y:auto}.file-list[data-v-1eb3fdac]{max-height:300px;overflow-y:auto}.file-item[data-v-1eb3fdac]{padding:8px 12px;margin-bottom:5px;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:space-between}.file-item[data-v-1eb3fdac]:hover{background-color:#f5f7fa}.file-info[data-v-1eb3fdac]{display:flex;align-items:center;flex-grow:1}.file-actions[data-v-1eb3fdac]{margin-left:10px;opacity:.5;transition:opacity .2s}.file-item:hover .file-actions[data-v-1eb3fdac]{opacity:1}.file-type[data-v-1eb3fdac]{font-size:12px;padding:2px 6px;border-radius:10px;margin-right:10px;flex-shrink:0}.added .file-type[data-v-1eb3fdac]{background-color:#e1f3d8;color:#67c23a}.modified .file-type[data-v-1eb3fdac]{background-color:#e6f1fc;color:#409eff}.deleted .file-type[data-v-1eb3fdac]{background-color:#fef0f0;color:#f56c6c}.untracked .file-type[data-v-1eb3fdac]{background-color:#fdf6ec;color:#e6a23c}.file-path[data-v-1eb3fdac]{font-family:monospace;word-break:break-all}.diff-content[data-v-1eb3fdac]{font-family:monospace;white-space:pre-wrap;max-height:60vh;overflow-y:auto;padding:10px;background-color:#f5f7fa;border-radius:4px}.diff-formatted[data-v-1eb3fdac]{font-size:14px;line-height:1.5}.file-navigation[data-v-1eb3fdac]{display:flex;justify-content:center;align-items:center;margin-top:15px}.file-counter[data-v-1eb3fdac]{margin:0 15px;font-size:14px;color:#606266}.current-directory[data-v-1eb3fdac]{display:flex;align-items:center;margin-bottom:15px;padding:8px 12px;background-color:#f5f7fa;border-radius:4px;font-family:monospace}.current-directory .el-icon[data-v-1eb3fdac]{margin-right:8px;color:#409eff}.current-directory span[data-v-1eb3fdac]{flex-grow:1;word-break:break-all;margin-right:10px}.browser-current-path[data-v-1eb3fdac]{margin-bottom:10px;font-size:14px;color:#606266;background-color:#f5f7fa;padding:8px 12px;border-radius:4px;font-family:monospace;word-break:break-all}.browser-error[data-v-1eb3fdac]{margin-bottom:10px;color:#f56c6c;padding:8px 12px;background-color:#fef0f0;border-radius:4px}.directory-browser[data-v-1eb3fdac]{padding:10px;max-height:400px;overflow-y:auto}.browser-nav[data-v-1eb3fdac]{margin-bottom:10px;display:flex;justify-content:space-between}.directory-items[data-v-1eb3fdac]{list-style:none;padding:0;margin:0}.directory-item[data-v-1eb3fdac]{padding:8px 12px;margin-bottom:5px;border-radius:4px;cursor:pointer;display:flex;align-items:center}.directory-item[data-v-1eb3fdac]:hover{background-color:#f5f7fa}.directory-item.directory[data-v-1eb3fdac]{color:#409eff}.directory-item.file[data-v-1eb3fdac]{color:#606266}.directory-item .el-icon[data-v-1eb3fdac]{margin-right:10px}.directory-item span[data-v-1eb3fdac]{font-family:monospace;word-break:break-all}.directory-buttons[data-v-1eb3fdac]{display:flex;gap:10px;margin-top:10px}.no-padding-left[data-v-1eb3fdac]{padding-left:8px!important}.diff-header{font-weight:700;background-color:#e6f1fc;padding:3px;margin:5px 0}.diff-old-file,.diff-new-file{color:#888}.diff-hunk-header{color:#6f42c1}.diff-added{background-color:#e6ffed;color:#28a745}.diff-removed{background-color:#ffeef0;color:#d73a49}.diff-context{color:#444}.card[data-v-34334c1f]{background-color:#fff;border-radius:5px;box-shadow:0 2px 5px #0000001a;margin-bottom:20px;padding:20px}.commit-form[data-v-34334c1f]{display:flex;margin-bottom:15px;gap:10px}.git-actions[data-v-34334c1f]{margin-top:20px;display:flex;flex-direction:column;gap:10px}.action-row[data-v-34334c1f]{display:flex;gap:10px;flex-wrap:wrap}.standard-commit-form[data-v-34334c1f]{display:flex;flex-direction:column;gap:15px;margin-bottom:15px}.standard-commit-header[data-v-34334c1f]{display:flex;gap:10px;width:100%}.type-select[data-v-34334c1f]{width:120px;flex-shrink:0}.scope-container[data-v-34334c1f]{display:flex;align-items:center;gap:5px;flex-grow:0;width:200px}.scope-input[data-v-34334c1f]{flex-grow:1}.description-container[data-v-34334c1f]{display:flex;align-items:center;gap:5px;flex-grow:1}.description-input[data-v-34334c1f]{flex-grow:1;min-width:200px}.settings-button[data-v-34334c1f]{flex-shrink:0}.preview-section[data-v-34334c1f]{background-color:#f5f7fa;padding:10px;border-radius:4px}.preview-title[data-v-34334c1f]{font-weight:700;margin-bottom:5px}.preview-content[data-v-34334c1f]{white-space:pre-wrap;font-family:monospace;margin:0;padding:10px;background-color:#ebeef5;border-radius:4px}.template-container[data-v-34334c1f]{display:flex;flex-direction:column;height:calc(85vh - 100px);overflow-y:auto}.template-form[data-v-34334c1f]{margin-bottom:20px}.template-list[data-v-34334c1f]{flex:1;overflow-y:auto}.template-input[data-v-34334c1f]{flex-grow:1}.template-list[data-v-34334c1f]{overflow-y:auto;height:100%}.template-item[data-v-34334c1f]{margin-bottom:10px}.template-item[data-v-34334c1f]:hover{background-color:#f5f7fa}.template-content[data-v-34334c1f]{flex-grow:1;margin-right:10px;word-break:break-all}.template-actions[data-v-34334c1f]{display:flex;gap:5px;justify-content:flex-end;min-width:120px;flex-shrink:0}.options-row[data-v-34334c1f]{display:flex;justify-content:space-between;align-items:center;margin-bottom:15px}.code-command[data-v-34334c1f]{background-color:#2d2d2d;color:#f8f8f2;font-family:Courier New,Courier,monospace;padding:10px;border-radius:4px;overflow-x:auto;white-space:pre;font-size:14px}@media (max-width: 768px){.action-row[data-v-34334c1f]{flex-direction:column}}.log-header[data-v-45e00b57]{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}.log-actions[data-v-45e00b57]{display:flex;gap:8px}.branch-container[data-v-45e00b57]{display:flex;flex-wrap:wrap;gap:4px}.branch-tag[data-v-45e00b57]{margin-right:4px}.commit-count[data-v-45e00b57]{margin-bottom:10px;font-size:14px;color:#606266;text-align:right}.graph-container[data-v-45e00b57]{width:100%;height:600px;overflow:auto;border:1px solid #ebeef5;border-radius:4px;padding:10px;background-color:#fff;position:relative}.graph-container svg[data-v-45e00b57]{transform-origin:top left;transition:transform .2s ease}.graph-view[data-v-45e00b57]{width:100%}.graph-controls[data-v-45e00b57]{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}.zoom-controls[data-v-45e00b57]{display:flex;gap:8px}.zoom-slider[data-v-45e00b57]{width:200px}.scale-info[data-v-45e00b57]{font-size:14px;color:#606266}body{font-family:Arial,sans-serif;margin:0;padding:0;background-color:#f5f5f5}.container{margin:0 auto;padding:20px 30px}.main-header{background-color:#24292e;color:#fff;padding:15px 20px;display:flex;justify-content:space-between;align-items:center}.header-left{display:flex;align-items:center;gap:10px}.logo{height:32px;width:auto}h1{margin:0;font-size:24px}.header-info{display:flex;flex-direction:column;align-items:flex-end;gap:5px}#branch-info,#user-info{background-color:#ffffff1a;padding:4px 8px;border-radius:4px;font-size:14px}.branch-label,.user-label,.user-name{font-weight:700;margin-right:5px}.user-email{color:#e0e0e0}.branch-name{font-family:monospace}.card{background-color:#fff;border-radius:5px;box-shadow:0 2px 5px #0000001a;margin-bottom:20px;padding:20px}.status-box{background-color:#f6f8fa;border:1px solid #e1e4e8;border-radius:3px;padding:15px;white-space:pre-wrap;font-family:monospace;max-height:300px;overflow-y:auto}.layout-container{display:flex;gap:20px}.left-panel{flex:0 0 30%;max-width:30%}.right-panel{flex:0 0 70%;max-width:70%}@media (max-width: 768px){.layout-container{flex-direction:column}.left-panel,.right-panel{flex:0 0 100%;max-width:100%}.header-left{gap:8px}.logo{height:24px}h1{font-size:20px}}.commit-form{display:flex;margin-bottom:15px}.log-item{padding:10px 0;border-bottom:1px solid #eee}.log-item:last-child{border-bottom:none}.log-hash{color:#6f42c1;font-family:monospace}.log-author,.log-date{color:#6a737d}.log-message{font-weight:700}.log-branch{display:inline-block;background-color:#0366d6;color:#fff;border-radius:3px;padding:2px 6px;margin-left:8px;font-size:12px}.branch-select{width:150px;margin-left:5px}.branch-select :deep(.el-input__inner){background-color:#ffffff1a;color:#fff;border:none}.branch-select :deep(.el-input__suffix){color:#fff}.tips{margin-top:20px;padding:15px;background-color:#f5f7fa;border-radius:5px;border-left:4px solid #409eff}.tips h3{margin-top:0;font-size:16px;margin-bottom:10px}.code-block{background-color:#2d2d2d;color:#f8f8f2;font-family:monospace;padding:10px 15px;border-radius:4px;margin-bottom:10px}.loading-container{display:flex;justify-content:center;align-items:center;min-height:400px}.loading-card{width:300px;text-align:center;padding:30px}.loading-spinner{font-size:48px;margin-bottom:20px;color:#409eff}.loading-text{font-size:18px;color:#606266}.logo[data-v-7161cf53]{will-change:filter;transition:filter .3s}.logo[data-v-7161cf53]:hover{filter:drop-shadow(0 0 2em #42b883aa)}.main-footer[data-v-7161cf53]{background-color:#24292e;color:#fff;padding:10px 20px;display:flex;justify-content:space-between;align-items:center;position:fixed;bottom:0;left:0;right:0;z-index:100}.branch-info[data-v-7161cf53]{display:flex;align-items:center}.footer-right[data-v-7161cf53]{color:#ffffffb3;font-size:12px}
|