diff --git a/src-tauri/src/commands/templates.rs b/src-tauri/src/commands/templates.rs
index 1616adf..2678a3f 100644
--- a/src-tauri/src/commands/templates.rs
+++ b/src-tauri/src/commands/templates.rs
@@ -406,7 +406,7 @@ pub fn seed_builtin_templates() {
name: "pnpm Monorepo",
description: "多包 monorepo,含 apps/ 与 packages/ 目录",
platform: "both",
- tech_stack: "Node,pnpm",
+ tech_stack: r#"{"客户端":["Web"],"框架/工具":["Node","pnpm"]}"#,
init_commands: "pnpm install",
variants: None,
files: vec![
@@ -439,7 +439,7 @@ pub fn seed_builtin_templates() {
name: "pnpm App",
description: "单包 Node.js 应用",
platform: "both",
- tech_stack: "Node,pnpm",
+ tech_stack: r#"{"客户端":["Web"],"框架/工具":["Node","pnpm"]}"#,
init_commands: "pnpm install",
variants: None,
files: vec![
@@ -463,7 +463,7 @@ pub fn seed_builtin_templates() {
name: "Rust",
description: "Rust 二进制项目",
platform: "both",
- tech_stack: "Rust",
+ tech_stack: r#"{"客户端":["CLI","Desktop"],"语言":["Rust"]}"#,
init_commands: "cargo build",
variants: None,
files: vec![
@@ -493,7 +493,7 @@ edition = "2021"
name: "Tauri + React",
description: "桌面应用,Tauri v2 + React 19 + TypeScript + Vite",
platform: "both",
- tech_stack: "Rust,Node,pnpm,React,TypeScript,Tauri",
+ tech_stack: r#"{"客户端":["Desktop"],"语言":["TypeScript","Rust"],"框架/工具":["React","Tauri","Node","pnpm"]}"#,
init_commands: "pnpm install",
variants: None,
files: vec![
@@ -680,7 +680,7 @@ pub fn run() {
name: "Expo",
description: "React Native 移动端 App(Android / iOS)。TypeScript + Expo SDK,适合游戏、工具、内容类手机应用。选择下方模板变体来确定初始化方式。",
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",
variants: Some(r#"[
{"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",
description: "pnpm 多包 Monorepo:apps/ 内含 Expo Router (React Native) 应用,packages/ 内含共享 @repo/types 包。Metro 已配置 monorepo 路径解析(含 Windows 兼容修复),适合多 App 扩展场景。",
platform: "both",
- tech_stack: "React Native,TypeScript,Expo,pnpm",
+ tech_stack: r#"{"客户端":["Android","iOS","Web"],"语言":["TypeScript"],"框架/工具":["React Native","Expo","pnpm"]}"#,
init_commands: "pnpm install",
variants: None,
files: vec![
@@ -905,7 +905,7 @@ const styles = StyleSheet.create({
name: "React + Vite SPA",
description: "纯前端 Web 应用,React 19 + TypeScript + TailwindCSS v4 + React Router v7",
platform: "both",
- tech_stack: "React,TypeScript,Vite,TailwindCSS",
+ tech_stack: r#"{"客户端":["Web"],"语言":["TypeScript"],"框架/工具":["React","Vite","TailwindCSS"]}"#,
init_commands: "pnpm install",
variants: None,
files: vec![
@@ -1020,7 +1020,7 @@ export default function App() {
name: "Tauri + React + Tailwind",
description: "桌面应用,Tauri v2 + React 19 + TypeScript + TailwindCSS v4",
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",
variants: None,
files: vec![
@@ -1220,7 +1220,7 @@ pub fn run() {
name: "Web + Mobile Monorepo",
description: "跨平台代码复用:packages/core 共享业务逻辑,apps/web(Vite + Tailwind)+ apps/mobile(Expo + NativeWind)",
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",
variants: None,
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#"
+
+
+
+
+ {{project_name}}
+
+
+
+
+
+
+"#),
+ (".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(
+
+
+
+);
+"#),
+ ("src/App.tsx", r#"import { Button } from "antd";
+import { useAppStore } from "./store";
+
+export default function App() {
+ const { count, increment } = useAppStore();
+ return (
+
+
{{project_name}}
+
React 18 + Ant Design 5 + Zustand
+
+
+ );
+}
+"#),
+ ("src/store/index.ts", r#"import { create } from "zustand";
+
+interface AppStore {
+ count: number;
+ increment: () => void;
+}
+
+export const useAppStore = create((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#"
+
+
+
+
+ {{project_name}}
+
+
+
+
+
+
+"#),
+ (".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(
+
+
+
+);
+"#),
+ ("src/App.tsx", r#"import { Button } from "antd";
+import { useAppStore } from "./store";
+
+export default function App() {
+ const { count, increment } = useAppStore();
+ return (
+
+
{{project_name}}
+
React 18 + Ant Design 5 + Zustand + Capacitor
+
+
+ );
+}
+"#),
+ ("src/store/index.ts", r#"import { create } from "zustand";
+
+interface AppStore {
+ count: number;
+ increment: () => void;
+}
+
+export const useAppStore = create((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#"
+
+
+
+
+ {{project_name}}
+
+
+
+
+
+
+"#),
+ (".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(
+
+
+
+);
+"#),
+ ("src/App.tsx", r#"import { Button } from "antd";
+import { useAppStore } from "./store";
+
+export default function App() {
+ const { count, increment } = useAppStore();
+ return (
+
+
{{project_name}}
+
React 18 + Ant Design 5 + Zustand + Tauri 2
+
+
+ );
+}
+"#),
+ ("src/store/index.ts", r#"import { create } from "zustand";
+
+interface AppStore {
+ count: number;
+ increment: () => void;
+}
+
+export const useAppStore = create((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#"
+
+
+
+
+ {{project_name}}
+
+
+
+
+
+
+"#),
+ (".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(
+
+
+
+);
+"#),
+ ("src/App.tsx", r#"import { Button } from "antd";
+import { useAppStore } from "./store";
+
+export default function App() {
+ const { count, increment } = useAppStore();
+ return (
+
+
{{project_name}}
+
React 18 + Ant Design 5 + Zustand · Web / Android / Desktop
+
+
+ );
+}
+"#),
+ ("src/store/index.ts", r#"import { create } from "zustand";
+
+interface AppStore {
+ count: number;
+ increment: () => void;
+}
+
+export const useAppStore = create((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 {
id: "builtin-knowledge-loop",
@@ -1709,9 +2396,15 @@ cases/ 是检验层:概念卡晋升 CORE 的依据来自真实案例。
for t in &templates {
let _ = conn.execute(
- "INSERT OR IGNORE INTO project_templates
+ "INSERT INTO project_templates
(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![
t.id,
t.name,
@@ -1722,16 +2415,11 @@ cases/ 是检验层:概念卡晋升 CORE 的依据来自真实案例。
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 {
let file_id = format!("{}-{}", t.id, path.replace('/', "-"));
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)",
params![file_id, t.id, path, content],
);
diff --git a/src/components/manage/ManagePage.tsx b/src/components/manage/ManagePage.tsx
index e11de66..5911868 100644
--- a/src/components/manage/ManagePage.tsx
+++ b/src/components/manage/ManagePage.tsx
@@ -12,6 +12,7 @@ import { PriorityBadge, StatusBadge } from "../ui/Badge";
import { Dialog } from "../ui/Dialog";
import { TemplateEditor } from "./TemplateEditor";
import { NewProjectModal } from "./NewProjectModal";
+import { renderTechStack } from "./techStackUtils";
import { TagsManager } from "./TagsManager";
import { RegistryTab } from "../dashboard/RepoRegistryModal";
@@ -849,15 +850,7 @@ function TemplatesTab({ spaceId: _spaceId }: { spaceId?: string }) {
)}
- {t.techStack && (
-
- {t.techStack.split(",").map((tag) => (
-
- {tag.trim()}
-
- ))}
-
- )}
+ {t.techStack && renderTechStack(t.techStack)}
{t.initCommands && (
$ {t.initCommands.split("\n")[0]}
diff --git a/src/components/manage/NewProjectModal.tsx b/src/components/manage/NewProjectModal.tsx
index 95e012e..a693669 100644
--- a/src/components/manage/NewProjectModal.tsx
+++ b/src/components/manage/NewProjectModal.tsx
@@ -5,6 +5,7 @@ import {
listTemplates, createProjectFromTemplate, TemplateVariant,
} from "../../lib/commands";
import { useUIStore } from "../../store/ui";
+import { renderTechStack } from "./techStackUtils";
interface Props {
spaceId?: string;
@@ -286,15 +287,7 @@ export function NewProjectModal({ spaceId, onClose }: Props) {
{t.description && (
{t.description}
)}
- {t.techStack && (
-
- {t.techStack.split(",").map((tag) => (
-
- {tag.trim()}
-
- ))}
-
- )}
+ {t.techStack && {renderTechStack(t.techStack)}
}
{variants.length === 0 && t.initCommands && (
$ {t.initCommands.split("\n")[0]}
)}
diff --git a/src/components/manage/TemplateEditor.tsx b/src/components/manage/TemplateEditor.tsx
index 9e06f74..f98162a 100644
--- a/src/components/manage/TemplateEditor.tsx
+++ b/src/components/manage/TemplateEditor.tsx
@@ -149,13 +149,13 @@ export function TemplateEditor({ templateId, onClose }: Props) {
-
+
setTechStack(e.target.value)}
disabled={isBuiltin}
- placeholder="Node,pnpm,TypeScript"
+ placeholder='{"客户端":["Web"],"语言":["TypeScript"],"框架/工具":["React","Vite"]}'
/>
diff --git a/src/components/manage/techStackUtils.tsx b/src/components/manage/techStackUtils.tsx
new file mode 100644
index 0000000..0ae2597
--- /dev/null
+++ b/src/components/manage/techStackUtils.tsx
@@ -0,0 +1,41 @@
+const TECH_STACK_COLORS: Record
= {
+ "体系": "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;
+ return (
+
+ {Object.entries(parsed).map(([cat, tags]) =>
+ tags.length > 0 ? (
+
+ {cat}
+ {tags.map((tag) => (
+
+ {tag}
+
+ ))}
+
+ ) : null
+ )}
+
+ );
+ } catch {
+ return (
+
+ {techStack.split(",").map((tag) => (
+
+ {tag.trim()}
+
+ ))}
+
+ );
+ }
+}
diff --git a/src/lib/commands.ts b/src/lib/commands.ts
index c1d48c1..18263b3 100644
--- a/src/lib/commands.ts
+++ b/src/lib/commands.ts
@@ -614,6 +614,7 @@ export interface ProjectTemplate {
name: string;
description?: string;
platform: string; // "win" | "wsl" | "both"
+ /** 逗号字符串(旧格式)或分类 JSON `{"客户端":[],"语言":[],"框架/工具":[]}` */
techStack?: string;
initCommands?: string;
isBuiltin: boolean;