实现小游戏与小程序项目创建发布
This commit is contained in:
@@ -39,6 +39,7 @@ import type { OpencodeProject } from '@/types/opencode';
|
||||
import { buildConfiguredModelOptions } from '@/lib/model-options';
|
||||
import { useProjectConfigStore } from '@/stores/project-config';
|
||||
import { EMPTY_AGENT_USER_PROFILE, getProfileAccountKey, isUserProfileComplete, useUserProfileStore, type UserGender } from '@/stores/user-profile';
|
||||
import type { ProjectType } from '../../../shared/project-config';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
type OpenDialogResult = {
|
||||
@@ -162,6 +163,7 @@ export function Sidebar({ sidebarPeekOpen = false, onSidebarPeekChange }: Sideba
|
||||
const [newProjectName, setNewProjectName] = useState('');
|
||||
const [newProjectSelectedPath, setNewProjectSelectedPath] = useState('');
|
||||
const [newProjectDirectoryMode, setNewProjectDirectoryMode] = useState<ProjectDirectoryMode>('use-selected-directory');
|
||||
const [newProjectType, setNewProjectType] = useState<ProjectType>('mini_game');
|
||||
const [newProjectDefaultModel, setNewProjectDefaultModel] = useState('');
|
||||
const [createProjectError, setCreateProjectError] = useState<string | null>(null);
|
||||
const [creatingProject, setCreatingProject] = useState(false);
|
||||
@@ -391,6 +393,7 @@ export function Sidebar({ sidebarPeekOpen = false, onSidebarPeekChange }: Sideba
|
||||
setNewProjectName('');
|
||||
setNewProjectSelectedPath('');
|
||||
setNewProjectDirectoryMode('use-selected-directory');
|
||||
setNewProjectType('mini_game');
|
||||
setNewProjectDefaultModel('');
|
||||
setCreateProjectError(null);
|
||||
setCreateDialogOpen(true);
|
||||
@@ -399,6 +402,7 @@ export function Sidebar({ sidebarPeekOpen = false, onSidebarPeekChange }: Sideba
|
||||
const closeCreateProject = () => {
|
||||
if (creatingProject) return;
|
||||
setCreateDialogOpen(false);
|
||||
setNewProjectType('mini_game');
|
||||
setNewProjectDefaultModel('');
|
||||
setCreateProjectError(null);
|
||||
};
|
||||
@@ -460,6 +464,7 @@ export function Sidebar({ sidebarPeekOpen = false, onSidebarPeekChange }: Sideba
|
||||
const project = await createProject({
|
||||
directoryMode: newProjectDirectoryMode,
|
||||
selectedPath,
|
||||
projectType: newProjectType,
|
||||
...(newProjectDirectoryMode === 'create-child-directory' ? { projectName } : {}),
|
||||
...(newProjectDefaultModel ? { defaultModel: newProjectDefaultModel } : {}),
|
||||
});
|
||||
@@ -666,6 +671,46 @@ export function Sidebar({ sidebarPeekOpen = false, onSidebarPeekChange }: Sideba
|
||||
void confirmCreateProject();
|
||||
}}
|
||||
>
|
||||
<fieldset className="space-y-2">
|
||||
<legend className="text-xs font-semibold">项目类型</legend>
|
||||
<div className="grid gap-2 sm:grid-cols-3">
|
||||
{([
|
||||
['mini_game', '小游戏', '适合实时互动、Canvas 和关卡玩法;创建后可直接提交审核。'],
|
||||
['mini_program', '小程序', '适合页面、表单和轻量工具;创建后可直接提交审核。'],
|
||||
['custom', '自定义项目', '只创建项目空间,不配置默认发布方式。'],
|
||||
] as const).map(([value, title, description]) => (
|
||||
<label
|
||||
key={value}
|
||||
className={cn(
|
||||
'min-h-28 cursor-pointer rounded-xl bg-white p-3 shadow-soft transition-[box-shadow,transform] active:scale-[0.96]',
|
||||
newProjectType === value
|
||||
? 'ring-2 ring-brand/35 shadow-float'
|
||||
: 'ring-1 ring-foreground/10 hover:shadow-float',
|
||||
)}
|
||||
>
|
||||
<span className="flex items-center gap-2 text-sm font-semibold">
|
||||
<input
|
||||
type="radio"
|
||||
name="project-type"
|
||||
value={value}
|
||||
aria-label={title}
|
||||
checked={newProjectType === value}
|
||||
onChange={() => {
|
||||
setNewProjectType(value);
|
||||
setCreateProjectError(null);
|
||||
}}
|
||||
disabled={creatingProject}
|
||||
/>
|
||||
{title}
|
||||
</span>
|
||||
<span className="mt-2 block text-[11px] font-medium leading-5 text-muted-foreground [text-wrap:pretty]">
|
||||
{description}
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset className="space-y-2">
|
||||
<legend className="text-xs font-semibold">项目目录方式</legend>
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
type WorksProjectMetadataInput,
|
||||
} from '@/lib/works-square';
|
||||
import type { OpencodeProject } from '@/types/opencode';
|
||||
import type { ProjectType } from '../../../shared/project-config';
|
||||
|
||||
type PublishPhase = 'idle' | 'submitting' | 'polling' | 'succeeded' | 'failed' | 'uncertain';
|
||||
|
||||
@@ -23,6 +24,7 @@ type BuildVersion = {
|
||||
|
||||
type ProjectPublishActionProps = {
|
||||
project: OpencodeProject;
|
||||
projectType: Exclude<ProjectType, 'custom'>;
|
||||
};
|
||||
|
||||
const BUILD_POLL_INTERVAL_MS = 2_000;
|
||||
@@ -63,7 +65,7 @@ function buttonLabel(phase: PublishPhase): string {
|
||||
return '一键提交审核';
|
||||
}
|
||||
|
||||
export function ProjectPublishAction({ project }: ProjectPublishActionProps) {
|
||||
export function ProjectPublishAction({ project, projectType }: ProjectPublishActionProps) {
|
||||
const [phase, setPhase] = useState<PublishPhase>('idle');
|
||||
const [failure, setFailure] = useState<WorksPublishFailure | null>(null);
|
||||
const pollGeneration = useRef(0);
|
||||
@@ -79,7 +81,7 @@ export function ProjectPublishAction({ project }: ProjectPublishActionProps) {
|
||||
title,
|
||||
summary: `${title},由 Makelore 创建的作品。`,
|
||||
cover_url: null,
|
||||
category: 'web',
|
||||
category: projectType,
|
||||
age_band: null,
|
||||
difficulty: null,
|
||||
};
|
||||
|
||||
@@ -19,8 +19,13 @@ const PUBLISH_FAILURES: Record<string, WorksPublishFailure> = {
|
||||
},
|
||||
PROJECT_FILE_MISSING: {
|
||||
title: '项目文件不完整',
|
||||
reason: '发布所需的基础文件缺失。',
|
||||
nextStep: '请让开发助手补齐 package.json、package-lock.json 和 index.html 后重试。',
|
||||
reason: '项目模板中的发布文件被删除或损坏。',
|
||||
nextStep: '请让开发助手修复当前项目模板后重试。',
|
||||
},
|
||||
PROJECT_TYPE_UNPUBLISHABLE: {
|
||||
title: '这个项目没有配置发布方式',
|
||||
reason: '自定义项目只创建工作空间,不会自动生成平台发布模板。',
|
||||
nextStep: '如需一键提交,请新建小游戏或小程序项目。',
|
||||
},
|
||||
PROJECT_FILE_INVALID: {
|
||||
title: '项目配置无法读取',
|
||||
|
||||
@@ -324,7 +324,17 @@ export function ProjectConfiguration() {
|
||||
<Button type="button" variant="outline" onClick={() => setDeleteDialogOpen(true)} className="h-12 border border-foreground/15 bg-accent-soft px-5 font-semibold text-foreground shadow-soft"><Trash2 className="mr-2 h-5 w-5" />删除项目</Button>
|
||||
<div className="ml-auto flex flex-wrap items-start justify-end gap-3">
|
||||
<Button onClick={() => void submit()} disabled={saving} className="project-save-button h-12 border border-brand/20 bg-brand px-6 font-semibold text-primary-foreground"><Check className="mr-2 h-5 w-5" />{saving ? '保存中…' : draft.initialized ? '保存项目配置' : '确认并完成初始化'}</Button>
|
||||
<ProjectPublishAction key={activeProject.id} project={activeProject} />
|
||||
{draft.projectType === 'mini_game' || draft.projectType === 'mini_program' ? (
|
||||
<ProjectPublishAction
|
||||
key={activeProject.id}
|
||||
project={activeProject}
|
||||
projectType={draft.projectType}
|
||||
/>
|
||||
) : (
|
||||
<p className="max-w-sm self-center text-right text-xs font-medium leading-5 text-muted-foreground [text-wrap:pretty]">
|
||||
自定义项目暂未配置发布方式。如需一键提交,请新建小游戏或小程序项目。
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -60,6 +60,7 @@ import {
|
||||
getOpencodeErrorKind,
|
||||
type OpencodeErrorKind,
|
||||
} from '../../shared/opencode-error-kind';
|
||||
import type { ProjectType } from '../../shared/project-config';
|
||||
|
||||
export type { OpencodeErrorKind } from '../../shared/opencode-error-kind';
|
||||
|
||||
@@ -80,6 +81,7 @@ export type ProjectDirectoryMode = 'use-selected-directory' | 'create-child-dire
|
||||
export interface ProjectCreationInput {
|
||||
directoryMode: ProjectDirectoryMode;
|
||||
selectedPath: string;
|
||||
projectType: ProjectType;
|
||||
projectName?: string;
|
||||
defaultModel?: string | null;
|
||||
}
|
||||
@@ -3956,6 +3958,7 @@ export const useOpencodeStore = create<OpencodeState>((set, get) => ({
|
||||
...(input.directoryMode === 'use-selected-directory'
|
||||
? { projectPath: input.selectedPath }
|
||||
: { parentPath: input.selectedPath, projectName: input.projectName }),
|
||||
projectType: input.projectType,
|
||||
...(input.defaultModel !== undefined ? { defaultModel: input.defaultModel } : {}),
|
||||
}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user