284 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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))