11<template >
2- <el-dialog :model-value =" visible" @update:model-value =" onVisibleChange" title =" 🚀 分享到官方市场(GitHub)" width =" 560px " append-to-body >
2+ <el-dialog :model-value =" visible" @update:model-value =" onVisibleChange" title =" 🚀 分享到官方市场(GitHub)" width =" 580px " append-to-body >
33 <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- 管理员定期审核合并后进入官方市场, 其他用户可通过「发现」拉取。
4+ 分享流程:① 填写作者 → ② 在本机生成 <b >market/{type}/{id}.json </b > 并更新 <b >market/index.json </b >
5+ → ③ 登录 GitHub 授权(Token)→ ④ 自动生成 <b >Pull Request </b > 到官方市场仓库
6+ < b >pythonHuang/APIWorkflowCanvasDesignNet</ b >,管理员定期审核合并后进入官方市场( 其他用户可通过「发现」拉取) 。
77 </div >
88 <el-form label-width =" 90px" size =" small" >
9+ <el-form-item label =" 作者" ><el-input v-model =" form.author" placeholder =" 您的署名(如 pythonHuang)" /></el-form-item >
10+ <el-form-item label =" 版本号" ><el-input v-model =" form.version" placeholder =" 如 1.0.0" /></el-form-item >
911 <el-form-item label =" GitHub Token" >
1012 <el-input v-model =" form.token" type =" password" show-password placeholder =" ghp_...(仅保存在本机浏览器)" />
1113 <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 。
14+ 需要 Personal Access Token(classic)并勾选 <b >repo</b > 权限:GitHub → Settings → Developer settings → Tokens。仅用于本次分享操作。
1315 </div >
1416 </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 >
1717 <el-form-item label =" 条目" >
1818 <div style =" font-size :13px ;color :#303133 " >
1919 <span style =" margin-right :4px " >{{ props.icon || '📦' }}</span >{{ props.itemName }}
2020 <el-tag size =" small" style =" margin-left :6px " >{{ typeLabel }}</el-tag >
2121 </div >
2222 </el-form-item >
2323 </el-form >
24+ <div v-if =" shareSteps.length > 0" style =" margin-bottom :8px ;font-size :12px ;color :#606266 ;line-height :1.8 " >
25+ <div v-for =" (s, i) in shareSteps" :key =" i" :style =" `color:${s.ok === undefined ? '#909399' : s.ok ? '#67c23a' : '#f56c6c'}`" >
26+ {{ s.ok === undefined ? '⏳' : s.ok ? '✅' : '❌' }} {{ s.text }}
27+ </div >
28+ </div >
2429 <el-alert v-if =" shareResult" :type =" shareResult.ok ? 'success' : 'error'" :closable =" false" style =" margin-bottom :8px " >
2530 <template #title >{{ shareResult.msg }}</template >
2631 </el-alert >
3439<script setup lang="ts">
3540import { ref , computed } from ' vue'
3641import { ElMessage } from ' element-plus'
42+ import request from ' ../utils/request'
3743
3844const props = defineProps <{
3945 visible: boolean
@@ -58,6 +64,7 @@ const typeLabel = computed(() => typeLabels[props.itemType] || props.itemType)
5864const form = ref ({ token: localStorage .getItem (TOKEN_KEY ) || ' ' , author: ' ' , version: ' 1.0.0' })
5965const sharing = ref (false )
6066const shareResult = ref <{ ok: boolean ; msg: string } | null >(null )
67+ const shareSteps = ref <{ text: string ; ok? : boolean }[]>([])
6168
6269async function gh(path : string , token : string , opts ? : { method? : string ; body? : any }) {
6370 const res = await fetch (` ${GITHUB_API }${path } ` , {
@@ -76,20 +83,44 @@ async function gh(path: string, token: string, opts?: { method?: string; body?:
7683
7784function sleep(ms : number ) { return new Promise (r => setTimeout (r , ms )) }
7885
86+ /** base64 编码(支持中文) */
87+ function b64(s : string ) { return btoa (unescape (encodeURIComponent (s ))) }
88+
7989async function doShare() {
80- const token = form .value .token .trim ()
81- if (! token ) { ElMessage .warning (' 请填写 GitHub 访问令牌' ); return }
8290 if (! form .value .author .trim ()) { ElMessage .warning (' 请填写作者' ); return }
83- localStorage .setItem (TOKEN_KEY , token )
91+ if (! form .value .token .trim ()) { ElMessage .warning (' 请填写 GitHub 访问令牌' ); return }
92+ localStorage .setItem (TOKEN_KEY , form .value .token .trim ())
8493
8594 sharing .value = true
8695 shareResult .value = null
96+ shareSteps .value = []
97+ const step = (text : string , ok ? : boolean ) => shareSteps .value .push ({ text , ok })
98+ const token = form .value .token .trim ()
99+
87100 try {
88- // 1. 校验令牌并获取登录用户
101+ // ① 本地生成 market/{type}/{id}.json 并更新 market/index.json(后端落盘)
102+ step (' 生成本地 market/{type}/{id}.json 并更新 index.json...' )
103+ const genRes: any = await request .post (' /market/generate-share-file' , {
104+ itemType: props .itemType ,
105+ name: props .itemName ,
106+ description: props .description || ' ' ,
107+ icon: props .icon || ' 📦' ,
108+ author: form .value .author .trim (),
109+ version: form .value .version .trim () || ' 1.0.0' ,
110+ contentJson: props .contentJson
111+ })
112+ const { id, itemJson, indexEntry } = genRes .data || {}
113+ if (! id || ! itemJson ) throw new Error (' 生成分享文件失败' )
114+ step (` 已生成本地文件 market/${props .itemType }/${id }.json 并更新 index.json ` , true )
115+
116+ // ② 校验令牌并获取登录用户
117+ step (' 校验 GitHub Token...' )
89118 const user = await gh (' /user' , token )
90119 const username = user .login
120+ step (` GitHub 登录成功(${username }) ` , true )
91121
92- // 2. 确保 fork 存在(不存在则创建并轮询等待)
122+ // ③ 确保 fork 存在(不存在则创建并轮询等待)
123+ step (' 确保仓库 fork 存在...' )
93124 let fork = await gh (` /repos/${username }/${REPO_NAME } ` , token ).catch (() => null )
94125 if (! fork ) {
95126 await gh (` /repos/${REPO_OWNER }/${REPO_NAME }/forks ` , token , { method: ' POST' })
@@ -100,44 +131,54 @@ async function doShare() {
100131 }
101132 if (! fork ) throw new Error (' Fork 创建超时,请稍后在 GitHub 页面确认后重试' )
102133 }
134+ step (' fork 就绪' , true )
103135
104- // 3. 基于 fork 默认分支创建分享分支
136+ // ④ 基于 fork 默认分支创建分享分支
105137 const baseBranch = fork .default_branch || ' main'
106138 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 } `
139+ const branch = ` market-share-${props .itemType }-${id } `
109140 try {
110141 await gh (` /repos/${username }/${REPO_NAME }/git/refs ` , token , {
111142 method: ' POST' ,
112143 body: { ref: ` refs/heads/${branch } ` , sha: baseRef .object .sha }
113144 })
114145 } catch { /* 分支已存在则直接复用 */ }
115146
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 `
147+ // ⑤ 写入条目文件 market/{type}/{id}.json
148+ step (` 写入 market/${props .itemType }/${id }.json... ` )
149+ const filePath = ` market/${props .itemType }/${id }.json `
131150 await gh (` /repos/${username }/${REPO_NAME }/contents/${encodeURIComponent (filePath )} ` , token , {
132151 method: ' PUT' ,
133152 body: {
134153 message: ` market: add ${typeLabel .value }「${props .itemName }」 by ${form .value .author .trim ()} ` ,
135- content: btoa ( unescape ( encodeURIComponent ( itemJson )) ),
154+ content: b64 ( itemJson ),
136155 branch
137156 }
138157 })
158+ step (` 已写入 ${filePath } ` , true )
139159
140- // 5. 创建 Pull Request
160+ // ⑥ 基于官方仓库最新 index.json 合并本条索引并写入
161+ step (' 更新 market/index.json...' )
162+ const upstreamIndex = await fetchUpstreamIndex ()
163+ const idx = upstreamIndex .findIndex ((e : any ) =>
164+ Number (e ?.id ) === Number (indexEntry .id ) && e ?.type === indexEntry .type )
165+ if (idx >= 0 ) upstreamIndex [idx ] = indexEntry
166+ else upstreamIndex .push (indexEntry )
167+ const indexJson = JSON .stringify ({ items: upstreamIndex }, null , 2 )
168+ const idxFile = await gh (` /repos/${username }/${REPO_NAME }/contents/market/index.json?ref=${encodeURIComponent (baseBranch )} ` , token ).catch (() => null )
169+ await gh (` /repos/${username }/${REPO_NAME }/contents/market/index.json ` , token , {
170+ method: ' PUT' ,
171+ body: {
172+ message: ` market: update index.json (${typeLabel .value }「${props .itemName }」) ` ,
173+ content: b64 (indexJson ),
174+ branch ,
175+ ... (idxFile ?.sha ? { sha: idxFile .sha } : {})
176+ }
177+ })
178+ step (' 已更新 market/index.json' , true )
179+
180+ // ⑦ 创建 Pull Request
181+ step (' 创建 Pull Request...' )
141182 const pr = await gh (` /repos/${REPO_OWNER }/${REPO_NAME }/pulls ` , token , {
142183 method: ' POST' ,
143184 body: {
@@ -152,19 +193,30 @@ async function doShare() {
152193 ` 描述:${props .description || ' ' } ` ,
153194 ' ' ,
154195 ` 文件:${filePath } ` ,
196+ ` 索引:market/index.json(已同步本条目) ` ,
155197 ' ' ,
156198 ' 管理员审核合并后进入官方市场(market 目录),其他用户可通过「市场 → 发现」拉取。'
157199 ].join (' \n ' )
158200 }
159201 })
160-
202+ step ( ` PR 创建成功:${ pr . html_url } ` , true )
161203 shareResult .value = { ok: true , msg: ` PR 创建成功:${pr .html_url } ` }
162204 emit (' shared' , pr .html_url )
163205 window .open (pr .html_url , ' _blank' )
164206 } catch (e : any ) {
165- shareResult .value = { ok: false , msg: ` 分享失败:${e ?.message || ' 未知错误' } ` }
207+ const msg = e ?.message || ' 未知错误'
208+ shareSteps .value [shareSteps .value .length - 1 ] && (shareSteps .value [shareSteps .value .length - 1 ].ok = false )
209+ shareResult .value = { ok: false , msg: ` 分享失败:${msg } ` }
166210 } finally {
167211 sharing .value = false
168212 }
169213}
214+
215+ /** 拉取官方仓库最新 market/index.json 条目列表(不存在则返回空数组) */
216+ async function fetchUpstreamIndex(): Promise <any []> {
217+ const res = await fetch (` https://raw.githubusercontent.com/${REPO_OWNER }/${REPO_NAME }/main/market/index.json ` )
218+ if (! res .ok ) return []
219+ const data = await res .json ().catch (() => ({}))
220+ return Array .isArray (data ) ? data : (data ?.items || [])
221+ }
170222 </script >
0 commit comments