@liu_jimmy/create-fe-kit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +92 -0
- package/package.json +30 -0
- package/templates/react/.cursor/rules/fe-kit-style.mdc +23 -0
- package/templates/react/AGENTS.md +3 -0
- package/templates/react/eslint.config.js +3 -0
- package/templates/react/index.html +12 -0
- package/templates/react/package.json +32 -0
- package/templates/react/postcss.config.js +6 -0
- package/templates/react/src/App.tsx +69 -0
- package/templates/react/src/main.tsx +11 -0
- package/templates/react/src/style.css +3 -0
- package/templates/react/src/vite-env.d.ts +6 -0
- package/templates/react/tailwind.config.js +7 -0
- package/templates/react/tsconfig.json +21 -0
- package/templates/react/tsconfig.node.json +11 -0
- package/templates/react/vite.config.ts +6 -0
- package/templates/svelte/.cursor/rules/fe-kit-style.mdc +8 -0
- package/templates/svelte/AGENTS.md +3 -0
- package/templates/svelte/eslint.config.js +3 -0
- package/templates/svelte/index.html +12 -0
- package/templates/svelte/package.json +28 -0
- package/templates/svelte/postcss.config.js +6 -0
- package/templates/svelte/src/App.svelte +56 -0
- package/templates/svelte/src/main.ts +7 -0
- package/templates/svelte/src/style.css +3 -0
- package/templates/svelte/src/vite-env.d.ts +7 -0
- package/templates/svelte/svelte.config.js +5 -0
- package/templates/svelte/tailwind.config.js +7 -0
- package/templates/svelte/tsconfig.json +19 -0
- package/templates/svelte/tsconfig.node.json +11 -0
- package/templates/svelte/vite.config.ts +6 -0
- package/templates/vue/.cursor/rules/fe-kit-style.mdc +51 -0
- package/templates/vue/AGENTS.md +22 -0
- package/templates/vue/eslint.config.js +3 -0
- package/templates/vue/index.html +12 -0
- package/templates/vue/package.json +29 -0
- package/templates/vue/postcss.config.js +6 -0
- package/templates/vue/src/App.vue +59 -0
- package/templates/vue/src/main.ts +6 -0
- package/templates/vue/src/style.css +3 -0
- package/templates/vue/src/vite-env.d.ts +6 -0
- package/templates/vue/tailwind.config.js +7 -0
- package/templates/vue/tsconfig.json +22 -0
- package/templates/vue/tsconfig.node.json +11 -0
- package/templates/vue/vite.config.ts +6 -0
package/index.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* create-fe-kit
|
|
5
|
+
* Usage: pnpm create @liu_jimmy/fe-kit or npx @liu_jimmy/create-fe-kit
|
|
6
|
+
* Prompts for project name + framework (Vue / React / Svelte), then copies template.
|
|
7
|
+
*/
|
|
8
|
+
import { createInterface } from "readline";
|
|
9
|
+
import { createWriteStream, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
10
|
+
import { dirname, join } from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const TEMPLATES_DIR = join(__dirname, "templates");
|
|
15
|
+
|
|
16
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
17
|
+
|
|
18
|
+
function ask(question, defaultAnswer = "") {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
const prompt = defaultAnswer ? `${question} (${defaultAnswer}): ` : `${question}: `;
|
|
21
|
+
rl.question(prompt, (answer) => resolve(answer.trim() || defaultAnswer));
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function copyRecursive(src, dest, vars = {}) {
|
|
26
|
+
mkdirSync(dest, { recursive: true });
|
|
27
|
+
for (const name of readdirSync(src)) {
|
|
28
|
+
if (name === "node_modules") continue;
|
|
29
|
+
const srcPath = join(src, name);
|
|
30
|
+
const destPath = join(dest, name);
|
|
31
|
+
const stat = statSync(srcPath);
|
|
32
|
+
if (stat.isDirectory()) {
|
|
33
|
+
copyRecursive(srcPath, destPath, vars);
|
|
34
|
+
} else {
|
|
35
|
+
let content = readFileSync(srcPath, "utf8");
|
|
36
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
37
|
+
content = content.replace(new RegExp(`{{${key}}}`, "g"), value);
|
|
38
|
+
}
|
|
39
|
+
writeFileSync(destPath, content);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function main() {
|
|
45
|
+
console.log("\n create-fe-kit — Vue / React / Svelte + Vite + fe-kit\n");
|
|
46
|
+
|
|
47
|
+
const projectName = await ask("Project name", "my-fe-kit-app");
|
|
48
|
+
const nameSafe = projectName.replace(/\s+/g, "-").toLowerCase();
|
|
49
|
+
console.log("\nFramework:");
|
|
50
|
+
console.log(" 1) Vue");
|
|
51
|
+
console.log(" 2) React");
|
|
52
|
+
console.log(" 3) Svelte");
|
|
53
|
+
const choice = await ask("Choose (1/2/3)", "1");
|
|
54
|
+
const framework = { "1": "vue", "2": "react", "3": "svelte" }[choice] || "vue";
|
|
55
|
+
|
|
56
|
+
const templatePath = join(TEMPLATES_DIR, framework);
|
|
57
|
+
try {
|
|
58
|
+
statSync(templatePath);
|
|
59
|
+
} catch {
|
|
60
|
+
console.error(`\nTemplate "${framework}" not found. Use Vue (1) for now.`);
|
|
61
|
+
rl.close();
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const targetDir = join(process.cwd(), nameSafe);
|
|
66
|
+
try {
|
|
67
|
+
statSync(targetDir);
|
|
68
|
+
console.error(`\nDirectory already exists: ${targetDir}`);
|
|
69
|
+
rl.close();
|
|
70
|
+
process.exit(1);
|
|
71
|
+
} catch {}
|
|
72
|
+
|
|
73
|
+
const vars = {
|
|
74
|
+
PROJECT_NAME: nameSafe,
|
|
75
|
+
TITLE: projectName,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
console.log(`\nCreating ${nameSafe} with ${framework}...`);
|
|
79
|
+
copyRecursive(templatePath, targetDir, vars);
|
|
80
|
+
|
|
81
|
+
console.log("\nDone. Next steps:\n");
|
|
82
|
+
console.log(` cd ${nameSafe}`);
|
|
83
|
+
console.log(" pnpm install");
|
|
84
|
+
console.log(" pnpm dev\n");
|
|
85
|
+
rl.close();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
main().catch((err) => {
|
|
89
|
+
console.error(err);
|
|
90
|
+
rl.close();
|
|
91
|
+
process.exit(1);
|
|
92
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@liu_jimmy/create-fe-kit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Create a new project with fe-kit (Vue / React / Svelte + Vite + Tailwind v3 + ESLint)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-fe-kit": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"create",
|
|
15
|
+
"fe-kit",
|
|
16
|
+
"vue",
|
|
17
|
+
"react",
|
|
18
|
+
"svelte",
|
|
19
|
+
"vite",
|
|
20
|
+
"scaffold"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/fe-kit/fe-kit.git"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 生成/修改前端代码时必须遵守 fe-kit 的样式与命名规范,禁止偏离
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# fe-kit 风格约束(生成代码时必须遵守)
|
|
7
|
+
|
|
8
|
+
本项目中**所有生成或修改的前端代码**必须符合以下规范。不得使用任意颜色/间距/圆角,必须使用 fe-kit 的语义 class 与命名约定。
|
|
9
|
+
|
|
10
|
+
## 一、样式:只允许 fe-kit 语义 class
|
|
11
|
+
|
|
12
|
+
- **禁止**:内联 `style` 写颜色、间距、圆角、阴影;禁止任意 Tailwind 颜色如 `bg-blue-500`;禁止 Tailwind 任意值写颜色如 `bg-[#fff]`。
|
|
13
|
+
- **必须**:使用 fe-kit tailwind preset 的语义 class。
|
|
14
|
+
|
|
15
|
+
### 背景与文字:`bg-bg`、`bg-bg-elevated`、`text-fg`、`text-fg-muted`
|
|
16
|
+
### 主色与边框:`bg-primary`、`text-primary-fg`、`border-border`
|
|
17
|
+
### 圆角/阴影:`rounded-xl`、`shadow-md` 等;主题 `theme-blue`/`theme-purple`/`theme-green`,深色 `dark`
|
|
18
|
+
|
|
19
|
+
## 二、命名
|
|
20
|
+
|
|
21
|
+
- 目录 kebab-case;组件文件 PascalCase;非组件文件 kebab-case;变量 camelCase;常量 UPPER_SNAKE_CASE;类型 PascalCase;函数动词开头。
|
|
22
|
+
|
|
23
|
+
## 三、生成后应通过 `pnpm lint`。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{TITLE}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"lint": "eslint .",
|
|
11
|
+
"lint:fix": "eslint . --fix"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@liu_jimmy/fe-kit-tailwind-preset": "^0.1.0",
|
|
15
|
+
"@liu_jimmy/fe-kit-tokens": "^0.1.0",
|
|
16
|
+
"react": "^19.0.0",
|
|
17
|
+
"react-dom": "^19.0.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@eslint/js": "^9.15.0",
|
|
21
|
+
"@liu_jimmy/fe-kit-eslint-preset": "^0.1.0",
|
|
22
|
+
"@types/react": "^19.0.0",
|
|
23
|
+
"@types/react-dom": "^19.0.0",
|
|
24
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
25
|
+
"autoprefixer": "^10.4.20",
|
|
26
|
+
"eslint": "^9.39.3",
|
|
27
|
+
"postcss": "^8.4.49",
|
|
28
|
+
"tailwindcss": "^3.4.15",
|
|
29
|
+
"typescript": "~5.6.2",
|
|
30
|
+
"vite": "^6.0.3"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
|
|
3
|
+
type ThemeId = "theme-blue" | "theme-purple" | "theme-green";
|
|
4
|
+
|
|
5
|
+
const themes: { id: ThemeId; label: string }[] = [
|
|
6
|
+
{ id: "theme-blue", label: "蓝" },
|
|
7
|
+
{ id: "theme-purple", label: "紫" },
|
|
8
|
+
{ id: "theme-green", label: "绿" },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
function setTheme(id: ThemeId) {
|
|
12
|
+
document.documentElement.classList.remove("theme-blue", "theme-purple", "theme-green");
|
|
13
|
+
document.documentElement.classList.add(id);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function App() {
|
|
17
|
+
const [theme, setThemeState] = useState<ThemeId>("theme-blue");
|
|
18
|
+
const [isDark, setIsDark] = useState(false);
|
|
19
|
+
|
|
20
|
+
function handleSetTheme(id: ThemeId) {
|
|
21
|
+
setTheme(id);
|
|
22
|
+
setThemeState(id);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function toggleDark() {
|
|
26
|
+
setIsDark((v) => !v);
|
|
27
|
+
document.documentElement.classList.toggle("dark", !isDark);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="min-h-screen bg-bg text-fg font-sans">
|
|
32
|
+
<header className="border-b border-border bg-bg-elevated px-6 py-4 shadow-sm">
|
|
33
|
+
<h1 className="text-2xl font-semibold text-fg">{{TITLE}}</h1>
|
|
34
|
+
<p className="mt-1 text-sm text-fg-muted">fe-kit + React + Vite</p>
|
|
35
|
+
</header>
|
|
36
|
+
|
|
37
|
+
<main className="mx-auto max-w-2xl space-y-8 p-6">
|
|
38
|
+
<section className="rounded-xl border border-border bg-bg-elevated p-6 shadow-md">
|
|
39
|
+
<h2 className="text-lg font-medium text-fg">主题</h2>
|
|
40
|
+
<div className="mt-4 flex flex-wrap gap-2">
|
|
41
|
+
{themes.map((t) => (
|
|
42
|
+
<button
|
|
43
|
+
key={t.id}
|
|
44
|
+
type="button"
|
|
45
|
+
className={`rounded-lg px-4 py-2 text-sm font-medium transition-colors ${
|
|
46
|
+
theme === t.id ? "bg-primary text-primary-fg" : "bg-primary-muted text-fg hover:bg-primary hover:text-primary-fg"
|
|
47
|
+
}`}
|
|
48
|
+
onClick={() => handleSetTheme(t.id)}
|
|
49
|
+
>
|
|
50
|
+
{t.label}
|
|
51
|
+
</button>
|
|
52
|
+
))}
|
|
53
|
+
<button
|
|
54
|
+
type="button"
|
|
55
|
+
className={`rounded-lg border px-4 py-2 text-sm font-medium ${
|
|
56
|
+
isDark ? "border-fg bg-fg text-bg" : "border-border bg-bg-muted text-fg"
|
|
57
|
+
}`}
|
|
58
|
+
onClick={toggleDark}
|
|
59
|
+
>
|
|
60
|
+
{isDark ? "浅色" : "深色"}
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
</section>
|
|
64
|
+
</main>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default App;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import "@liu_jimmy/fe-kit-tokens";
|
|
2
|
+
import "./style.css";
|
|
3
|
+
import { StrictMode } from "react";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
import App from "./App.tsx";
|
|
6
|
+
|
|
7
|
+
createRoot(document.getElementById("root")!).render(
|
|
8
|
+
<StrictMode>
|
|
9
|
+
<App />
|
|
10
|
+
</StrictMode>,
|
|
11
|
+
);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true
|
|
18
|
+
},
|
|
19
|
+
"include": ["src"],
|
|
20
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
21
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 生成/修改前端代码时必须遵守 fe-kit 的样式与命名规范
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# fe-kit 风格约束
|
|
7
|
+
|
|
8
|
+
只使用 fe-kit 语义 class(bg-bg、text-fg、border-border、bg-primary、rounded-xl 等);禁止内联颜色、任意 Tailwind 颜色或任意值;命名:目录 kebab-case、组件 PascalCase、变量 camelCase、常量 UPPER_SNAKE_CASE;生成后应通过 pnpm lint。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{TITLE}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"lint": "eslint .",
|
|
11
|
+
"lint:fix": "eslint . --fix"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@liu_jimmy/fe-kit-tailwind-preset": "^0.1.0",
|
|
15
|
+
"@liu_jimmy/fe-kit-tokens": "^0.1.0",
|
|
16
|
+
"svelte": "^4.2.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@liu_jimmy/fe-kit-eslint-preset": "^0.1.0",
|
|
20
|
+
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
|
21
|
+
"autoprefixer": "^10.4.20",
|
|
22
|
+
"eslint": "^9.39.3",
|
|
23
|
+
"postcss": "^8.4.49",
|
|
24
|
+
"tailwindcss": "^3.4.15",
|
|
25
|
+
"typescript": "~5.6.2",
|
|
26
|
+
"vite": "^6.0.3"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
type ThemeId = "theme-blue" | "theme-purple" | "theme-green";
|
|
3
|
+
|
|
4
|
+
let theme: ThemeId = "theme-blue";
|
|
5
|
+
let isDark = false;
|
|
6
|
+
|
|
7
|
+
const themes: { id: ThemeId; label: string }[] = [
|
|
8
|
+
{ id: "theme-blue", label: "蓝" },
|
|
9
|
+
{ id: "theme-purple", label: "紫" },
|
|
10
|
+
{ id: "theme-green", label: "绿" },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function setTheme(id: ThemeId) {
|
|
14
|
+
document.documentElement.classList.remove("theme-blue", "theme-purple", "theme-green");
|
|
15
|
+
document.documentElement.classList.add(id);
|
|
16
|
+
theme = id;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function toggleDark() {
|
|
20
|
+
isDark = !isDark;
|
|
21
|
+
document.documentElement.classList.toggle("dark", isDark);
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<main class="min-h-screen bg-bg text-fg font-sans">
|
|
26
|
+
<header class="border-b border-border bg-bg-elevated px-6 py-4 shadow-sm">
|
|
27
|
+
<h1 class="text-2xl font-semibold text-fg">{{TITLE}}</h1>
|
|
28
|
+
<p class="mt-1 text-sm text-fg-muted">fe-kit + Svelte + Vite</p>
|
|
29
|
+
</header>
|
|
30
|
+
|
|
31
|
+
<div class="mx-auto max-w-2xl space-y-8 p-6">
|
|
32
|
+
<section class="rounded-xl border border-border bg-bg-elevated p-6 shadow-md">
|
|
33
|
+
<h2 class="text-lg font-medium text-fg">主题</h2>
|
|
34
|
+
<div class="mt-4 flex flex-wrap gap-2">
|
|
35
|
+
{#each themes as t}
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
class="rounded-lg px-4 py-2 text-sm font-medium transition-colors {theme === t.id
|
|
39
|
+
? 'bg-primary text-primary-fg'
|
|
40
|
+
: 'bg-primary-muted text-fg hover:bg-primary hover:text-primary-fg'}"
|
|
41
|
+
on:click={() => setTheme(t.id)}
|
|
42
|
+
>
|
|
43
|
+
{t.label}
|
|
44
|
+
</button>
|
|
45
|
+
{/each}
|
|
46
|
+
<button
|
|
47
|
+
type="button"
|
|
48
|
+
class="rounded-lg border px-4 py-2 text-sm font-medium {isDark ? 'border-fg bg-fg text-bg' : 'border-border bg-bg-muted text-fg'}"
|
|
49
|
+
on:click={toggleDark}
|
|
50
|
+
>
|
|
51
|
+
{isDark ? "浅色" : "深色"}
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
</section>
|
|
55
|
+
</div>
|
|
56
|
+
</main>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"moduleResolution": "bundler",
|
|
8
|
+
"allowImportingTsExtensions": true,
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"strict": true,
|
|
13
|
+
"noUnusedLocals": true,
|
|
14
|
+
"noUnusedParameters": true,
|
|
15
|
+
"noFallthroughCasesInSwitch": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*.ts", "src/**/*.svelte"],
|
|
18
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
19
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 生成/修改前端代码时必须遵守 fe-kit 的样式与命名规范,禁止偏离
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# fe-kit 风格约束(生成代码时必须遵守)
|
|
7
|
+
|
|
8
|
+
本项目中**所有生成或修改的前端代码**必须符合以下规范。不得使用任意颜色/间距/圆角,必须使用 fe-kit 的语义 class 与命名约定。
|
|
9
|
+
|
|
10
|
+
## 一、样式:只允许 fe-kit 语义 class
|
|
11
|
+
|
|
12
|
+
- **禁止**:内联 `style` 写颜色、间距、圆角、阴影;禁止任意 Tailwind 颜色如 `bg-blue-500`、`text-gray-700`;禁止 Tailwind 任意值写颜色如 `bg-[#fff]`、`text-[rgb(0,0,0)]`。
|
|
13
|
+
- **必须**:使用 fe-kit tailwind preset 的语义 class。
|
|
14
|
+
|
|
15
|
+
### 背景与文字
|
|
16
|
+
- 背景:`bg-bg`、`bg-bg-elevated`、`bg-bg-muted`
|
|
17
|
+
- 文字:`text-fg`、`text-fg-muted`、`text-fg-subtle`
|
|
18
|
+
|
|
19
|
+
### 主色与边框
|
|
20
|
+
- 主色:`bg-primary`、`text-primary-fg`、`bg-primary-muted`、hover 用 `hover:bg-primary-hover`
|
|
21
|
+
- 边框:`border-border`、`border-border-strong`
|
|
22
|
+
|
|
23
|
+
### 圆角、间距、阴影
|
|
24
|
+
- 圆角:`rounded-sm`、`rounded-md`、`rounded-lg`、`rounded-xl`、`rounded-2xl`、`rounded-full`
|
|
25
|
+
- 阴影:`shadow-sm`、`shadow-md`、`shadow-lg`、`shadow-xl`
|
|
26
|
+
- 间距:`p-4`、`px-6`、`space-y-4` 等
|
|
27
|
+
|
|
28
|
+
### 主题
|
|
29
|
+
- 主题色:`theme-blue`、`theme-purple`、`theme-green`(加在 `document.documentElement`)
|
|
30
|
+
- 深色:根节点加 `dark` class
|
|
31
|
+
|
|
32
|
+
## 二、文件与目录命名
|
|
33
|
+
|
|
34
|
+
- **目录**:kebab-case(`user-card/`、`data-table/`)
|
|
35
|
+
- **组件文件**(.vue / .tsx / .svelte):PascalCase(`UserCard.vue`)
|
|
36
|
+
- **非组件文件**(.ts / .js):kebab-case(`format-date.ts`)
|
|
37
|
+
|
|
38
|
+
## 三、变量与函数命名
|
|
39
|
+
|
|
40
|
+
- **变量**:camelCase(`userName`、`isLoading`)
|
|
41
|
+
- **函数**:动词开头(`fetchUser()`、`formatDate()`)
|
|
42
|
+
- **常量**:UPPER_SNAKE_CASE(`API_BASE_URL`)
|
|
43
|
+
- **类型**:PascalCase(`type User`、`interface Pagination`)
|
|
44
|
+
|
|
45
|
+
## 四、Vue 组件
|
|
46
|
+
|
|
47
|
+
- 模板中组件名 PascalCase;块顺序:script → template → style
|
|
48
|
+
|
|
49
|
+
## 五、生成后自检
|
|
50
|
+
|
|
51
|
+
- 代码应能通过 `pnpm lint`;若有报错只做最小修复。
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# AI 生成代码约束(fe-kit)
|
|
2
|
+
|
|
3
|
+
生成或修改前端代码时必须遵守以下约束。
|
|
4
|
+
|
|
5
|
+
## 核心原则
|
|
6
|
+
|
|
7
|
+
1. **样式**:只使用 fe-kit 语义 class(`bg-bg`、`text-fg`、`border-border`、`bg-primary`、`rounded-xl` 等),禁止内联颜色/间距,禁止任意 Tailwind 颜色(如 `bg-blue-500`)或任意值(如 `bg-[#fff]`)。
|
|
8
|
+
2. **命名**:目录 kebab-case、组件文件 PascalCase、变量 camelCase、常量 UPPER_SNAKE_CASE、类型 PascalCase、函数动词开头。
|
|
9
|
+
3. **生成后**:应能通过 `pnpm lint`。
|
|
10
|
+
|
|
11
|
+
## 语义 class 速查
|
|
12
|
+
|
|
13
|
+
| 用途 | 使用示例 |
|
|
14
|
+
|------|----------|
|
|
15
|
+
| 背景 | `bg-bg`、`bg-bg-elevated`、`bg-bg-muted` |
|
|
16
|
+
| 文字 | `text-fg`、`text-fg-muted`、`text-fg-subtle` |
|
|
17
|
+
| 主色 | `bg-primary`、`text-primary-fg`、`bg-primary-muted`、`hover:bg-primary-hover` |
|
|
18
|
+
| 边框 | `border-border`、`border-border-strong` |
|
|
19
|
+
| 圆角 | `rounded-sm`~`rounded-2xl`、`rounded-full` |
|
|
20
|
+
| 阴影 | `shadow-sm`~`shadow-xl` |
|
|
21
|
+
|
|
22
|
+
主题:根节点 `theme-blue` / `theme-purple` / `theme-green`;深色加 `dark`。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{TITLE}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vue-tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"lint": "eslint .",
|
|
11
|
+
"lint:fix": "eslint . --fix"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@liu_jimmy/fe-kit-tailwind-preset": "^0.1.0",
|
|
15
|
+
"@liu_jimmy/fe-kit-tokens": "^0.1.0",
|
|
16
|
+
"vue": "^3.5.13"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@liu_jimmy/fe-kit-eslint-preset": "^0.1.0",
|
|
20
|
+
"@vitejs/plugin-vue": "^5.2.1",
|
|
21
|
+
"autoprefixer": "^10.4.20",
|
|
22
|
+
"eslint": "^9.39.3",
|
|
23
|
+
"postcss": "^8.4.49",
|
|
24
|
+
"tailwindcss": "^3.4.15",
|
|
25
|
+
"typescript": "~5.6.2",
|
|
26
|
+
"vite": "^6.0.3",
|
|
27
|
+
"vue-tsc": "^2.1.10"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from "vue";
|
|
3
|
+
|
|
4
|
+
const theme = ref<"theme-blue" | "theme-purple" | "theme-green">("theme-blue");
|
|
5
|
+
const isDark = ref(false);
|
|
6
|
+
|
|
7
|
+
const themes = [
|
|
8
|
+
{ id: "theme-blue" as const, label: "蓝" },
|
|
9
|
+
{ id: "theme-purple" as const, label: "紫" },
|
|
10
|
+
{ id: "theme-green" as const, label: "绿" },
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
function setTheme(id: (typeof themes)[number]["id"]) {
|
|
14
|
+
document.documentElement.classList.remove("theme-blue", "theme-purple", "theme-green");
|
|
15
|
+
document.documentElement.classList.add(id);
|
|
16
|
+
theme.value = id;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function toggleDark() {
|
|
20
|
+
isDark.value = !isDark.value;
|
|
21
|
+
document.documentElement.classList.toggle("dark", isDark.value);
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<div class="min-h-screen bg-bg text-fg font-sans">
|
|
27
|
+
<header class="border-b border-border bg-bg-elevated px-6 py-4 shadow-sm">
|
|
28
|
+
<h1 class="text-2xl font-semibold text-fg">{{TITLE}}</h1>
|
|
29
|
+
<p class="mt-1 text-sm text-fg-muted">fe-kit + Vue + Vite</p>
|
|
30
|
+
</header>
|
|
31
|
+
|
|
32
|
+
<main class="mx-auto max-w-2xl space-y-8 p-6">
|
|
33
|
+
<section class="rounded-xl border border-border bg-bg-elevated p-6 shadow-md">
|
|
34
|
+
<h2 class="text-lg font-medium text-fg">主题</h2>
|
|
35
|
+
<div class="mt-4 flex flex-wrap gap-2">
|
|
36
|
+
<button
|
|
37
|
+
v-for="t in themes"
|
|
38
|
+
:key="t.id"
|
|
39
|
+
type="button"
|
|
40
|
+
:class="[
|
|
41
|
+
'rounded-lg px-4 py-2 text-sm font-medium transition-colors',
|
|
42
|
+
theme === t.id ? 'bg-primary text-primary-fg' : 'bg-primary-muted text-fg hover:bg-primary hover:text-primary-fg',
|
|
43
|
+
]"
|
|
44
|
+
@click="setTheme(t.id)"
|
|
45
|
+
>
|
|
46
|
+
{{ t.label }}
|
|
47
|
+
</button>
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
:class="['rounded-lg border px-4 py-2 text-sm font-medium', isDark ? 'border-fg bg-fg text-bg' : 'border-border bg-bg-muted text-fg']"
|
|
51
|
+
@click="toggleDark"
|
|
52
|
+
>
|
|
53
|
+
{{ isDark ? "浅色" : "深色" }}
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
56
|
+
</section>
|
|
57
|
+
</main>
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "preserve",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"paths": { "@/*": ["./src/*"] }
|
|
19
|
+
},
|
|
20
|
+
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
|
|
21
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
22
|
+
}
|