feat: v0.4.0 — 配置架构重构 + 一键多端推送 + 项目级代理控制\n\n架构重构:\n- 配置从 Platform→User→Project 重构为扁平的 Projects+Groups+Credentials 模型\n- 侧边栏完全重写,适配新分组→项目树结构\n- 新增分组(Group)管理和凭据(Credential)管理\n\n新功能:\n- 一键多端推送:Push All 按钮,将当前分支/标签推送到所有远程仓库\n- 项目级代理控制:每个项目独立设置代理(跟随全局/强制开启/强制关闭)\n- GitClient 新增 RunWithProxy 方法,支持按调用级别覆盖全局代理\n- 所有网络操作(Pull/Push/Fetch)均支持项目级代理设置\n\n其他:\n- 新增 CHANGELOG.md 版本更新日志\n- 更新 README/README_CN 文档,反映最新 72+ API 方法\n- 版本号升级到 0.4.0(config.yml/info.json/Info.plist)
Some checks failed
Release / build-windows (push) Has been cancelled

This commit is contained in:
zyj
2026-03-12 11:41:26 +08:00
parent 3ec453cc38
commit c787c99622
17 changed files with 1443 additions and 727 deletions

View File

@@ -112,6 +112,9 @@ const showSettings = ref(false)
const settingsLoading = ref(false)
const gitConfig = ref({ userName: '', userEmail: '' })
// ---- 代理开关 ----
const projectProxy = ref(null) // null: 跟随全局, true: 强制使用, false: 强制不使用
// ---- 冲突处理 ----
const isMerging = ref(false)
const conflictFiles = ref([])
@@ -266,6 +269,8 @@ async function loadStatus() {
if (remotes.value.length && !remotes.value.find(r => r.name === currentRemote.value)) {
currentRemote.value = remotes.value[0].name
}
// 同步代理设置
projectProxy.value = result.useProxy ?? null
} else {
errorMsg.value = '获取项目状态返回空值: ' + JSON.stringify(result)
loadingBase.value = false
@@ -542,6 +547,85 @@ async function gitAction(action) {
}
}
// ---- 多端推送 ----
async function pushToAllRemotes() {
if (!props.project?.path) return
actionLoading.value = 'pushAll'
try {
const results = await AppService.PushToAllRemotes(props.project.path)
const successCount = results.filter(r => r.success).length
const failCount = results.length - successCount
if (failCount === 0) {
message.success(`已成功推送到全部 ${successCount} 个远程仓库`)
} else {
Modal.info({
title: '多端推送结果',
content: h('div', results.map(r =>
h('div', { style: `padding: 4px 0; color: ${r.success ? 'var(--success, #a6e3a1)' : '#f38ba8'}` }, [
h('span', { style: 'font-weight: 600; margin-right: 8px;' }, r.remote + ':'),
h('span', r.success ? '✓ 成功' : '✗ ' + r.message),
])
)),
})
}
await loadStatus()
} catch (e) {
console.error('多端推送失败:', e)
Modal.error({ title: '多端推送失败', content: String(e) })
} finally {
actionLoading.value = ''
}
}
async function pushTagToAllRemotes(tag) {
if (!props.project?.path) return
try {
const results = await AppService.PushTagToAllRemotes(props.project.path, tag.name)
const successCount = results.filter(r => r.success).length
const failCount = results.length - successCount
if (failCount === 0) {
message.success(`标签 ${tag.name} 已推送到全部 ${successCount} 个远程仓库`)
} else {
Modal.info({
title: '标签多端推送结果',
content: h('div', results.map(r =>
h('div', { style: `padding: 4px 0; color: ${r.success ? 'var(--success, #a6e3a1)' : '#f38ba8'}` }, [
h('span', { style: 'font-weight: 600; margin-right: 8px;' }, r.remote + ':'),
h('span', r.success ? '✓ 成功' : '✗ ' + r.message),
])
)),
})
}
} catch (e) {
console.error('标签多端推送失败:', e)
Modal.error({ title: '推送失败', content: String(e) })
}
}
// ---- 代理开关 ----
async function toggleProxy(checked) {
if (!props.project?.path) return
const useProxy = checked ? true : false
try {
await AppService.SetProjectProxy(props.project.path, useProxy)
projectProxy.value = useProxy
} catch (e) {
console.error('设置代理失败:', e)
Modal.error({ title: '设置代理失败', content: String(e) })
}
}
async function resetProxy() {
if (!props.project?.path) return
try {
await AppService.SetProjectProxy(props.project.path, null)
projectProxy.value = null
message.success('已恢复跟随全局设置')
} catch (e) {
console.error('重置代理失败:', e)
}
}
function getStatusColor(status) {
const colors = { 'M': 'orange', 'A': 'green', 'D': 'red', '?': 'default' }
return colors[status] || 'default'
@@ -1399,6 +1483,30 @@ const currentRemoteUrl = computed(() => {
</template>
</div>
<a-space class="project-actions">
<a-tooltip :title="projectProxy === null ? '代理: 跟随全局' : projectProxy ? '代理: 已开启' : '代理: 已关闭'">
<a-dropdown :trigger="['click']">
<a-button size="small" :type="projectProxy === true ? 'primary' : 'default'" :ghost="projectProxy === true">
<template #icon><GlobalOutlined /></template>
</a-button>
<template #overlay>
<div class="proxy-dropdown">
<div class="proxy-dropdown-title">代理设置</div>
<div class="proxy-dropdown-item" :class="{ active: projectProxy === null }" @click="resetProxy()">
<span>跟随全局</span>
<span v-if="projectProxy === null" style="color: var(--success, #a6e3a1);"></span>
</div>
<div class="proxy-dropdown-item" :class="{ active: projectProxy === true }" @click="toggleProxy(true)">
<span>强制使用代理</span>
<span v-if="projectProxy === true" style="color: var(--success, #a6e3a1);"></span>
</div>
<div class="proxy-dropdown-item" :class="{ active: projectProxy === false }" @click="toggleProxy(false)">
<span>强制不使用代理</span>
<span v-if="projectProxy === false" style="color: var(--success, #a6e3a1);"></span>
</div>
</div>
</template>
</a-dropdown>
</a-tooltip>
<a-button size="small" :loading="actionLoading === 'fetch'" :disabled="loadingBase" @click="gitAction('fetch')">
<template #icon><SyncOutlined /></template>
Fetch
@@ -1411,6 +1519,12 @@ const currentRemoteUrl = computed(() => {
<template #icon><CloudUploadOutlined /></template>
Push
</a-button>
<a-tooltip title="推送到所有远程仓库">
<a-button size="small" :loading="actionLoading === 'pushAll'" :disabled="loadingBase || remotes.length < 2" @click="pushToAllRemotes">
<template #icon><SendOutlined /></template>
Push All
</a-button>
</a-tooltip>
<a-button size="small" :disabled="loadingBase" @click="loadStatus">
<template #icon><ReloadOutlined /></template>
</a-button>
@@ -1813,11 +1927,16 @@ const currentRemoteUrl = computed(() => {
<TagOutlined class="tag-icon" />
<span class="tag-name">{{ tag.name }}</span>
<div class="tag-action-btns" @click.stop>
<a-tooltip title="推送到远程">
<a-tooltip title="推送到当前远程">
<a-button type="text" size="small" class="tag-action-btn push" @click="pushTag(tag)">
<template #icon><SendOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip title="推送到所有远程" v-if="remotes.length > 1">
<a-button type="text" size="small" class="tag-action-btn push" @click="pushTagToAllRemotes(tag)">
<template #icon><CloudUploadOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip title="删除标签">
<a-button type="text" size="small" class="tag-action-btn delete" @click="deleteTag(tag)">
<template #icon><DeleteOutlined /></template>
@@ -2078,6 +2197,41 @@ const currentRemoteUrl = computed(() => {
flex-shrink: 0;
}
/* 代理设置下拉 */
.proxy-dropdown {
background: var(--dropdown-bg, #1e1e2e);
border: 1px solid var(--border-color, #313244);
border-radius: 8px;
padding: 4px 0;
min-width: 180px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
}
.proxy-dropdown-title {
padding: 6px 12px;
font-size: 11px;
color: var(--text-muted);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
border-bottom: 1px solid var(--border-color, #313244);
}
.proxy-dropdown-item {
padding: 8px 12px;
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
font-size: 13px;
color: var(--text-primary, #cdd6f4);
transition: background 0.15s;
}
.proxy-dropdown-item:hover {
background: var(--hover-bg, rgba(137, 180, 250, 0.08));
}
.proxy-dropdown-item.active {
color: var(--accent, #89b4fa);
}
/* 主体区域 */
.project-body {
flex: 1;