Skip to content

Commit c7202c9

Browse files
committed
feat: 市场分享到官方GitHub-发布弹窗成功后可选分享,新组件MarketShareDialog(GitHub Token授权(仅存本机)/填写作者与版本号),自动完成:校验令牌→确保fork→创建分支→写入market/{type}/{id}.json→生成Pull Request到pythonHuang/APIWorkflowCanvasDesignNet并打开PR页面,管理员审核合并后进入官方市场,覆盖流程/接口/模型助手/Skill/报表5类条目
1 parent ad7f7b2 commit c7202c9

2 files changed

Lines changed: 187 additions & 3 deletions

File tree

‎JuggleNet6.Frontend/src/components/MarketPublishDialog.vue‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,33 @@
55
<el-form-item label="描述"><el-input v-model="form.description" type="textarea" :rows="2" placeholder="用途与亮点说明" /></el-form-item>
66
<el-form-item label="分组"><el-input v-model="form.groupName" placeholder="可选" /></el-form-item>
77
</el-form>
8-
<div style="color:#909399;font-size:12px">发布后条目进入市场,平台内其他租户可浏览并一键导入。</div>
8+
<div style="color:#909399;font-size:12px">
9+
发布后条目进入平台市场,其他租户可浏览并一键导入;发布成功后可选择<b>分享到官方 GitHub 市场</b>(生成 PR 供管理员审核)。
10+
</div>
911
<template #footer>
1012
<el-button size="small" @click="emit('update:visible', false)">取消</el-button>
1113
<el-button size="small" type="primary" :loading="publishing" @click="doPublish">发布</el-button>
1214
</template>
1315
</el-dialog>
16+
17+
<!-- 分享到官方 GitHub 市场(PR) -->
18+
<MarketShareDialog v-model:visible="shareVisible" :item-type="props.itemType" :item-name="form.itemName"
19+
:description="form.description" :icon="props.icon" :content-json="props.contentJson" />
1420
</template>
1521

