284 lines
10 KiB
Python
284 lines
10 KiB
Python
from fastapi import APIRouter, HTTPException
|
||
from typing import Dict, Any
|
||
from pathlib import Path
|
||
import re
|
||
|
||
router = APIRouter()
|
||
|
||
def to_snake(name: str) -> str:
|
||
return name.replace(' ', '_').replace('-', '_').lower()
|
||
|
||
@router.post("/jingrow/dev/create-pagetype-template")
|
||
async def create_pagetypes(payload: Dict[str, Any]):
|
||
try:
|
||
name = payload.get("pagetype")
|
||
app = payload.get("app", "jingrow")
|
||
module = payload.get("module", "").replace('.', '/').lower()
|
||
create_frontend = bool(payload.get("create_frontend", True))
|
||
create_backend = bool(payload.get("create_backend", True))
|
||
frontend_options = payload.get("frontend_options", ["toolbar"])
|
||
field_type_names = payload.get("field_type_names", [])
|
||
if not name:
|
||
raise ValueError("pagetype is required")
|
||
slug = to_snake(name)
|
||
|
||
current = Path(__file__).resolve()
|
||
root = current.parents[4]
|
||
|
||
frontend_paths = []
|
||
backend_path = None
|
||
frontend_exists = False
|
||
backend_exists = False
|
||
|
||
if create_frontend:
|
||
# 工具栏
|
||
if "toolbar" in frontend_options:
|
||
fp = root / "apps" / app / "frontend" / "src" / "views" / "pagetype" / slug / f"{slug}_toolbar.vue"
|
||
fp.parent.mkdir(parents=True, exist_ok=True)
|
||
if fp.exists():
|
||
frontend_exists = True
|
||
else:
|
||
toolbar_content = "<template>\n <div></div>\n</template>\n<script setup lang=\"ts\">\n</script>\n"
|
||
fp.write_text(toolbar_content, encoding="utf-8")
|
||
frontend_paths.append(str(fp))
|
||
|
||
# 侧边栏
|
||
if "sidebar" in frontend_options:
|
||
fp = root / "apps" / app / "frontend" / "src" / "views" / "pagetype" / slug / f"{slug}_sidebar.vue"
|
||
fp.parent.mkdir(parents=True, exist_ok=True)
|
||
if fp.exists():
|
||
frontend_exists = True
|
||
else:
|
||
sidebar_content = "<template>\n <div class=\"sidebar\"></div>\n</template>\n<script setup lang=\"ts\">\n</script>\n<style scoped>\n.sidebar {}\n</style>\n"
|
||
fp.write_text(sidebar_content, encoding="utf-8")
|
||
frontend_paths.append(str(fp))
|
||
|
||
# 页面
|
||
if "page" in frontend_options:
|
||
fp = root / "apps" / app / "frontend" / "src" / "views" / "pagetype" / slug / f"{slug}.vue"
|
||
fp.parent.mkdir(parents=True, exist_ok=True)
|
||
if fp.exists():
|
||
frontend_exists = True
|
||
else:
|
||
page_content = "<template>\n <div class=\"pagetype-page\"></div>\n</template>\n<script setup lang=\"ts\">\n</script>\n<style scoped>\n.pagetype-page {}\n</style>\n"
|
||
fp.write_text(page_content, encoding="utf-8")
|
||
frontend_paths.append(str(fp))
|
||
|
||
# 字段类型控件
|
||
if "field_types" in frontend_options:
|
||
fp = root / "apps" / app / "frontend" / "src" / "views" / "pagetype" / slug / "form" / "controls"
|
||
fp.mkdir(parents=True, exist_ok=True)
|
||
|
||
if field_type_names:
|
||
# 为每个选中的字段类型创建文件
|
||
for field_type_name in field_type_names:
|
||
field_file = fp / f"{field_type_name}.vue"
|
||
if field_file.exists():
|
||
frontend_exists = True
|
||
else:
|
||
# 创建字段类型组件的模板
|
||
field_content = '''<script setup lang="ts">
|
||
import { computed } from 'vue'
|
||
import { NInput } from 'naive-ui'
|
||
|
||
const props = defineProps<{
|
||
df: any;
|
||
record: Record<string, any>;
|
||
canEdit: boolean;
|
||
ctx: any
|
||
}>()
|
||
|
||
// Label布局:上下结构(vertical) 或 左右结构(horizontal)
|
||
const labelLayout = computed(() => props.df.label_layout || 'vertical')
|
||
</script>
|
||
|
||
<template>
|
||
<div :class="['field-wrapper', `layout-${labelLayout}`]">
|
||
<label class="field-label">
|
||
{{ ctx.t(df.label || df.fieldname) }}
|
||
<span v-if="df.reqd" class="required">*</span>
|
||
</label>
|
||
<n-input
|
||
v-model:value="record[df.fieldname]"
|
||
type="text"
|
||
:placeholder="ctx.t(df.fieldname)"
|
||
:disabled="!canEdit"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.field-wrapper :deep(.n-input) {
|
||
flex: 1;
|
||
}
|
||
</style>
|
||
'''
|
||
field_file.write_text(field_content, encoding="utf-8")
|
||
frontend_paths.append(str(field_file))
|
||
else:
|
||
frontend_paths.append(f"{str(fp)}/ (创建自定义字段控件)")
|
||
|
||
frontend_path = "\n".join(frontend_paths) if frontend_paths else None
|
||
else:
|
||
frontend_path = None
|
||
|
||
if create_backend:
|
||
bp = root / "apps" / app / app / module / "pagetype" / slug / f"{slug}.py"
|
||
bp.parent.mkdir(parents=True, exist_ok=True)
|
||
if bp.exists():
|
||
backend_exists = True
|
||
else:
|
||
class_name = ''.join(word.capitalize() for word in slug.split('_'))
|
||
bp.write_text("# Copyright (c) 2025, jingrow and contributors\n# For license information, please see license.txt\n\n# import jingrow\nfrom jingrow.model.page import Page\n\n\nclass " + class_name + "(Page):\n\tpass\n", encoding="utf-8")
|
||
backend_path = str(bp)
|
||
|
||
return {
|
||
"success": True,
|
||
"frontend_path": frontend_path,
|
||
"backend_path": backend_path,
|
||
"frontend_exists": frontend_exists,
|
||
"backend_exists": backend_exists
|
||
}
|
||
except Exception as e:
|
||
raise HTTPException(status_code=400, detail=str(e))
|
||
|
||
|
||
@router.post("/jingrow/dev/create-app-template")
|
||
async def create_app_template(payload: Dict[str, Any]):
|
||
"""创建现代化的App模板,符合最佳实践"""
|
||
try:
|
||
app_name = payload.get("appName", "").strip()
|
||
app_title = payload.get("appTitle", "").strip()
|
||
publisher = payload.get("publisher", "").strip()
|
||
description = payload.get("description", "").strip()
|
||
email = payload.get("email", "").strip()
|
||
license_type = payload.get("license", "MIT")
|
||
# 自动生成模块名称,与bench new-app保持一致
|
||
module_name = app_title # 使用app_title作为模块名称
|
||
# 默认包含所有功能,简化用户选择
|
||
include_api = True
|
||
include_frontend = True
|
||
include_tests = True
|
||
cloud_compatible = True
|
||
|
||
if not app_name:
|
||
raise ValueError("App name is required")
|
||
if not app_title:
|
||
raise ValueError("App title is required")
|
||
if not publisher:
|
||
raise ValueError("Publisher is required")
|
||
if not description:
|
||
raise ValueError("Description is required")
|
||
|
||
# 验证app名称格式
|
||
if not re.match(r'^[a-z][a-z0-9_]*$', app_name):
|
||
raise ValueError("App name must start with lowercase letter and contain only lowercase letters, numbers, and underscores")
|
||
|
||
current = Path(__file__).resolve()
|
||
# project root (apps/jingrow/jingrow/api/dev.py) -> go up 4 to reach /home/dev/jingrow-framework/
|
||
root = current.parents[4]
|
||
|
||
# 创建app目录结构 - 保存到apps/app_name
|
||
app_dir = root / "apps" / app_name
|
||
app_dir.mkdir(parents=True, exist_ok=True)
|
||
|
||
# 创建frontend和app_name子目录
|
||
frontend_dir = app_dir / "frontend"
|
||
backend_dir = app_dir / app_name
|
||
frontend_dir.mkdir(parents=True, exist_ok=True)
|
||
backend_dir.mkdir(parents=True, exist_ok=True)
|
||
|
||
# 创建app内部结构(在app_name目录下)
|
||
app_inner_dir = backend_dir
|
||
|
||
# 创建hooks.py
|
||
hooks_content = f'''app_name = "{app_name}"
|
||
app_title = "{app_title}"
|
||
app_publisher = "{publisher}"
|
||
app_description = "{description}"
|
||
app_email = "{email}"
|
||
app_license = "{license_type.lower()}"
|
||
'''
|
||
|
||
hooks_file = app_inner_dir / "hooks.py"
|
||
hooks_file.write_text(hooks_content, encoding="utf-8")
|
||
|
||
# 创建__init__.py
|
||
init_content = f'''__version__ = "0.0.1"
|
||
'''
|
||
(app_inner_dir / "__init__.py").write_text(init_content, encoding="utf-8")
|
||
|
||
# (不再创建 modules.txt 与 config 目录)
|
||
|
||
(app_inner_dir / "public").mkdir(exist_ok=True)
|
||
(app_inner_dir / "public" / "css").mkdir(exist_ok=True)
|
||
(app_inner_dir / "public" / "js").mkdir(exist_ok=True)
|
||
(app_inner_dir / "public" / ".gitkeep").write_text("", encoding="utf-8")
|
||
|
||
# 创建module目录
|
||
(app_inner_dir / app_name).mkdir(exist_ok=True)
|
||
(app_inner_dir / app_name / "__init__.py").write_text("", encoding="utf-8")
|
||
|
||
# 创建README.md(最小化文档)
|
||
readme_content = f'''# {app_title}
|
||
|
||
{description}
|
||
|
||
## 安装
|
||
|
||
```bash
|
||
pip install -e .
|
||
```
|
||
|
||
## 开发
|
||
|
||
```bash
|
||
pip install -e ".[dev]"
|
||
```
|
||
|
||
## 许可证
|
||
|
||
{license_type} License
|
||
'''
|
||
(app_dir / "README.md").write_text(readme_content, encoding="utf-8")
|
||
|
||
pyproject_content = f'''[build-system]
|
||
requires = ["setuptools>=61.0", "wheel"]
|
||
build-backend = "setuptools.build_meta"
|
||
|
||
[project]
|
||
name = "{app_name}"
|
||
version = "0.0.1"
|
||
description = "{description}"
|
||
authors = [
|
||
{{name = "{publisher}", email = "{email}"}}
|
||
]
|
||
license = {{text = "{license_type}"}}
|
||
requires-python = ">=3.8"
|
||
|
||
dependencies = [
|
||
# Add your app-specific dependencies here
|
||
]
|
||
|
||
[project.optional-dependencies]
|
||
dev = [
|
||
"pytest>=7.0.0",
|
||
"black>=23.0.0",
|
||
]
|
||
'''
|
||
(app_dir / "pyproject.toml").write_text(pyproject_content, encoding="utf-8")
|
||
|
||
return {
|
||
"success": True,
|
||
"appName": app_name,
|
||
"appTitle": app_title,
|
||
"appPath": str(app_dir),
|
||
"backendPath": str(app_inner_dir),
|
||
"frontendPath": str(frontend_dir) if include_frontend else None
|
||
}
|
||
|
||
except Exception as e:
|
||
raise HTTPException(status_code=400, detail=str(e))
|
||
|
||
|