Compare commits

...

3 Commits

Author SHA1 Message Date
cfq 18db27a22c feat(git): 在面板中显示当前Git分支并改进布局
- 新增获取当前分支的API接口和前端状态管理
- 在Git面板顶部显示当前分支名称和图标
- 重新组织操作按钮布局,将远程仓库和刷新按钮移至顶部工具栏
- 为状态列表添加文件数量统计
- 优化响应式布局,使拉取/推送按钮等宽显示
2026-01-28 20:00:26 +08:00
cfq 44a2c54da3 feat(git): 增强Git面板功能并支持远程仓库操作
- 添加远程仓库URL获取与外部链接打开功能
- 改进git状态显示为可视化文件列表,支持状态分类着色
- 修改git status命令以正确处理特殊字符路径
- 默认提交消息设置为"md helper"
- 在Git面板添加打开远程仓库的快捷按钮
2026-01-28 19:16:35 +08:00
cfq f846aecee5 chore(release): 1.2.0 2026-01-28 18:49:06 +08:00
6 changed files with 241 additions and 21 deletions

View File

@ -2,6 +2,15 @@
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. 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.
# [1.2.0](https://gitea.cuifuqi.online/utools-plug-in/markdown/compare/v1.1.0...v1.2.0) (2026-01-28)
### Features
* **editor:** 添加图片预览功能并支持缩放 ([3373abe](https://gitea.cuifuqi.online/utools-plug-in/markdown/commits/3373abe563096eef18a631fc1c3125bee642a825))
# 1.1.0 (2026-01-28) # 1.1.0 (2026-01-28)

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "project-helper", "name": "project-helper",
"version": "1.1.0", "version": "1.2.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "project-helper", "name": "project-helper",
"version": "1.1.0", "version": "1.2.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ant-design/icons-vue": "^7.0.1", "@ant-design/icons-vue": "^7.0.1",

View File

@ -1,7 +1,7 @@
{ {
"type": "module", "type": "module",
"name": "project-helper", "name": "project-helper",
"version": "1.1.0", "version": "1.2.0",
"description": "markdown 笔记本", "description": "markdown 笔记本",
"author": "Project Helper", "author": "Project Helper",
"license": "MIT", "license": "MIT",

View File

@ -105,6 +105,11 @@ window.services = {
window.utools.shellShowItemInFolder(itemPath); window.utools.shellShowItemInFolder(itemPath);
}, },
// 打开外部链接
openExternal(url) {
window.utools.shellOpenExternal(url);
},
// --- Image Service --- // --- Image Service ---
// 保存图片 // 保存图片
@ -168,7 +173,11 @@ window.services = {
}, },
async gitStatus(dirPath) { async gitStatus(dirPath) {
return this.execGit("status --short", dirPath); return this.execGit("-c core.quotePath=false status --short", dirPath);
},
async gitBranch(dirPath) {
return this.execGit("branch --show-current", dirPath);
}, },
async gitAdd(dirPath, files = ".") { async gitAdd(dirPath, files = ".") {
@ -189,6 +198,48 @@ window.services = {
return this.execGit("pull", dirPath); return this.execGit("pull", dirPath);
}, },
async gitRemoteUrl(dirPath) {
try {
const configPath = path.join(dirPath, '.git', 'config');
if (!fs.existsSync(configPath)) {
return { success: false, error: 'Config file not found' };
}
const content = await fs.promises.readFile(configPath, 'utf-8');
// 简单解析 ini
// 寻找 [remote "origin"] 及其下的 url
const lines = content.split('\n');
let inRemoteOrigin = false;
let url = null;
for (const line of lines) {
const trimmed = line.trim();
if (trimmed === '[remote "origin"]') {
inRemoteOrigin = true;
continue;
}
if (inRemoteOrigin) {
if (trimmed.startsWith('[')) {
// 进入下一个 section结束查找
break;
}
if (trimmed.startsWith('url =')) {
url = trimmed.substring(5).trim();
break;
}
}
}
if (url) {
return { success: true, stdout: url };
}
return { success: false, error: 'Remote origin url not found' };
} catch (e) {
return { success: false, error: e.message };
}
},
// --- Search Service --- // --- Search Service ---
// 搜索文件 (文件名) // 搜索文件 (文件名)

View File

@ -5,26 +5,46 @@
</div> </div>
<div v-else class="git-content"> <div v-else class="git-content">
<div class="git-header">
<div class="git-branch" v-if="state.branch">
<BranchesOutlined />
<span class="branch-name">{{ state.branch }}</span>
</div>
<div class="git-toolbar">
<a-button @click="openRemoteUrl" :disabled="!state.remoteUrl" title="打开 Git 仓库" size="small">
<template #icon><GithubOutlined /></template>
</a-button>
<a-button @click="handleRefresh" :loading="state.loading" size="small">
<template #icon><ReloadOutlined /></template>
</a-button>
</div>
</div>
<div class="git-actions"> <div class="git-actions">
<a-button-group> <a-button-group class="action-group">
<a-button @click="handlePull" :loading="state.loading"> <a-button @click="handlePull" :loading="state.loading" block>
<template #icon><ArrowDownOutlined /></template> <template #icon><ArrowDownOutlined /></template>
拉取 拉取
</a-button> </a-button>
<a-button @click="handlePush" :loading="state.loading"> <a-button @click="handlePush" :loading="state.loading" block>
<template #icon><ArrowUpOutlined /></template> <template #icon><ArrowUpOutlined /></template>
推送 推送
</a-button> </a-button>
</a-button-group> </a-button-group>
<a-button @click="handleRefresh" :loading="state.loading">
<template #icon><ReloadOutlined /></template>
</a-button>
</div> </div>
<div class="git-status"> <div class="git-status">
<h3>状态</h3> <div class="status-header">
<pre class="status-output">{{ state.statusOutput || '无变更' }}</pre> <h3>状态</h3>
<span class="status-count" v-if="state.statusFiles.length">{{ state.statusFiles.length }}</span>
</div>
<div class="status-list" v-if="state.statusFiles.length > 0">
<div v-for="(file, index) in state.statusFiles" :key="index" class="status-item">
<span class="status-badge" :class="getStatusClass(file.status)">{{ file.status }}</span>
<span class="file-path" :title="file.path">{{ file.path }}</span>
</div>
</div>
<div v-else class="empty-status">无变更</div>
</div> </div>
<div class="git-commit"> <div class="git-commit">
@ -51,15 +71,15 @@
<script setup> <script setup>
import { ref, onMounted, watch } from 'vue'; import { ref, onMounted, watch } from 'vue';
import { ArrowDownOutlined, ArrowUpOutlined, ReloadOutlined } from '@ant-design/icons-vue'; import { ArrowDownOutlined, ArrowUpOutlined, ReloadOutlined, GithubOutlined, BranchesOutlined } from '@ant-design/icons-vue';
import { useGit } from '../composables/useGit'; import { useGit } from '../composables/useGit';
import { useFileTree } from '../composables/useFileTree'; import { useFileTree } from '../composables/useFileTree';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
const { state, checkGitRepo, getStatus, commit, push, pull } = useGit(); const { state, checkGitRepo, getStatus, commit, push, pull, openRemoteUrl } = useGit();
const { state: fileTreeState } = useFileTree(); const { state: fileTreeState } = useFileTree();
const commitMessage = ref(''); const commitMessage = ref(`md helper`);
// Git // Git
watch(() => fileTreeState.rootPath, async (newPath) => { watch(() => fileTreeState.rootPath, async (newPath) => {
@ -114,6 +134,13 @@ const handlePull = async () => {
message.error('拉取异常: ' + e.message); message.error('拉取异常: ' + e.message);
} }
}; };
const getStatusClass = (status) => {
if (status.includes('M')) return 'status-modified';
if (status.includes('A') || status.includes('?')) return 'status-added';
if (status.includes('D')) return 'status-deleted';
return '';
};
</script> </script>
<style scoped> <style scoped>
@ -127,25 +154,111 @@ const handlePull = async () => {
text-align: center; text-align: center;
} }
.git-actions { .git-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid var(--border-color);
}
.git-branch {
display: flex;
align-items: center;
font-weight: bold;
color: var(--primary-color, #1890ff);
}
.branch-name {
margin-left: 6px;
}
.git-toolbar {
display: flex;
gap: 8px;
}
.git-actions {
margin-bottom: 20px; margin-bottom: 20px;
} }
.action-group {
display: flex;
width: 100%;
}
.action-group .ant-btn {
flex: 1;
}
.git-status { .git-status {
margin-bottom: 20px; margin-bottom: 20px;
} }
.status-output { .status-header {
background-color: var(--app-background); display: flex;
color: var(--text-color); justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.status-header h3 {
margin: 0;
}
.status-count {
background-color: var(--border-color);
padding: 2px 6px;
border-radius: 10px;
font-size: 12px;
}
.status-list {
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
padding: 8px;
border-radius: 4px; border-radius: 4px;
max-height: 200px; max-height: 200px;
overflow-y: auto; overflow-y: auto;
background-color: var(--app-background);
}
.status-item {
display: flex;
align-items: center;
padding: 4px 8px;
font-size: 12px;
border-bottom: 1px solid var(--border-color);
}
.status-item:last-child {
border-bottom: none;
}
.status-badge {
display: inline-block;
width: 24px;
margin-right: 8px;
font-family: monospace; font-family: monospace;
font-weight: bold;
}
.status-modified { color: #1890ff; }
.status-added { color: #52c41a; }
.status-deleted { color: #f5222d; }
.file-path {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.empty-status {
text-align: center;
color: #999;
padding: 16px;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 12px; font-size: 12px;
} }

View File

@ -3,6 +3,9 @@ import { reactive } from 'vue';
const state = reactive({ const state = reactive({
isGitRepo: false, isGitRepo: false,
statusOutput: '', statusOutput: '',
statusFiles: [],
remoteUrl: '',
branch: '',
loading: false loading: false
}); });
@ -14,6 +17,36 @@ export function useGit() {
if (state.isGitRepo) { if (state.isGitRepo) {
// 自动获取一次状态 // 自动获取一次状态
await getStatus(rootDir); await getStatus(rootDir);
await getRemoteUrl(rootDir);
await getBranch(rootDir);
}
};
const getBranch = async (rootDir) => {
try {
const res = await window.services.gitBranch(rootDir);
if (res.success) {
state.branch = res.stdout.trim();
}
} catch (e) {
console.error("Failed to get branch", e);
}
};
const getRemoteUrl = async (rootDir) => {
try {
const res = await window.services.gitRemoteUrl(rootDir);
if (res.success) {
state.remoteUrl = res.stdout.trim();
}
} catch (e) {
console.error("Failed to get remote url", e);
}
};
const openRemoteUrl = () => {
if (state.remoteUrl) {
window.services.openExternal(state.remoteUrl);
} }
}; };
@ -23,6 +56,17 @@ export function useGit() {
const res = await window.services.gitStatus(rootDir); const res = await window.services.gitStatus(rootDir);
if (res.success) { if (res.success) {
state.statusOutput = res.stdout; state.statusOutput = res.stdout;
// 解析状态文件列表
state.statusFiles = res.stdout
.split('\n')
.filter(line => line.trim())
.map(line => {
// git status --short 格式: XY PATH
// 前两个字符是状态,后面是路径
const status = line.substring(0, 2);
const path = line.substring(3);
return { status, path };
});
} }
} finally { } finally {
state.loading = false; state.loading = false;
@ -65,6 +109,9 @@ export function useGit() {
state, state,
checkGitRepo, checkGitRepo,
getStatus, getStatus,
getRemoteUrl,
getBranch,
openRemoteUrl,
commit, commit,
push, push,
pull pull