feat: 脚本创作+发布+市场体系

- 数据模型新增: title(必填), description(可选), status(draft/published)
- 新增 API: POST /scripts/:id/publish, GET /api/market (搜索+分页+runtime过滤)
- 前端首页重构: 选语言 → CodeMirror 编辑器(8种语言语法高亮) → 标题/描述 → 草稿/发布
- 新增 /market 页面: 浏览已发布脚本, 搜索+过滤+分页
- 详情页新增: 发布按钮(草稿→市场), title/description 展示
- Shell 类运行时显示 source 命令(继承环境变量)
- backend GetSourceCommand 支持 bash/zsh/sh/fish 四种 shell 格式

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 14:04:15 +08:00
parent e3d380f9ab
commit e6e4357a28
16 changed files with 1051 additions and 183 deletions

View File

@@ -1,9 +1,7 @@
import { CreateScriptResponse, ScriptDetail, RuntimeOption, ExpiresIn } from '../types'
const BASE = ''
import { CreateScriptResponse, ScriptDetail, RuntimeOption, ExpiresIn, MarketResponse } from '../types'
async function request<T>(url: string, options?: RequestInit): Promise<T> {
const res = await fetch(BASE + url, {
const res = await fetch(url, {
headers: { 'Content-Type': 'application/json' },
...options,
})
@@ -15,9 +13,12 @@ async function request<T>(url: string, options?: RequestInit): Promise<T> {
}
export async function createScript(params: {
title: string
description?: string
content: string
runtime: RuntimeOption
expires_in: ExpiresIn
publish: boolean
}): Promise<CreateScriptResponse> {
return request('/api/scripts', {
method: 'POST',
@@ -29,8 +30,14 @@ export async function getScript(id: string): Promise<ScriptDetail> {
return request(`/api/scripts/${id}`)
}
export async function publishScript(id: string, token: string): Promise<{ id: string; status: string }> {
return request(`/api/scripts/${id}/publish?token=${encodeURIComponent(token)}`, {
method: 'POST',
})
}
export async function deleteScript(id: string, token: string): Promise<void> {
await fetch(`${BASE}/api/scripts/${id}?token=${encodeURIComponent(token)}`, {
await fetch(`/api/scripts/${id}?token=${encodeURIComponent(token)}`, {
method: 'DELETE',
}).then((res) => {
if (!res.ok && res.status !== 204) {
@@ -39,6 +46,16 @@ export async function deleteScript(id: string, token: string): Promise<void> {
})
}
export function getCommandUrl(id: string): string {
return `${window.location.origin}/raw/${id}`
}
export async function listMarket(params: {
page?: number
per_page?: number
runtime?: string
search?: string
}): Promise<MarketResponse> {
const query = new URLSearchParams()
if (params.page) query.set('page', String(params.page))
if (params.per_page) query.set('per_page', String(params.per_page))
if (params.runtime) query.set('runtime', params.runtime)
if (params.search) query.set('search', params.search)
return request(`/api/market?${query.toString()}`)
}