优化卡死的问题
This commit is contained in:
158
lib/scaffold/config.js
Normal file
158
lib/scaffold/config.js
Normal file
@ -0,0 +1,158 @@
|
||||
// 二级菜单 - 项目类型
|
||||
export const projectTypes = [
|
||||
{ name: "前端", desc: "React/Vue项目" },
|
||||
{ name: "后端", desc: "Node.js服务端" },
|
||||
{ name: "全栈", desc: "前后端一体化" },
|
||||
];
|
||||
|
||||
// 三级菜单 - 具体框架
|
||||
export const frameworks = {
|
||||
前端: [
|
||||
{ name: "React", desc: "Vite + React", value: "react-vite" },
|
||||
{ name: "Vue", desc: "Vite + Vue", value: "vue-vite" },
|
||||
],
|
||||
后端: [
|
||||
{ name: "Bun", desc: "Bun运行时", value: "bun" },
|
||||
{ name: "NestJS", desc: "企业级框架", value: "nestjs" },
|
||||
{ name: "Express", desc: "Express Generator", value: "express" },
|
||||
{ name: "Koa", desc: "Koa Generator", value: "koa" },
|
||||
{ name: "Egg", desc: "阿里Egg.js", value: "egg" },
|
||||
{ name: "Midway", desc: "阿里Midway", value: "midway" },
|
||||
{ name: "Fastify", desc: "高性能框架", value: "fastify" },
|
||||
{ name: "AdonisJS", desc: "全功能MVC", value: "adonisjs" },
|
||||
],
|
||||
全栈: [
|
||||
{ name: "Next.js", desc: "React全栈框架", value: "nextjs" },
|
||||
{ name: "Nuxt", desc: "Vue全栈框架", value: "nuxt" },
|
||||
],
|
||||
};
|
||||
|
||||
// 前端组件配置步骤
|
||||
export const frontendSteps = [
|
||||
{
|
||||
name: "路由",
|
||||
type: "select",
|
||||
message: "选择路由方案",
|
||||
options: [
|
||||
{ value: "none", label: "不需要路由" },
|
||||
{ value: "react-router", label: "React Router(React项目)" },
|
||||
{ value: "vue-router", label: "Vue Router(Vue项目)" },
|
||||
],
|
||||
default: "none"
|
||||
},
|
||||
{
|
||||
name: "状态管理",
|
||||
type: "select",
|
||||
message: "选择状态管理方案",
|
||||
options: [
|
||||
{ value: "none", label: "不需要状态管理" },
|
||||
{ value: "zustand", label: "Zustand(轻量级,React推荐)" },
|
||||
{ value: "pinia", label: "Pinia(Vue官方推荐)" },
|
||||
{ value: "redux", label: "Redux Toolkit(大型项目)" },
|
||||
{ value: "mobx", label: "MobX(响应式状态管理)" },
|
||||
],
|
||||
default: "none"
|
||||
},
|
||||
{
|
||||
name: "HTTP请求",
|
||||
type: "select",
|
||||
message: "选择HTTP请求库",
|
||||
options: [
|
||||
{ value: "none", label: "不需要HTTP库" },
|
||||
{ value: "axios", label: "Axios(功能全面,拦截器支持)" },
|
||||
{ value: "ky", label: "Ky(轻量级,基于Fetch)" },
|
||||
{ value: "ofetch", label: "ofetch(Nuxt团队出品)" },
|
||||
],
|
||||
default: "none"
|
||||
},
|
||||
{
|
||||
name: "UI组件库",
|
||||
type: "select",
|
||||
message: "选择UI组件库",
|
||||
options: [
|
||||
{ value: "none", label: "不需要UI库" },
|
||||
{ value: "antd", label: "Ant Design(企业级,React)" },
|
||||
{ value: "element-plus", label: "Element Plus(饿了么,Vue)" },
|
||||
{ value: "arco", label: "Arco Design(字节跳动)" },
|
||||
{ value: "naive-ui", label: "Naive UI(Vue3原生)" },
|
||||
{ value: "shadcn", label: "shadcn/ui(可定制,React)" },
|
||||
],
|
||||
default: "none"
|
||||
},
|
||||
{
|
||||
name: "CSS方案",
|
||||
type: "select",
|
||||
message: "选择CSS方案",
|
||||
options: [
|
||||
{ value: "none", label: "原生CSS" },
|
||||
{ value: "tailwind", label: "Tailwind CSS(原子化CSS)" },
|
||||
{ value: "unocss", label: "UnoCSS(即时原子化)" },
|
||||
{ value: "sass", label: "Sass/SCSS(预处理器)" },
|
||||
{ value: "less", label: "Less(预处理器)" },
|
||||
{ value: "styled", label: "Styled Components(CSS-in-JS)" },
|
||||
],
|
||||
default: "none"
|
||||
},
|
||||
{
|
||||
name: "工具库",
|
||||
type: "multiselect",
|
||||
message: "选择常用工具库",
|
||||
options: [
|
||||
{ value: "lodash", label: "Lodash(工具函数库)" },
|
||||
{ value: "dayjs", label: "Day.js(日期处理)" },
|
||||
{ value: "iconify", label: "Iconify(图标库)" },
|
||||
{ value: "vueuse", label: "VueUse(Vue组合式工具)" },
|
||||
{ value: "ahooks", label: "ahooks(React Hooks库)" },
|
||||
],
|
||||
default: []
|
||||
},
|
||||
{
|
||||
name: "表单验证",
|
||||
type: "select",
|
||||
message: "选择表单验证方案",
|
||||
options: [
|
||||
{ value: "none", label: "不需要表单验证" },
|
||||
{ value: "zod", label: "Zod(TypeScript优先)" },
|
||||
{ value: "yup", label: "Yup(声明式验证)" },
|
||||
{ value: "vee-validate", label: "VeeValidate(Vue专用)" },
|
||||
{ value: "react-hook-form", label: "React Hook Form" },
|
||||
],
|
||||
default: "none"
|
||||
},
|
||||
{
|
||||
name: "国际化",
|
||||
type: "select",
|
||||
message: "选择国际化方案",
|
||||
options: [
|
||||
{ value: "none", label: "不需要国际化" },
|
||||
{ value: "i18next", label: "i18next(通用方案)" },
|
||||
{ value: "vue-i18n", label: "Vue I18n(Vue专用)" },
|
||||
],
|
||||
default: "none"
|
||||
},
|
||||
{
|
||||
name: "代码规范",
|
||||
type: "multiselect",
|
||||
message: "选择代码规范工具",
|
||||
options: [
|
||||
{ value: "eslint", label: "ESLint(代码检查)" },
|
||||
{ value: "prettier", label: "Prettier(代码格式化)" },
|
||||
{ value: "husky", label: "Husky(Git Hooks)" },
|
||||
{ value: "lint-staged", label: "lint-staged(暂存区检查)" },
|
||||
{ value: "commitlint", label: "Commitlint(提交信息规范)" },
|
||||
],
|
||||
default: []
|
||||
},
|
||||
{
|
||||
name: "其他",
|
||||
type: "multiselect",
|
||||
message: "选择其他功能",
|
||||
options: [
|
||||
{ value: "mock", label: "Mock.js(模拟数据)" },
|
||||
{ value: "pwa", label: "PWA支持" },
|
||||
{ value: "storage", label: "持久化存储封装" },
|
||||
{ value: "env", label: "环境变量配置" },
|
||||
],
|
||||
default: []
|
||||
}
|
||||
];
|
||||
39
lib/scaffold/index.js
Normal file
39
lib/scaffold/index.js
Normal file
@ -0,0 +1,39 @@
|
||||
import color from "picocolors";
|
||||
import { projectTypes, frameworks } from "./config.js";
|
||||
import { gridSelect, createComponentUI, formatResults, waitKey, showPlaceholder } from "./ui.js";
|
||||
|
||||
export async function run() {
|
||||
while (true) {
|
||||
// 二级菜单 - 项目类型
|
||||
const typeResult = await gridSelect(projectTypes, "项目脚手架 - 选择类型");
|
||||
if (typeResult.action === "back") return "back";
|
||||
|
||||
const projectType = typeResult.item.name;
|
||||
const frameworkList = frameworks[projectType];
|
||||
|
||||
while (true) {
|
||||
// 三级菜单 - 框架选择
|
||||
const frameworkResult = await gridSelect(frameworkList, `${projectType}项目 - 选择框架`);
|
||||
if (frameworkResult.action === "back") break;
|
||||
|
||||
const framework = frameworkResult.item;
|
||||
|
||||
if (projectType === "前端" || projectType === "全栈") {
|
||||
// 组件配置
|
||||
const ui = createComponentUI(framework.name);
|
||||
const result = await ui.runInteractive();
|
||||
|
||||
if (result) {
|
||||
const summary = formatResults(result.results);
|
||||
ui.showSummary(summary);
|
||||
console.log(color.yellow("功能开发中,敬请期待..."));
|
||||
await waitKey();
|
||||
}
|
||||
} else {
|
||||
// 后端暂无组件配置
|
||||
showPlaceholder(framework);
|
||||
await waitKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
152
lib/scaffold/ui.js
Normal file
152
lib/scaffold/ui.js
Normal file
@ -0,0 +1,152 @@
|
||||
import color from "picocolors";
|
||||
import { initKeypress, onKey, stopKeypress } from "../keyboard.js";
|
||||
import { createStepUI } from "../utils/stepui.js";
|
||||
import { frontendSteps } from "./config.js";
|
||||
|
||||
function clearScreen() {
|
||||
process.stdout.write('\x1Bc');
|
||||
}
|
||||
|
||||
function strWidth(str) {
|
||||
let width = 0;
|
||||
for (const char of str) {
|
||||
width += char.charCodeAt(0) > 127 ? 2 : 1;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
function padEnd(str, width) {
|
||||
return str + " ".repeat(Math.max(0, width - strWidth(str)));
|
||||
}
|
||||
|
||||
// 网格选择器
|
||||
export async function gridSelect(items, title) {
|
||||
let current = 0;
|
||||
let resolved = false;
|
||||
const termWidth = process.stdout.columns || 80;
|
||||
const cols = Math.min(3, items.length);
|
||||
const colWidth = 20;
|
||||
const rows = Math.ceil(items.length / cols);
|
||||
const totalWidth = cols * colWidth;
|
||||
const pad = " ".repeat(Math.max(0, Math.floor((termWidth - totalWidth) / 2)));
|
||||
|
||||
function render() {
|
||||
clearScreen();
|
||||
console.log("");
|
||||
const titlePad = " ".repeat(Math.max(0, Math.floor((termWidth - strWidth(title) - 4) / 2)));
|
||||
console.log(titlePad + color.bgMagenta(color.white(` ${title} `)));
|
||||
console.log("");
|
||||
console.log(pad + color.dim("↑ ↓ ← → 选择 | Enter 确认 | Esc 返回"));
|
||||
console.log("\n");
|
||||
|
||||
for (let row = 0; row < rows; row++) {
|
||||
let line = "";
|
||||
let descLine = "";
|
||||
|
||||
for (let col = 0; col < cols; col++) {
|
||||
const idx = row * cols + col;
|
||||
if (idx < items.length) {
|
||||
const item = items[idx];
|
||||
const isSelected = idx === current;
|
||||
|
||||
if (isSelected) {
|
||||
line += color.cyan("[" + item.name + "]");
|
||||
} else {
|
||||
line += " " + item.name + " ";
|
||||
}
|
||||
line += " ".repeat(colWidth - strWidth(item.name) - 2);
|
||||
descLine += padEnd(item.desc || "", colWidth);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(pad + line);
|
||||
console.log(pad + color.dim(descLine));
|
||||
console.log("\n");
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
initKeypress();
|
||||
render();
|
||||
|
||||
onKey((str, key) => {
|
||||
if (!key || resolved) return;
|
||||
|
||||
const row = Math.floor(current / cols);
|
||||
const col = current % cols;
|
||||
|
||||
switch (key.name) {
|
||||
case "up":
|
||||
if (row > 0) { current -= cols; render(); }
|
||||
break;
|
||||
case "down":
|
||||
if (row < rows - 1 && current + cols < items.length) { current += cols; render(); }
|
||||
break;
|
||||
case "left":
|
||||
if (col > 0) { current--; render(); }
|
||||
break;
|
||||
case "right":
|
||||
if (col < cols - 1 && current < items.length - 1) { current++; render(); }
|
||||
break;
|
||||
case "return":
|
||||
resolved = true;
|
||||
stopKeypress();
|
||||
setImmediate(() => resolve({ action: "select", item: items[current] }));
|
||||
break;
|
||||
case "escape":
|
||||
resolved = true;
|
||||
stopKeypress();
|
||||
setImmediate(() => resolve({ action: "back" }));
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 创建组件配置UI
|
||||
export function createComponentUI(frameworkName) {
|
||||
return createStepUI({
|
||||
title: `${frameworkName} - 组件配置`,
|
||||
getSteps: () => frontendSteps
|
||||
});
|
||||
}
|
||||
|
||||
// 解析配置结果
|
||||
export function formatResults(results) {
|
||||
const stepNames = frontendSteps.map(s => s.name);
|
||||
const summary = [];
|
||||
|
||||
results.forEach((val, i) => {
|
||||
if (Array.isArray(val) && val.length > 0) {
|
||||
summary.push(`${stepNames[i]}: ${val.join(", ")}`);
|
||||
} else if (val && val !== "none") {
|
||||
summary.push(`${stepNames[i]}: ${val}`);
|
||||
}
|
||||
});
|
||||
|
||||
return summary.length ? summary : ["未选择任何组件"];
|
||||
}
|
||||
|
||||
// 等待按键
|
||||
export async function waitKey(message = "按任意键返回") {
|
||||
console.log(color.dim(`\n${message}`));
|
||||
|
||||
return new Promise(resolve => {
|
||||
initKeypress();
|
||||
onKey(() => {
|
||||
stopKeypress();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 显示占位信息
|
||||
export function showPlaceholder(framework) {
|
||||
clearScreen();
|
||||
console.log("");
|
||||
console.log(color.bgGreen(color.black(" 配置完成 ")));
|
||||
console.log("");
|
||||
console.log(color.cyan("框架: ") + framework.name);
|
||||
console.log("");
|
||||
console.log(color.yellow("功能开发中,敬请期待..."));
|
||||
}
|
||||
Reference in New Issue
Block a user