Compare commits
2 Commits
1bc0629827
...
c12e8df0ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c12e8df0ae | ||
|
|
3d9336d908 |
@ -406,7 +406,7 @@ pub fn seed_builtin_templates() {
|
|||||||
name: "pnpm Monorepo",
|
name: "pnpm Monorepo",
|
||||||
description: "多包 monorepo,含 apps/ 与 packages/ 目录",
|
description: "多包 monorepo,含 apps/ 与 packages/ 目录",
|
||||||
platform: "both",
|
platform: "both",
|
||||||
tech_stack: "Node,pnpm",
|
tech_stack: r#"{"客户端":["Web"],"框架/工具":["Node","pnpm"]}"#,
|
||||||
init_commands: "pnpm install",
|
init_commands: "pnpm install",
|
||||||
variants: None,
|
variants: None,
|
||||||
files: vec![
|
files: vec![
|
||||||
@ -439,7 +439,7 @@ pub fn seed_builtin_templates() {
|
|||||||
name: "pnpm App",
|
name: "pnpm App",
|
||||||
description: "单包 Node.js 应用",
|
description: "单包 Node.js 应用",
|
||||||
platform: "both",
|
platform: "both",
|
||||||
tech_stack: "Node,pnpm",
|
tech_stack: r#"{"客户端":["Web"],"框架/工具":["Node","pnpm"]}"#,
|
||||||
init_commands: "pnpm install",
|
init_commands: "pnpm install",
|
||||||
variants: None,
|
variants: None,
|
||||||
files: vec![
|
files: vec![
|
||||||
@ -463,7 +463,7 @@ pub fn seed_builtin_templates() {
|
|||||||
name: "Rust",
|
name: "Rust",
|
||||||
description: "Rust 二进制项目",
|
description: "Rust 二进制项目",
|
||||||
platform: "both",
|
platform: "both",
|
||||||
tech_stack: "Rust",
|
tech_stack: r#"{"客户端":["CLI","Desktop"],"语言":["Rust"]}"#,
|
||||||
init_commands: "cargo build",
|
init_commands: "cargo build",
|
||||||
variants: None,
|
variants: None,
|
||||||
files: vec![
|
files: vec![
|
||||||
@ -493,7 +493,7 @@ edition = "2021"
|
|||||||
name: "Tauri + React",
|
name: "Tauri + React",
|
||||||
description: "桌面应用,Tauri v2 + React 19 + TypeScript + Vite",
|
description: "桌面应用,Tauri v2 + React 19 + TypeScript + Vite",
|
||||||
platform: "both",
|
platform: "both",
|
||||||
tech_stack: "Rust,Node,pnpm,React,TypeScript,Tauri",
|
tech_stack: r#"{"客户端":["Desktop"],"语言":["TypeScript","Rust"],"框架/工具":["React","Tauri","Node","pnpm"]}"#,
|
||||||
init_commands: "pnpm install",
|
init_commands: "pnpm install",
|
||||||
variants: None,
|
variants: None,
|
||||||
files: vec![
|
files: vec![
|
||||||
@ -680,7 +680,7 @@ pub fn run() {
|
|||||||
name: "Expo",
|
name: "Expo",
|
||||||
description: "React Native 移动端 App(Android / iOS)。TypeScript + Expo SDK,适合游戏、工具、内容类手机应用。选择下方模板变体来确定初始化方式。",
|
description: "React Native 移动端 App(Android / iOS)。TypeScript + Expo SDK,适合游戏、工具、内容类手机应用。选择下方模板变体来确定初始化方式。",
|
||||||
platform: "both",
|
platform: "both",
|
||||||
tech_stack: "React Native,TypeScript,Expo",
|
tech_stack: r#"{"客户端":["Android","iOS"],"语言":["TypeScript"],"框架/工具":["React Native","Expo"]}"#,
|
||||||
init_commands: "pnpm create expo-app@latest . --template blank-typescript",
|
init_commands: "pnpm create expo-app@latest . --template blank-typescript",
|
||||||
variants: Some(r#"[
|
variants: Some(r#"[
|
||||||
{"label":"blank-typescript(推荐)","description":"最小化 TypeScript 起步,适合游戏类、工具类、独立 App","initCommands":"pnpm create expo-app@latest . --template blank-typescript"},
|
{"label":"blank-typescript(推荐)","description":"最小化 TypeScript 起步,适合游戏类、工具类、独立 App","initCommands":"pnpm create expo-app@latest . --template blank-typescript"},
|
||||||
@ -695,7 +695,7 @@ pub fn run() {
|
|||||||
name: "pnpm Monorepo + Expo",
|
name: "pnpm Monorepo + Expo",
|
||||||
description: "pnpm 多包 Monorepo:apps/ 内含 Expo Router (React Native) 应用,packages/ 内含共享 @repo/types 包。Metro 已配置 monorepo 路径解析(含 Windows 兼容修复),适合多 App 扩展场景。",
|
description: "pnpm 多包 Monorepo:apps/ 内含 Expo Router (React Native) 应用,packages/ 内含共享 @repo/types 包。Metro 已配置 monorepo 路径解析(含 Windows 兼容修复),适合多 App 扩展场景。",
|
||||||
platform: "both",
|
platform: "both",
|
||||||
tech_stack: "React Native,TypeScript,Expo,pnpm",
|
tech_stack: r#"{"客户端":["Android","iOS","Web"],"语言":["TypeScript"],"框架/工具":["React Native","Expo","pnpm"]}"#,
|
||||||
init_commands: "pnpm install",
|
init_commands: "pnpm install",
|
||||||
variants: None,
|
variants: None,
|
||||||
files: vec![
|
files: vec![
|
||||||
@ -905,7 +905,7 @@ const styles = StyleSheet.create({
|
|||||||
name: "React + Vite SPA",
|
name: "React + Vite SPA",
|
||||||
description: "纯前端 Web 应用,React 19 + TypeScript + TailwindCSS v4 + React Router v7",
|
description: "纯前端 Web 应用,React 19 + TypeScript + TailwindCSS v4 + React Router v7",
|
||||||
platform: "both",
|
platform: "both",
|
||||||
tech_stack: "React,TypeScript,Vite,TailwindCSS",
|
tech_stack: r#"{"客户端":["Web"],"语言":["TypeScript"],"框架/工具":["React","Vite","TailwindCSS"]}"#,
|
||||||
init_commands: "pnpm install",
|
init_commands: "pnpm install",
|
||||||
variants: None,
|
variants: None,
|
||||||
files: vec![
|
files: vec![
|
||||||
@ -1020,7 +1020,7 @@ export default function App() {
|
|||||||
name: "Tauri + React + Tailwind",
|
name: "Tauri + React + Tailwind",
|
||||||
description: "桌面应用,Tauri v2 + React 19 + TypeScript + TailwindCSS v4",
|
description: "桌面应用,Tauri v2 + React 19 + TypeScript + TailwindCSS v4",
|
||||||
platform: "both",
|
platform: "both",
|
||||||
tech_stack: "Rust,Node,pnpm,React,TypeScript,Tauri,TailwindCSS",
|
tech_stack: r#"{"客户端":["Desktop"],"语言":["TypeScript","Rust"],"框架/工具":["React","Tauri","TailwindCSS","Node","pnpm"]}"#,
|
||||||
init_commands: "pnpm install",
|
init_commands: "pnpm install",
|
||||||
variants: None,
|
variants: None,
|
||||||
files: vec![
|
files: vec![
|
||||||
@ -1220,7 +1220,7 @@ pub fn run() {
|
|||||||
name: "Web + Mobile Monorepo",
|
name: "Web + Mobile Monorepo",
|
||||||
description: "跨平台代码复用:packages/core 共享业务逻辑,apps/web(Vite + Tailwind)+ apps/mobile(Expo + NativeWind)",
|
description: "跨平台代码复用:packages/core 共享业务逻辑,apps/web(Vite + Tailwind)+ apps/mobile(Expo + NativeWind)",
|
||||||
platform: "both",
|
platform: "both",
|
||||||
tech_stack: "React,React Native,TypeScript,Expo,Vite,TailwindCSS,NativeWind,pnpm",
|
tech_stack: r#"{"客户端":["Android","iOS","Web"],"语言":["TypeScript"],"框架/工具":["React","React Native","Expo","Vite","TailwindCSS","NativeWind","pnpm"]}"#,
|
||||||
init_commands: "pnpm install",
|
init_commands: "pnpm install",
|
||||||
variants: None,
|
variants: None,
|
||||||
files: vec![
|
files: vec![
|
||||||
@ -1589,6 +1589,693 @@ export default function HomeScreen() {
|
|||||||
"#),
|
"#),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// ── 三端体系模板(同一架构,按需起步)────────────────────────────────────
|
||||||
|
Tpl {
|
||||||
|
id: "builtin-react-web",
|
||||||
|
name: "React Web",
|
||||||
|
description: "三端体系 Web 基础。React 18 + TypeScript + Ant Design 5 + Zustand,可随时扩展为 Capacitor Android 或 Tauri Desktop。",
|
||||||
|
platform: "both",
|
||||||
|
tech_stack: r#"{"体系":["三端"],"客户端":["Web"],"语言":["TypeScript"],"框架/工具":["React","Ant Design","Zustand","Vite","pnpm"]}"#,
|
||||||
|
init_commands: "pnpm install",
|
||||||
|
variants: None,
|
||||||
|
files: vec![
|
||||||
|
("package.json", r#"{
|
||||||
|
"name": "{{project_name_kebab}}",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"web": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"antd": "^5.21.0",
|
||||||
|
"react": "^18.3.0",
|
||||||
|
"react-dom": "^18.3.0",
|
||||||
|
"zustand": "^5.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.3.0",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.0",
|
||||||
|
"typescript": "^5.5.0",
|
||||||
|
"vite": "^5.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("vite.config.ts", r#"import { defineConfig, loadEnv } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
loadEnv(mode, process.cwd(), "");
|
||||||
|
return {
|
||||||
|
plugins: [react()],
|
||||||
|
base: mode === "capacitor" ? "./" : "/",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
"#),
|
||||||
|
("tsconfig.json", r#"{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("index.html", r#"<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>{{project_name}}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"#),
|
||||||
|
(".env", "VITE_PLATFORM=web\n"),
|
||||||
|
("src/main.tsx", r#"import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
"#),
|
||||||
|
("src/App.tsx", r#"import { Button } from "antd";
|
||||||
|
import { useAppStore } from "./store";
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const { count, increment } = useAppStore();
|
||||||
|
return (
|
||||||
|
<div style={{ padding: "2rem", fontFamily: "sans-serif" }}>
|
||||||
|
<h1>{{project_name}}</h1>
|
||||||
|
<p>React 18 + Ant Design 5 + Zustand</p>
|
||||||
|
<Button type="primary" onClick={increment}>
|
||||||
|
count: {count}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("src/store/index.ts", r#"import { create } from "zustand";
|
||||||
|
|
||||||
|
interface AppStore {
|
||||||
|
count: number;
|
||||||
|
increment: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppStore = create<AppStore>((set) => ({
|
||||||
|
count: 0,
|
||||||
|
increment: () => set((s) => ({ count: s.count + 1 })),
|
||||||
|
}));
|
||||||
|
"#),
|
||||||
|
(".gitignore", "node_modules/\ndist/\n*.log\n.env\n.env.*\n!.env.example\n"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Tpl {
|
||||||
|
id: "builtin-react-capacitor",
|
||||||
|
name: "React + Capacitor",
|
||||||
|
description: "Web + Android。React 18 + Ant Design 5 + Zustand + Capacitor 8,pnpm build:cap 打包为原生 APK。",
|
||||||
|
platform: "both",
|
||||||
|
tech_stack: r#"{"体系":["三端"],"客户端":["Web","Android"],"语言":["TypeScript"],"框架/工具":["React","Ant Design","Zustand","Capacitor","Vite","pnpm"]}"#,
|
||||||
|
init_commands: "pnpm install",
|
||||||
|
variants: None,
|
||||||
|
files: vec![
|
||||||
|
("package.json", r#"{
|
||||||
|
"name": "{{project_name_kebab}}",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"web": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"build:cap": "vite build --mode capacitor && npx cap sync",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@capacitor/core": "^8.0.0",
|
||||||
|
"antd": "^5.21.0",
|
||||||
|
"react": "^18.3.0",
|
||||||
|
"react-dom": "^18.3.0",
|
||||||
|
"zustand": "^5.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@capacitor/android": "^8.0.0",
|
||||||
|
"@capacitor/cli": "^8.0.0",
|
||||||
|
"@types/react": "^18.3.0",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.0",
|
||||||
|
"typescript": "^5.5.0",
|
||||||
|
"vite": "^5.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("vite.config.ts", r#"import { defineConfig, loadEnv } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
loadEnv(mode, process.cwd(), "");
|
||||||
|
return {
|
||||||
|
plugins: [react()],
|
||||||
|
base: mode === "capacitor" ? "./" : "/",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
"#),
|
||||||
|
("tsconfig.json", r#"{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"include": ["src", "capacitor.config.ts"]
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("index.html", r#"<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>{{project_name}}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"#),
|
||||||
|
(".env", "VITE_PLATFORM=web\n"),
|
||||||
|
(".env.capacitor", "VITE_PLATFORM=android\n"),
|
||||||
|
("capacitor.config.ts", r#"import type { CapacitorConfig } from "@capacitor/cli";
|
||||||
|
|
||||||
|
const config: CapacitorConfig = {
|
||||||
|
appId: "com.example.{{project_name_snake}}",
|
||||||
|
appName: "{{project_name}}",
|
||||||
|
webDir: "dist",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
"#),
|
||||||
|
("src/main.tsx", r#"import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
"#),
|
||||||
|
("src/App.tsx", r#"import { Button } from "antd";
|
||||||
|
import { useAppStore } from "./store";
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const { count, increment } = useAppStore();
|
||||||
|
return (
|
||||||
|
<div style={{ padding: "2rem", fontFamily: "sans-serif" }}>
|
||||||
|
<h1>{{project_name}}</h1>
|
||||||
|
<p>React 18 + Ant Design 5 + Zustand + Capacitor</p>
|
||||||
|
<Button type="primary" onClick={increment}>
|
||||||
|
count: {count}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("src/store/index.ts", r#"import { create } from "zustand";
|
||||||
|
|
||||||
|
interface AppStore {
|
||||||
|
count: number;
|
||||||
|
increment: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppStore = create<AppStore>((set) => ({
|
||||||
|
count: 0,
|
||||||
|
increment: () => set((s) => ({ count: s.count + 1 })),
|
||||||
|
}));
|
||||||
|
"#),
|
||||||
|
(".gitignore", "node_modules/\ndist/\n*.log\n.env\n.env.*\n!.env.example\nandroid/\n"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Tpl {
|
||||||
|
id: "builtin-react-tauri-antd",
|
||||||
|
name: "React + Tauri",
|
||||||
|
description: "Web + Desktop。React 18 + Ant Design 5 + Zustand + Tauri 2,pnpm build:desktop 打包为原生桌面应用。",
|
||||||
|
platform: "both",
|
||||||
|
tech_stack: r#"{"体系":["三端"],"客户端":["Web","Desktop"],"语言":["TypeScript","Rust"],"框架/工具":["React","Ant Design","Zustand","Tauri","Vite","pnpm"]}"#,
|
||||||
|
init_commands: "pnpm install",
|
||||||
|
variants: None,
|
||||||
|
files: vec![
|
||||||
|
("package.json", r#"{
|
||||||
|
"name": "{{project_name_kebab}}",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"web": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"build:desktop": "tauri build",
|
||||||
|
"dev:desktop": "tauri dev",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^2",
|
||||||
|
"antd": "^5.21.0",
|
||||||
|
"react": "^18.3.0",
|
||||||
|
"react-dom": "^18.3.0",
|
||||||
|
"zustand": "^5.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2",
|
||||||
|
"@types/react": "^18.3.0",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.0",
|
||||||
|
"typescript": "^5.5.0",
|
||||||
|
"vite": "^5.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("vite.config.ts", r#"import { defineConfig, loadEnv } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
loadEnv(mode, process.cwd(), "");
|
||||||
|
return {
|
||||||
|
plugins: [react()],
|
||||||
|
base: mode === "capacitor" ? "./" : "/",
|
||||||
|
clearScreen: false,
|
||||||
|
server: {
|
||||||
|
port: 1420,
|
||||||
|
strictPort: true,
|
||||||
|
watch: {
|
||||||
|
ignored: ["**/src-tauri/**"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
"#),
|
||||||
|
("tsconfig.json", r#"{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("index.html", r#"<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>{{project_name}}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"#),
|
||||||
|
(".env", "VITE_PLATFORM=web\n"),
|
||||||
|
("src/main.tsx", r#"import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
"#),
|
||||||
|
("src/App.tsx", r#"import { Button } from "antd";
|
||||||
|
import { useAppStore } from "./store";
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const { count, increment } = useAppStore();
|
||||||
|
return (
|
||||||
|
<div style={{ padding: "2rem", fontFamily: "sans-serif" }}>
|
||||||
|
<h1>{{project_name}}</h1>
|
||||||
|
<p>React 18 + Ant Design 5 + Zustand + Tauri 2</p>
|
||||||
|
<Button type="primary" onClick={increment}>
|
||||||
|
count: {count}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("src/store/index.ts", r#"import { create } from "zustand";
|
||||||
|
|
||||||
|
interface AppStore {
|
||||||
|
count: number;
|
||||||
|
increment: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppStore = create<AppStore>((set) => ({
|
||||||
|
count: 0,
|
||||||
|
increment: () => set((s) => ({ count: s.count + 1 })),
|
||||||
|
}));
|
||||||
|
"#),
|
||||||
|
("src-tauri/tauri.conf.json", r#"{
|
||||||
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
|
"productName": "{{project_name_kebab}}",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"identifier": "com.example.{{project_name_snake}}",
|
||||||
|
"build": {
|
||||||
|
"beforeDevCommand": "pnpm web",
|
||||||
|
"devUrl": "http://localhost:1420",
|
||||||
|
"beforeBuildCommand": "pnpm build",
|
||||||
|
"frontendDist": "../dist"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"title": "{{project_name}}",
|
||||||
|
"width": 1200,
|
||||||
|
"height": 800
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": {
|
||||||
|
"csp": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"targets": "all",
|
||||||
|
"icon": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("src-tauri/Cargo.toml", r#"[package]
|
||||||
|
name = "{{project_name_snake}}"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "A Tauri App"
|
||||||
|
authors = []
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "{{project_name_snake}}_lib"
|
||||||
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tauri = { version = "2", features = [] }
|
||||||
|
tauri-plugin-opener = "2"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
"#),
|
||||||
|
("src-tauri/build.rs", "fn main() {\n tauri_build::build()\n}\n"),
|
||||||
|
("src-tauri/src/main.rs", r#"// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
{{project_name_snake}}_lib::run()
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("src-tauri/src/lib.rs", r#"#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
pub fn run() {
|
||||||
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_opener::init())
|
||||||
|
.invoke_handler(tauri::generate_handler![])
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("src-tauri/capabilities/default.json", r#"{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
|
"identifier": "default",
|
||||||
|
"description": "Capability for the main window",
|
||||||
|
"windows": ["main"],
|
||||||
|
"permissions": [
|
||||||
|
"core:default",
|
||||||
|
"opener:default"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
(".gitignore", "node_modules/\ndist/\n*.log\n.env\n.env.*\n!.env.example\nsrc-tauri/target/\nsrc-tauri/gen/\n"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Tpl {
|
||||||
|
id: "builtin-react-triplatform",
|
||||||
|
name: "React Tri-Platform",
|
||||||
|
description: "三端合一。同一套 React 18 + Ant Design 5 + Zustand 代码,pnpm web / build:cap / build:desktop 分别构建 Web / Android / Desktop。",
|
||||||
|
platform: "both",
|
||||||
|
tech_stack: r#"{"体系":["三端"],"客户端":["Web","Android","Desktop"],"语言":["TypeScript","Rust"],"框架/工具":["React","Ant Design","Zustand","Capacitor","Tauri","Vite","pnpm"]}"#,
|
||||||
|
init_commands: "pnpm install",
|
||||||
|
variants: None,
|
||||||
|
files: vec![
|
||||||
|
("package.json", r#"{
|
||||||
|
"name": "{{project_name_kebab}}",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"web": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"build:cap": "vite build --mode capacitor && npx cap sync",
|
||||||
|
"build:desktop": "tauri build",
|
||||||
|
"dev:desktop": "tauri dev",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@capacitor/core": "^8.0.0",
|
||||||
|
"@tauri-apps/api": "^2",
|
||||||
|
"antd": "^5.21.0",
|
||||||
|
"react": "^18.3.0",
|
||||||
|
"react-dom": "^18.3.0",
|
||||||
|
"zustand": "^5.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@capacitor/android": "^8.0.0",
|
||||||
|
"@capacitor/cli": "^8.0.0",
|
||||||
|
"@tauri-apps/cli": "^2",
|
||||||
|
"@types/react": "^18.3.0",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.0",
|
||||||
|
"typescript": "^5.5.0",
|
||||||
|
"vite": "^5.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("vite.config.ts", r#"import { defineConfig, loadEnv } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
loadEnv(mode, process.cwd(), "");
|
||||||
|
return {
|
||||||
|
plugins: [react()],
|
||||||
|
base: mode === "capacitor" ? "./" : "/",
|
||||||
|
clearScreen: false,
|
||||||
|
server: {
|
||||||
|
port: 1420,
|
||||||
|
strictPort: true,
|
||||||
|
watch: {
|
||||||
|
ignored: ["**/src-tauri/**"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
"#),
|
||||||
|
("tsconfig.json", r#"{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"include": ["src", "capacitor.config.ts"]
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("index.html", r#"<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>{{project_name}}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"#),
|
||||||
|
(".env", "VITE_PLATFORM=web\n"),
|
||||||
|
(".env.capacitor", "VITE_PLATFORM=android\n"),
|
||||||
|
("capacitor.config.ts", r#"import type { CapacitorConfig } from "@capacitor/cli";
|
||||||
|
|
||||||
|
const config: CapacitorConfig = {
|
||||||
|
appId: "com.example.{{project_name_snake}}",
|
||||||
|
appName: "{{project_name}}",
|
||||||
|
webDir: "dist",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
"#),
|
||||||
|
("src/main.tsx", r#"import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
"#),
|
||||||
|
("src/App.tsx", r#"import { Button } from "antd";
|
||||||
|
import { useAppStore } from "./store";
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const { count, increment } = useAppStore();
|
||||||
|
return (
|
||||||
|
<div style={{ padding: "2rem", fontFamily: "sans-serif" }}>
|
||||||
|
<h1>{{project_name}}</h1>
|
||||||
|
<p>React 18 + Ant Design 5 + Zustand · Web / Android / Desktop</p>
|
||||||
|
<Button type="primary" onClick={increment}>
|
||||||
|
count: {count}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("src/store/index.ts", r#"import { create } from "zustand";
|
||||||
|
|
||||||
|
interface AppStore {
|
||||||
|
count: number;
|
||||||
|
increment: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppStore = create<AppStore>((set) => ({
|
||||||
|
count: 0,
|
||||||
|
increment: () => set((s) => ({ count: s.count + 1 })),
|
||||||
|
}));
|
||||||
|
"#),
|
||||||
|
("src-tauri/tauri.conf.json", r#"{
|
||||||
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
|
"productName": "{{project_name_kebab}}",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"identifier": "com.example.{{project_name_snake}}",
|
||||||
|
"build": {
|
||||||
|
"beforeDevCommand": "pnpm web",
|
||||||
|
"devUrl": "http://localhost:1420",
|
||||||
|
"beforeBuildCommand": "pnpm build",
|
||||||
|
"frontendDist": "../dist"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"title": "{{project_name}}",
|
||||||
|
"width": 1200,
|
||||||
|
"height": 800
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": {
|
||||||
|
"csp": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"targets": "all",
|
||||||
|
"icon": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("src-tauri/Cargo.toml", r#"[package]
|
||||||
|
name = "{{project_name_snake}}"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "A Tauri App"
|
||||||
|
authors = []
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "{{project_name_snake}}_lib"
|
||||||
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tauri = { version = "2", features = [] }
|
||||||
|
tauri-plugin-opener = "2"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
"#),
|
||||||
|
("src-tauri/build.rs", "fn main() {\n tauri_build::build()\n}\n"),
|
||||||
|
("src-tauri/src/main.rs", r#"// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
{{project_name_snake}}_lib::run()
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("src-tauri/src/lib.rs", r#"#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
pub fn run() {
|
||||||
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_opener::init())
|
||||||
|
.invoke_handler(tauri::generate_handler![])
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
("src-tauri/capabilities/default.json", r#"{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
|
"identifier": "default",
|
||||||
|
"description": "Capability for the main window",
|
||||||
|
"windows": ["main"],
|
||||||
|
"permissions": [
|
||||||
|
"core:default",
|
||||||
|
"opener:default"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"#),
|
||||||
|
(".gitignore", "node_modules/\ndist/\n*.log\n.env\n.env.*\n!.env.example\nandroid/\nsrc-tauri/target/\nsrc-tauri/gen/\n"),
|
||||||
|
],
|
||||||
|
},
|
||||||
// ── 知识库(择升环)────────────────────────────────────────────────────
|
// ── 知识库(择升环)────────────────────────────────────────────────────
|
||||||
Tpl {
|
Tpl {
|
||||||
id: "builtin-knowledge-loop",
|
id: "builtin-knowledge-loop",
|
||||||
@ -1638,7 +2325,18 @@ cases/ 是检验层:概念卡晋升 CORE 的依据来自真实案例。
|
|||||||
1. 先读 `CORE.md` 与本次话题相关的 `concepts/` 卡——上一轮的结论是这一轮的输入
|
1. 先读 `CORE.md` 与本次话题相关的 `concepts/` 卡——上一轮的结论是这一轮的输入
|
||||||
2. 探讨结束后,把整理稿写入 `dialogues/YYYY-MM-DD-主题.md`
|
2. 探讨结束后,把整理稿写入 `dialogues/YYYY-MM-DD-主题.md`
|
||||||
3. 对照晋升规则检查是否触发晋升 / 降级,触发则执行
|
3. 对照晋升规则检查是否触发晋升 / 降级,触发则执行
|
||||||
4. git commit 收尾——本库以 git 历史作为演化台账
|
4. git commit 收尾——本库以 git 历史作为演化台账,scope 标注层级(见下)
|
||||||
|
|
||||||
|
## Commit 层级信号
|
||||||
|
|
||||||
|
统一格式 `docs(<层级>): <摘要>`,让外部观测者(如炼境飞轮)凭 commit 流
|
||||||
|
即可读出晋升频率与环的健康度——dialogue 多而 concept 少 = 只积累不过滤;
|
||||||
|
concept 多而 core 不动 = 过滤了但不收敛。
|
||||||
|
|
||||||
|
- `docs(dialogue): YYYY-MM-DD-主题` — 新对话沉淀
|
||||||
|
- `docs(concept): <概念名> 立卡|修订|废弃` — 概念卡变动
|
||||||
|
- `docs(core): <结论>入核,挤掉/合并<旧条目>` — 核心层变动
|
||||||
|
- `docs(case): <事件>复盘` — 案例复盘
|
||||||
|
|
||||||
## 晋升规则(择)
|
## 晋升规则(择)
|
||||||
|
|
||||||
@ -1709,9 +2407,15 @@ cases/ 是检验层:概念卡晋升 CORE 的依据来自真实案例。
|
|||||||
|
|
||||||
for t in &templates {
|
for t in &templates {
|
||||||
let _ = conn.execute(
|
let _ = conn.execute(
|
||||||
"INSERT OR IGNORE INTO project_templates
|
"INSERT INTO project_templates
|
||||||
(id, name, description, platform, tech_stack, init_commands, is_builtin, variants)
|
(id, name, description, platform, tech_stack, init_commands, is_builtin, variants)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, 1, ?7)",
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, 1, ?7)
|
||||||
|
ON CONFLICT(id) DO UPDATE SET
|
||||||
|
name = excluded.name,
|
||||||
|
description = excluded.description,
|
||||||
|
tech_stack = excluded.tech_stack,
|
||||||
|
init_commands = excluded.init_commands,
|
||||||
|
variants = excluded.variants",
|
||||||
params![
|
params![
|
||||||
t.id,
|
t.id,
|
||||||
t.name,
|
t.name,
|
||||||
@ -1722,16 +2426,11 @@ cases/ 是检验层:概念卡晋升 CORE 的依据来自真实案例。
|
|||||||
t.variants,
|
t.variants,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
// 已存在的模板也补充 variants(INSERT OR IGNORE 不更新已有行)
|
|
||||||
let _ = conn.execute(
|
|
||||||
"UPDATE project_templates SET variants = ?1 WHERE id = ?2 AND is_builtin = 1",
|
|
||||||
params![t.variants, t.id],
|
|
||||||
);
|
|
||||||
|
|
||||||
for (path, content) in &t.files {
|
for (path, content) in &t.files {
|
||||||
let file_id = format!("{}-{}", t.id, path.replace('/', "-"));
|
let file_id = format!("{}-{}", t.id, path.replace('/', "-"));
|
||||||
let _ = conn.execute(
|
let _ = conn.execute(
|
||||||
"INSERT OR IGNORE INTO project_template_files (id, template_id, path, content)
|
"INSERT OR REPLACE INTO project_template_files (id, template_id, path, content)
|
||||||
VALUES (?1, ?2, ?3, ?4)",
|
VALUES (?1, ?2, ?3, ?4)",
|
||||||
params![file_id, t.id, path, content],
|
params![file_id, t.id, path, content],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { PriorityBadge, StatusBadge } from "../ui/Badge";
|
|||||||
import { Dialog } from "../ui/Dialog";
|
import { Dialog } from "../ui/Dialog";
|
||||||
import { TemplateEditor } from "./TemplateEditor";
|
import { TemplateEditor } from "./TemplateEditor";
|
||||||
import { NewProjectModal } from "./NewProjectModal";
|
import { NewProjectModal } from "./NewProjectModal";
|
||||||
|
import { renderTechStack } from "./techStackUtils";
|
||||||
import { TagsManager } from "./TagsManager";
|
import { TagsManager } from "./TagsManager";
|
||||||
import { RegistryTab } from "../dashboard/RepoRegistryModal";
|
import { RegistryTab } from "../dashboard/RepoRegistryModal";
|
||||||
|
|
||||||
@ -849,15 +850,7 @@ function TemplatesTab({ spaceId: _spaceId }: { spaceId?: string }) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{t.techStack && (
|
{t.techStack && renderTechStack(t.techStack)}
|
||||||
<div className="flex flex-wrap gap-1">
|
|
||||||
{t.techStack.split(",").map((tag) => (
|
|
||||||
<span key={tag} className="text-[10px] px-1.5 py-0.5 rounded-full bg-indigo-50 text-indigo-500">
|
|
||||||
{tag.trim()}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{t.initCommands && (
|
{t.initCommands && (
|
||||||
<code className="text-[11px] text-gray-400 font-mono">
|
<code className="text-[11px] text-gray-400 font-mono">
|
||||||
$ {t.initCommands.split("\n")[0]}
|
$ {t.initCommands.split("\n")[0]}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
listTemplates, createProjectFromTemplate, TemplateVariant,
|
listTemplates, createProjectFromTemplate, TemplateVariant,
|
||||||
} from "../../lib/commands";
|
} from "../../lib/commands";
|
||||||
import { useUIStore } from "../../store/ui";
|
import { useUIStore } from "../../store/ui";
|
||||||
|
import { renderTechStack } from "./techStackUtils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
spaceId?: string;
|
spaceId?: string;
|
||||||
@ -286,15 +287,7 @@ export function NewProjectModal({ spaceId, onClose }: Props) {
|
|||||||
{t.description && (
|
{t.description && (
|
||||||
<p className="text-xs text-gray-400 mt-0.5">{t.description}</p>
|
<p className="text-xs text-gray-400 mt-0.5">{t.description}</p>
|
||||||
)}
|
)}
|
||||||
{t.techStack && (
|
{t.techStack && <div className="mt-1.5">{renderTechStack(t.techStack)}</div>}
|
||||||
<div className="flex flex-wrap gap-1 mt-1.5">
|
|
||||||
{t.techStack.split(",").map((tag) => (
|
|
||||||
<span key={tag} className="text-[10px] px-1.5 py-0.5 rounded-full bg-indigo-50 text-indigo-500">
|
|
||||||
{tag.trim()}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{variants.length === 0 && t.initCommands && (
|
{variants.length === 0 && t.initCommands && (
|
||||||
<p className="text-[11px] text-gray-300 mt-1 font-mono">$ {t.initCommands.split("\n")[0]}</p>
|
<p className="text-[11px] text-gray-300 mt-1 font-mono">$ {t.initCommands.split("\n")[0]}</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -149,13 +149,13 @@ export function TemplateEditor({ templateId, onClose }: Props) {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-500 mb-1">技术栈标签(逗号分隔)</label>
|
<label className="block text-xs font-medium text-gray-500 mb-1">技术栈标签</label>
|
||||||
<input
|
<input
|
||||||
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-blue-200"
|
className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-blue-200"
|
||||||
value={techStack}
|
value={techStack}
|
||||||
onChange={(e) => setTechStack(e.target.value)}
|
onChange={(e) => setTechStack(e.target.value)}
|
||||||
disabled={isBuiltin}
|
disabled={isBuiltin}
|
||||||
placeholder="Node,pnpm,TypeScript"
|
placeholder='{"客户端":["Web"],"语言":["TypeScript"],"框架/工具":["React","Vite"]}'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
|
|||||||
41
src/components/manage/techStackUtils.tsx
Normal file
41
src/components/manage/techStackUtils.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
const TECH_STACK_COLORS: Record<string, string> = {
|
||||||
|
"体系": "bg-orange-50 text-orange-500",
|
||||||
|
"客户端": "bg-green-50 text-green-600",
|
||||||
|
"语言": "bg-blue-50 text-blue-600",
|
||||||
|
"框架/工具": "bg-indigo-50 text-indigo-500",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function renderTechStack(techStack: string) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(techStack) as Record<string, string[]>;
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
{Object.entries(parsed).map(([cat, tags]) =>
|
||||||
|
tags.length > 0 ? (
|
||||||
|
<div key={cat} className="flex flex-wrap items-center gap-1">
|
||||||
|
<span className="text-[9px] text-gray-400 mr-0.5">{cat}</span>
|
||||||
|
{tags.map((tag) => (
|
||||||
|
<span
|
||||||
|
key={tag}
|
||||||
|
className={`text-[10px] px-1.5 py-0.5 rounded-full ${TECH_STACK_COLORS[cat] ?? "bg-indigo-50 text-indigo-500"}`}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{techStack.split(",").map((tag) => (
|
||||||
|
<span key={tag} className="text-[10px] px-1.5 py-0.5 rounded-full bg-indigo-50 text-indigo-500">
|
||||||
|
{tag.trim()}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -614,6 +614,7 @@ export interface ProjectTemplate {
|
|||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
platform: string; // "win" | "wsl" | "both"
|
platform: string; // "win" | "wsl" | "both"
|
||||||
|
/** 逗号字符串(旧格式)或分类 JSON `{"客户端":[],"语言":[],"框架/工具":[]}` */
|
||||||
techStack?: string;
|
techStack?: string;
|
||||||
initCommands?: string;
|
initCommands?: string;
|
||||||
isBuiltin: boolean;
|
isBuiltin: boolean;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user