1622
<script setup lang="ts">
1723
import { ref, watch } from 'vue'
18-
import { ElMessage } from 'element-plus'
24+
import { ElMessage, ElMessageBox } from 'element-plus'
1925
import request from '../utils/request'
26+
import MarketShareDialog from './MarketShareDialog.vue'
2027
2128
const props = defineProps<{
2229
visible: boolean
2330
itemType: string
2431
defaultName?: string
2532
defaultDesc?: string
2633
defaultGroup?: string
34+
icon?: string
2735
contentJson: string
2836
}>()
2937
const emit = defineEmits<{ (e: 'update:visible', v: boolean): void; (e: 'published'): void }>()
@@ -32,6 +40,7 @@ function onVisibleChange(v: boolean) { emit('update:visible', v) }
3240
3341
const form = ref({ itemName: '', description: '', groupName: '' })
3442
const publishing = ref(false)
43+
const shareVisible = ref(false)
3544
3645
watch(() => props.visible, v => {
3746
if (!v) return
@@ -53,9 +62,14 @@ async function doPublish() {
5362
groupName: form.value.groupName,
5463
contentJson: props.contentJson
5564
})
56-
ElMessage.success('已发布到市场')
5765
emit('update:visible', false)
5866
emit('published')
67+
try {
68+
await ElMessageBox.confirm(
69+
'已发布到平台市场!是否同时分享到官方 GitHub 市场?将自动生成 Pull Request,管理员审核合并后进入官方市场。',
70+
'分享到官方市场', { confirmButtonText: '分享', cancelButtonText: '暂不', type: 'info' })
71+
shareVisible.value = true
72+
} catch { /* 暂不分享 */ }
5973
} catch { /* 拦截器已提示 */ } finally { publishing.value = false }
6074
}
6175
</script>
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<template>
2+
<el-dialog :model-value="visible" @update:model-value="onVisibleChange" title="🚀 分享到官方市场(GitHub)" width="560px" append-to-body>
3+
<div style="margin-bottom:10px;color:#606266;font-size:13px;line-height:1.7">
4+
将当前条目以 <b>JSON 文件</b> 形式生成 <b>Pull Request</b> 到官方市场仓库
5+
<b>pythonHuang/APIWorkflowCanvasDesignNet</b> 的 <b>market/{type}/{id}.json</b>,
6+
管理员定期审核合并后进入官方市场,其他用户可通过「发现」拉取。
7+
</div>
8+
<el-form label-width="90px" size="small">
9+
<el-form-item label="GitHub Token">
10+
<el-input v-model="form.token" type="password" show-password placeholder="ghp_...(仅保存在本机浏览器)" />
11+
<div style="font-size:11px;color:#909399;line-height:1.6">
12+
需要 Personal Access Token(classic)并勾选 <b>repo</b> 权限:GitHub → Settings → Developer settings → Tokens。仅用于本次分享操作,保存在 localStorage。
13+
</div>
14+
</el-form-item>
15+
<el-form-item label="作者"><el-input v-model="form.author" placeholder="您的署名(如 pythonHuang)" /></el-form-item>
16+
<el-form-item label="版本号"><el-input v-model="form.version" placeholder="如 1.0.0" /></el-form-item>
17+
<el-form-item label="条目">
18+
<div style="font-size:13px;color:#303133">
19+
<span style="margin-right:4px">{{ props.icon || '📦' }}</span>{{ props.itemName }}
20+
<el-tag size="small" style="margin-left:6px">{{ typeLabel }}</el-tag>
21+
</div>
22+
</el-form-item>
23+
</el-form>
24+
<el-alert v-if="shareResult" :type="shareResult.ok ? 'success' : 'error'" :closable="false" style="margin-bottom:8px">
25+
<template #title>{{ shareResult.msg }}</template>
26+
</el-alert>
27+
<template #footer>
28+
<el-button size="small" @click="emit('update:visible', false)">关闭</el-button>
29+
<el-button size="small" type="primary" :loading="sharing" @click="doShare">🚀 生成 Pull Request</el-button>
30+
</template>
31+
</el-dialog>
32+
</template>
33+
34+
<script setup lang="ts">
35+
import { ref, computed } from 'vue'
36+
import { ElMessage } from 'element-plus'
37+
38+
const props = defineProps<{
39+
visible: boolean
40+
itemType: string
41+
itemName: string
42+
description?: string
43+
icon?: string
44+
contentJson: string
45+
}>()
46+
const emit = defineEmits<{ (e: 'update:visible', v: boolean): void; (e: 'shared', url: string): void }>()
47+
48+
function onVisibleChange(v: boolean) { emit('update:visible', v) }
49+
50+
const GITHUB_API = 'https://api.github.com'
51+
const REPO_OWNER = 'pythonHuang'
52+
const REPO_NAME = 'APIWorkflowCanvasDesignNet'
53+
const TOKEN_KEY = 'market_github_token'
54+
55+
const typeLabels: Record<string, string> = { api: '接口', flow: '流程', assistant: '模型助手', skill: 'Skill', report: '报表' }
56+
const typeLabel = computed(() => typeLabels[props.itemType] || props.itemType)
57+
58+
const form = ref({ token: localStorage.getItem(TOKEN_KEY) || '', author: '', version: '1.0.0' })
59+
const sharing = ref(false)
60+
const shareResult = ref<{ ok: boolean; msg: string } | null>(null)
61+
62+
async function gh(path: string, token: string, opts?: { method?: string; body?: any }) {
63+
const res = await fetch(`${GITHUB_API}${path}`, {
64+
headers: {
65+
Authorization: `token ${token}`,
66+
Accept: 'application/vnd.github+json',
67+
'Content-Type': 'application/json'
68+
},
69+
method: opts?.method || 'GET',
70+
body: opts?.body ? JSON.stringify(opts.body) : undefined
71+
})
72+
const data = await res.json().catch(() => ({}))
73+
if (!res.ok) throw new Error(data?.message || `GitHub API 返回 ${res.status}`)
74+
return data
75+
}
76+
77+
function sleep(ms: number) { return new Promise(r => setTimeout(r, ms)) }
78+
79+
async function doShare() {
80+
const token = form.value.token.trim()
81+
if (!token) { ElMessage.warning('请填写 GitHub 访问令牌'); return }
82+
if (!form.value.author.trim()) { ElMessage.warning('请填写作者'); return }
83+
localStorage.setItem(TOKEN_KEY, token)
84+
85+
sharing.value = true
86+
shareResult.value = null
87+
try {
88+
// 1. 校验令牌并获取登录用户
89+
const user = await gh('/user', token)
90+
const username = user.login
91+
92+
// 2. 确保 fork 存在(不存在则创建并轮询等待)
93+
let fork = await gh(`/repos/${username}/${REPO_NAME}`, token).catch(() => null)
94+
if (!fork) {
95+
await gh(`/repos/${REPO_OWNER}/${REPO_NAME}/forks`, token, { method: 'POST' })
96+
for (let i = 0; i < 20; i++) {
97+
await sleep(3000)
98+
fork = await gh(`/repos/${username}/${REPO_NAME}`, token).catch(() => null)
99+
if (fork) break
100+
}
101+
if (!fork) throw new Error('Fork 创建超时,请稍后在 GitHub 页面确认后重试')
102+
}
103+
104+
// 3. 基于 fork 默认分支创建分享分支
105+
const baseBranch = fork.default_branch || 'main'
106+
const baseRef = await gh(`/repos/${username}/${REPO_NAME}/git/ref/heads/${baseBranch}`, token)
107+
const shareId = Math.floor(Date.now() / 1000)
108+
const branch = `market-share-${props.itemType}-${shareId}`
109+
try {
110+
await gh(`/repos/${username}/${REPO_NAME}/git/refs`, token, {
111+
method: 'POST',
112+
body: { ref: `refs/heads/${branch}`, sha: baseRef.object.sha }
113+
})
114+
} catch { /* 分支已存在则直接复用 */ }
115+
116+
// 4. 组装条目 JSON 并写入 market/{type}/{id}.json
117+
let content: any = {}
118+
try { content = JSON.parse(props.contentJson || '{}') } catch { content = {} }
119+
const itemJson = JSON.stringify({
120+
id: shareId,
121+
type: props.itemType,
122+
name: props.itemName,
123+
description: props.description || '',
124+
icon: props.icon || '📦',
125+
author: form.value.author.trim(),
126+
version: form.value.version.trim() || '1.0.0',
127+
updatedAt: new Date().toISOString().slice(0, 10),
128+
content
129+
}, null, 2)
130+
const filePath = `market/${props.itemType}/${shareId}.json`
131+
await gh(`/repos/${username}/${REPO_NAME}/contents/${encodeURIComponent(filePath)}`, token, {
132+
method: 'PUT',
133+
body: {
134+
message: `market: add ${typeLabel.value}「${props.itemName}」 by ${form.value.author.trim()}`,
135+
content: btoa(unescape(encodeURIComponent(itemJson))),
136+
branch
137+
}
138+
})
139+
140+
// 5. 创建 Pull Request
141+
const pr = await gh(`/repos/${REPO_OWNER}/${REPO_NAME}/pulls`, token, {
142+
method: 'POST',
143+
body: {
144+
title: `[市场] ${typeLabel.value}:${props.itemName}(by ${form.value.author.trim()})`,
145+
head: `${username}:${branch}`,
146+
base: baseBranch,
147+
body: [
148+
`分享类型:${typeLabel.value}`,
149+
`名称:${props.itemName}`,
150+
`作者:${form.value.author.trim()}`,
151+
`版本:${form.value.version.trim() || '1.0.0'}`,
152+
`描述:${props.description || ''}`,
153+
'',
154+
`文件:${filePath}`,
155+
'',
156+
'管理员审核合并后进入官方市场(market 目录),其他用户可通过「市场 → 发现」拉取。'
157+
].join('\n')
158+
}
159+
})
160+
161+
shareResult.value = { ok: true, msg: `PR 创建成功:${pr.html_url}` }
162+
emit('shared', pr.html_url)
163+
window.open(pr.html_url, '_blank')
164+
} catch (e: any) {
165+
shareResult.value = { ok: false, msg: `分享失败:${e?.message || '未知错误'}` }
166+
} finally {
167+
sharing.value = false
168+
}
169+
}
170+
</script>

0 commit comments

Comments
 (0)