前面使用 Admin.Core 的代码生成器生成了通用代码生成器的基础模块 分组,模板,项目,项目模型,项目字段的基础功能,本篇继续完善,实现最核心的模板生成功能,并提供生成预览及代码文件压缩下载
首先清楚几个模块的关系,如何使用,简单画一个流程图
前面完成了基础的模板组,模板管理,项目,模型,字段管理,都是由 Admin.Core 框架的代码生成器完成,感兴趣的可参考前篇使用,文末也会给出仓库地址,有问题欢迎交流
本文主要分享项目代码的生成,先放出效果图
需要实现上面效果的代码生成器,基础的增删改查都可以借由代码生成器生成,也写过几篇了这里就不再赘述,我们只需要关注核心部分:如何生成?如何预览?如何下载?
当我们有了一个模板(模板内容),也有了对应的配置项(项目&项目模型&项目字段),中间肯定是需要一个模板引擎来将模板根据配置项解析出来的。
市面上有很多模板引擎,比如 ejs,art 等,但既然是搞C#的,那不妨试试看 Razor (之前的Admin.Core代码生成器也是基于razor模板引擎,也可以换其他的或者支持多种模板引擎),C#的语法写起来还是挺舒服的,并且其实还可以新建一个.net core 的项目添加页面后可以复制模板和模型到页面为自己的模板增加智能提示
项目中引用包:RazorEngine.NetCore
<ItemGroup>
<PackageReference Include="RazorEngine.NetCore" Version="3.1.0" />
</ItemGroup>
var code="模板内容";
var key="模板名";
//模型名称
var model=new DevProjectRazorRenderModel();
RazorEngine.Engine.Razor.RunCompile(new LoadedTemplateSource(code), key, model.GetType(), model);
模板内容的写法
@{
var gen = Model as ZhonTai.Module.Dev.DevProjectRazorRenderModel;
}
//模型渲染
var gen = new DevProjectRazorRenderModel()
{
Project = Mapper.Map<DevProjectGetOutput>(project),
Model = Mapper.Map<DevProjectModelGetOutput>(model),
Fields = Mapper.Map<List<DevProjectModelFieldGetOutput>>(modelFields),
};
var pathCodeText = @"
@{
var gen = Model as ZhonTai.Module.Dev.DevProjectRazorRenderModel;
}
" + outPath;
//转换路径
var outPath = RazorCompile(gen, $"{project.Code}_{model.Code}_{tpl.Name}_Path.tpl", pathCodeText).Trim();
文末附完整代码
/// <summary>
/// 下载
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost]
public async Task<ActionResult> DownAsync(DevProjectGenGenerateInput input)
{
var path = Path.Combine(AppContext.BaseDirectory, "DownCodes", DateTime.Now.ToString("yyyyMMddHHmmss"));
var zipFileName = $"源码{DateTime.Now.ToString("yyyyMMddHHmmss")}.zip";
var zipPath = Path.Combine(AppContext.BaseDirectory, "DownCodes", zipFileName);
try
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
//获取内容信息
var codes = await GenerateAsync(input);
foreach (var code in codes)
{
var codePath = Path.Combine(path, code.Path);
var directory = Path.GetDirectoryName(codePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
if (!File.Exists(codePath))
{
using (var fs = File.Open(codePath, FileMode.Create, FileAccess.ReadWrite))
{
await fs.WriteAsync(Encoding.UTF8.GetBytes(code.Content));
}
}
}
ZipFile.CreateFromDirectory(path, zipPath);
var bytes = await File.ReadAllBytesAsync(zipPath);
return new FileContentResult(bytes, "application/zip")
{
FileDownloadName = zipFileName
};
}
finally
{
if (Directory.Exists(path))
{
Directory.Delete(path, true);
}
if (File.Exists(zipPath))
{
File.Delete(zipPath);
}
}
}
前端对应需要支持文件下载,可以修改为生成对应下载URL,用GET请求直接打开新窗口进行下载
整个框架主要使用者肯定是.net开发,如果没有写过 vue 项目,写起来的时候可能会有一些吃力,但因为现在有了代码生成器,大部分代码都可以生成,也可以做参考,所以这里只做一些关键点的说明
有时间的话至少过一遍再开始,磨刀不误砍柴工
生命周期的一定花时间看看:官方文档
以项目生成页面调整到预览页面为例
//引入路由
import { useRoute, useRouter } from 'vue-router'
//路由信息
const route = useRoute()
//路由跳转
const router = useRouter()
router.push({
path: '/dev/dev-project-gen/preview', query: {
projectId: row.projectId,
groupIds: row.groupIds_Values
}
})
//从路由中获取query参数
onMounted(() => {
state.filter.projectId = route.query.projectId
state.filter.groupIds = route.query.groupIds
})
将左侧的列表列封装为一个组件,右侧如果简单可封装,也可以直接写到预览页面
这里参考框架中用户列表做的
<my-layout class="my-layout">
<pane size="30" min-size="20" max-size="35">
<div class="my-flex-column w100 h100">
<group-template-menu :groupIds="state.filter.groupIds" :projectId="state.filter.projectId"
@node-click="onNodeClick" select-first-node></group-template-menu>
</div>
</pane>
<pane size="70" v-loading="state.loading">
<div class="my-flex-column w100 h100">
内容预览部分
</div>
</pane>
</my-layout>
当然,封装了组件记得引入用到的组件
const GroupTemplateMenu = defineAsyncComponent(() => import('./components/dev-group-template-menu.vue'))
const MyLayout = defineAsyncComponent(() => import('/@/components/my-layout/index.vue'))
组件中获取传递的参数示例
interface Props {
modelValue: number[] | null | undefined
selectFirstNode: boolean,
projectId: number,
groupIds: number[]
}
const props = withDefaults(defineProps<Props>(), {
modelValue: () => [],
selectFirstNode: false,
projectId: 0,
groupIds: () => []
})
在预览左侧菜单中,我们可以看到一个标记的小图标,用来直接编辑模板
这个弹窗其实是直接引用了编辑模板的组件
<template>
...
<dev-template-form ref="devTemplateFormRef" :title="'编辑模板'"></dev-template-form>
...
</template>
<script>
...
// 引入组件
const DevTemplateForm = defineAsyncComponent(() => import('../../dev-template/components/dev-template-form.vue'))
//使用
const devTemplateFormRef = ref()
const editTemplate = (node, data) => {
devTemplateFormRef.value.open({
id: data.id
})
}
...
</script>
defineExpose({
open,
})
如上可以看到我们使用和组件同名的 const devTemplateFormRef = ref() 即可获取到组件引用
另外调用的方法可以查看 dev-template-form.vue 是开放了方法的
defineExpose({
open,
})
首先需要后端返回文件流,然后调用对应接口的时候指定format格式为blob,并创建下载连接点击即可
const genCode = async (row: DevProjectGenGetOutput) => {
new DevProjectGenApi().down({ projectId: row.projectId, groupIds: row.groupIds_Values?.map(s => Number(s)) }, {
loading: false,
showErrorMessage: false,
format: 'blob'
})
.then((res) => {
const a = document.createElement('a');
a.href = URL.createObjectURL(res as Blob);
a.download = '源码.zip';
a.click();
});
}
本文所有代码皆在 yimogit/Emo.Dev 仓库中可以找到,觉得有用的来个 Star 吧
基于前面代码生成器生成的功能模块上,周末花了两天完善项目生成,终于算是搞定
后面还需要逐步完善生成器,欢迎点个赞,留个言,交流指点一二