feat: 代码生成器 模板仓库
This commit is contained in:
parent
2fbdaedf21
commit
2bd36d1848
@ -44,6 +44,56 @@ export interface CodegenResponse {
|
||||
tableName: string;
|
||||
}
|
||||
|
||||
// 用户模板
|
||||
export interface UserTemplate {
|
||||
id?: number;
|
||||
userId?: number;
|
||||
templateName: string;
|
||||
templateGroup: string;
|
||||
templateContent: string;
|
||||
description?: string;
|
||||
isPublic: number; // 0-私有 1-公开
|
||||
version?: string;
|
||||
useCount?: number;
|
||||
status?: number; // 0-草稿 1-发布 2-禁用
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
createBy?: string;
|
||||
updateBy?: string;
|
||||
isOwner?: boolean;
|
||||
isPublicText?: string;
|
||||
statusText?: string;
|
||||
}
|
||||
|
||||
// 模板仓库配置
|
||||
export interface TemplateRepository {
|
||||
id?: number;
|
||||
userId?: number;
|
||||
templateSource: string; // system-系统模板 user-用户模板
|
||||
templateId: string;
|
||||
templateName: string;
|
||||
templateGroup: string;
|
||||
isEnabled: number; // 0-隐藏 1-显示
|
||||
sortOrder?: number;
|
||||
createTime?: string;
|
||||
}
|
||||
|
||||
// 分页查询参数
|
||||
export interface PageQuery {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
// 分页响应(MyBatis-Plus Page结构)
|
||||
export interface PageResult<T> {
|
||||
records: T[];
|
||||
total: number;
|
||||
current: number;
|
||||
size: number;
|
||||
pages?: number;
|
||||
searchCount?: boolean;
|
||||
}
|
||||
|
||||
// 获取所有模板
|
||||
export function getAllTemplatesApi() {
|
||||
return http.request<ResponseData<TemplateGroup[]>>(
|
||||
@ -62,3 +112,171 @@ export function generateCodeApi(data: CodegenRequest) {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// ============= 用户模板管理 API =============
|
||||
|
||||
// 分页查询用户模板列表
|
||||
export function getUserTemplateListApi(
|
||||
params: PageQuery & {
|
||||
templateGroup?: string;
|
||||
isPublic?: number;
|
||||
}
|
||||
) {
|
||||
return http.request<ResponseData<PageResult<UserTemplate>>>(
|
||||
"get",
|
||||
"/codegen/user-template/list",
|
||||
{ params }
|
||||
);
|
||||
}
|
||||
|
||||
// 查询公开模板列表
|
||||
export function getPublicTemplatesApi(templateGroup?: string) {
|
||||
return http.request<ResponseData<UserTemplate[]>>(
|
||||
"get",
|
||||
"/codegen/user-template/public",
|
||||
{ params: { templateGroup } }
|
||||
);
|
||||
}
|
||||
|
||||
// 根据ID查询用户模板详情
|
||||
export function getUserTemplateByIdApi(id: number) {
|
||||
return http.request<ResponseData<UserTemplate>>(
|
||||
"get",
|
||||
`/codegen/user-template/${id}`
|
||||
);
|
||||
}
|
||||
|
||||
// 新增用户模板
|
||||
export function addUserTemplateApi(data: UserTemplate) {
|
||||
return http.request<ResponseData<void>>("post", "/codegen/user-template", {
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
// 修改用户模板
|
||||
export function updateUserTemplateApi(data: UserTemplate) {
|
||||
return http.request<ResponseData<void>>(
|
||||
"post",
|
||||
"/codegen/user-template/update",
|
||||
{ data }
|
||||
);
|
||||
}
|
||||
|
||||
// 删除用户模板
|
||||
export function deleteUserTemplateApi(id: number) {
|
||||
return http.request<ResponseData<void>>(
|
||||
"post",
|
||||
"/codegen/user-template/delete",
|
||||
{ data: { id } }
|
||||
);
|
||||
}
|
||||
|
||||
// 批量删除用户模板
|
||||
export function deleteUserTemplatesBatchApi(ids: number[]) {
|
||||
return http.request<ResponseData<void>>(
|
||||
"post",
|
||||
"/codegen/user-template/delete/batch",
|
||||
{ data: { ids } }
|
||||
);
|
||||
}
|
||||
|
||||
// 复制模板
|
||||
export function copyTemplateApi(templateId: number, newName: string) {
|
||||
return http.request<ResponseData<void>>(
|
||||
"post",
|
||||
"/codegen/user-template/copy",
|
||||
{ data: { templateId, newName } }
|
||||
);
|
||||
}
|
||||
|
||||
// 验证模板语法
|
||||
export function validateTemplateApi(templateContent: string) {
|
||||
return http.request<ResponseData<string>>(
|
||||
"post",
|
||||
"/codegen/user-template/validate",
|
||||
{ data: templateContent }
|
||||
);
|
||||
}
|
||||
|
||||
// 检查模板名称是否存在
|
||||
export function checkTemplateNameApi(templateName: string, excludeId?: number) {
|
||||
return http.request<ResponseData<boolean>>(
|
||||
"get",
|
||||
"/codegen/user-template/check-name",
|
||||
{ params: { templateName, excludeId } }
|
||||
);
|
||||
}
|
||||
|
||||
// ============= 模板仓库管理 API =============
|
||||
|
||||
// 获取用户的模板仓库列表
|
||||
export function getUserRepositoryListApi() {
|
||||
return http.request<ResponseData<TemplateRepository[]>>(
|
||||
"get",
|
||||
"/codegen/repository/list"
|
||||
);
|
||||
}
|
||||
|
||||
// 获取所有可用模板
|
||||
export function getAvailableTemplatesApi() {
|
||||
return http.request<ResponseData<TemplateRepository[]>>(
|
||||
"get",
|
||||
"/codegen/repository/available"
|
||||
);
|
||||
}
|
||||
|
||||
// 添加模板到用户仓库
|
||||
export function addTemplateToRepositoryApi(data: TemplateRepository) {
|
||||
return http.request<ResponseData<void>>("post", "/codegen/repository/add", {
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
// 从用户仓库移除模板
|
||||
export function removeTemplateFromRepositoryApi(
|
||||
templateSource: string,
|
||||
templateId: string
|
||||
) {
|
||||
return http.request<ResponseData<void>>(
|
||||
"post",
|
||||
"/codegen/repository/remove",
|
||||
{ data: { templateSource, templateId } }
|
||||
);
|
||||
}
|
||||
|
||||
// 切换模板启用状态
|
||||
export function toggleTemplateStatusApi(
|
||||
templateSource: string,
|
||||
templateId: string,
|
||||
isEnabled: number
|
||||
) {
|
||||
return http.request<ResponseData<void>>(
|
||||
"post",
|
||||
"/codegen/repository/toggle",
|
||||
{ data: { templateSource, templateId, isEnabled } }
|
||||
);
|
||||
}
|
||||
|
||||
// 批量更新模板排序
|
||||
export function updateTemplateSortApi(templates: TemplateRepository[]) {
|
||||
return http.request<ResponseData<void>>("post", "/codegen/repository/sort", {
|
||||
data: templates
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化用户模板仓库
|
||||
export function initUserRepositoryApi() {
|
||||
return http.request<ResponseData<void>>("post", "/codegen/repository/init");
|
||||
}
|
||||
|
||||
// 检查模板是否已添加到用户仓库
|
||||
export function checkTemplateExistsApi(
|
||||
templateSource: string,
|
||||
templateId: string
|
||||
) {
|
||||
return http.request<ResponseData<boolean>>(
|
||||
"get",
|
||||
"/codegen/repository/check",
|
||||
{ params: { templateSource, templateId } }
|
||||
);
|
||||
}
|
||||
|
||||
638
src/views/system/codegen/components/CustomTemplateDialog.vue
Normal file
638
src/views/system/codegen/components/CustomTemplateDialog.vue
Normal file
@ -0,0 +1,638 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import {
|
||||
getUserTemplateListApi,
|
||||
addUserTemplateApi,
|
||||
updateUserTemplateApi,
|
||||
deleteUserTemplateApi,
|
||||
deleteUserTemplatesBatchApi,
|
||||
copyTemplateApi,
|
||||
validateTemplateApi,
|
||||
checkTemplateNameApi,
|
||||
type UserTemplate,
|
||||
type PageQuery,
|
||||
type PageResult
|
||||
} from "@/api/system/codegen";
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: "update:visible", visible: boolean): void;
|
||||
(e: "refresh"): void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emits = defineEmits<Emits>();
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false);
|
||||
const saveLoading = ref(false);
|
||||
const activeTab = ref("list"); // list | edit
|
||||
const tableData = ref<UserTemplate[]>([]);
|
||||
const multipleSelection = ref<UserTemplate[]>([]);
|
||||
const searchForm = reactive({
|
||||
templateGroup: "",
|
||||
isPublic: ""
|
||||
});
|
||||
|
||||
// 分页数据
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
});
|
||||
|
||||
// 编辑表单
|
||||
const editForm = reactive<UserTemplate>({
|
||||
id: undefined,
|
||||
templateName: "",
|
||||
templateGroup: "",
|
||||
templateContent: "",
|
||||
description: "",
|
||||
isPublic: 0,
|
||||
version: "1.0.0",
|
||||
status: 1
|
||||
});
|
||||
|
||||
const editFormRef = ref();
|
||||
const isEditMode = ref(false);
|
||||
|
||||
// 表单验证规则
|
||||
const editRules = {
|
||||
templateName: [
|
||||
{ required: true, message: "请输入模板名称", trigger: "blur" },
|
||||
{
|
||||
min: 2,
|
||||
max: 50,
|
||||
message: "模板名称长度为 2 到 50 个字符",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
templateGroup: [
|
||||
{ required: true, message: "请输入模板分组", trigger: "blur" },
|
||||
{
|
||||
min: 2,
|
||||
max: 30,
|
||||
message: "模板分组长度为 2 到 30 个字符",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
templateContent: [
|
||||
{ required: true, message: "请输入模板内容", trigger: "blur" },
|
||||
{ min: 10, message: "模板内容至少需要 10 个字符", trigger: "blur" }
|
||||
]
|
||||
};
|
||||
|
||||
// 计算属性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: val => emits("update:visible", val)
|
||||
});
|
||||
|
||||
// 分组选项
|
||||
const groupOptions = computed(() => {
|
||||
const groups = new Set(tableData.value.map(item => item.templateGroup));
|
||||
return Array.from(groups).map(group => ({ label: group, value: group }));
|
||||
});
|
||||
|
||||
// 加载模板列表
|
||||
const loadTableData = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const params: PageQuery & typeof searchForm = {
|
||||
pageNum: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
...searchForm
|
||||
};
|
||||
|
||||
const res = await getUserTemplateListApi(params);
|
||||
if (res.code === 200) {
|
||||
const data = res.data as PageResult<UserTemplate>;
|
||||
tableData.value = data.records;
|
||||
pagination.total = data.total;
|
||||
} else {
|
||||
ElMessage.error(res.msg || "加载失败");
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error("加载失败");
|
||||
console.error(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1;
|
||||
loadTableData();
|
||||
};
|
||||
|
||||
// 重置搜索
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, {
|
||||
templateGroup: "",
|
||||
isPublic: ""
|
||||
});
|
||||
pagination.current = 1;
|
||||
loadTableData();
|
||||
};
|
||||
|
||||
// 分页变化
|
||||
const handlePageChange = (page: number) => {
|
||||
pagination.current = page;
|
||||
loadTableData();
|
||||
};
|
||||
|
||||
const handleSizeChange = (size: number) => {
|
||||
pagination.pageSize = size;
|
||||
pagination.current = 1;
|
||||
loadTableData();
|
||||
};
|
||||
|
||||
// 多选变化
|
||||
const handleSelectionChange = (selection: UserTemplate[]) => {
|
||||
multipleSelection.value = selection;
|
||||
};
|
||||
|
||||
// 新增模板
|
||||
const handleAdd = () => {
|
||||
isEditMode.value = false;
|
||||
Object.assign(editForm, {
|
||||
id: undefined,
|
||||
templateName: "",
|
||||
templateGroup: "",
|
||||
templateContent: "",
|
||||
description: "",
|
||||
isPublic: 0,
|
||||
version: "1.0.0",
|
||||
status: 1
|
||||
});
|
||||
activeTab.value = "edit";
|
||||
};
|
||||
|
||||
// 编辑模板
|
||||
const handleEdit = (row: UserTemplate) => {
|
||||
isEditMode.value = true;
|
||||
Object.assign(editForm, row);
|
||||
activeTab.value = "edit";
|
||||
};
|
||||
|
||||
// 复制模板
|
||||
const handleCopy = async (row: UserTemplate) => {
|
||||
try {
|
||||
const { value: newName } = await ElMessageBox.prompt(
|
||||
"请输入新的模板名称",
|
||||
"复制模板",
|
||||
{
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
inputValue: `${row.templateName}_副本`,
|
||||
inputPattern: /^.{2,50}$/,
|
||||
inputErrorMessage: "模板名称长度为 2 到 50 个字符"
|
||||
}
|
||||
);
|
||||
|
||||
const res = await copyTemplateApi(row.id!, newName);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("复制成功");
|
||||
loadTableData();
|
||||
} else {
|
||||
ElMessage.error(res.msg || "复制失败");
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== "cancel") {
|
||||
ElMessage.error("复制失败");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 删除模板
|
||||
const handleDelete = async (row: UserTemplate) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除模板 "${row.templateName}" 吗?`,
|
||||
"确认删除",
|
||||
{ type: "warning" }
|
||||
);
|
||||
|
||||
const res = await deleteUserTemplateApi(row.id!);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("删除成功");
|
||||
loadTableData();
|
||||
emits("refresh");
|
||||
} else {
|
||||
ElMessage.error(res.msg || "删除失败");
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== "cancel") {
|
||||
ElMessage.error("删除失败");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 批量删除
|
||||
const handleBatchDelete = async () => {
|
||||
if (multipleSelection.value.length === 0) {
|
||||
ElMessage.warning("请选择要删除的模板");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除选中的 ${multipleSelection.value.length} 个模板吗?`,
|
||||
"确认删除",
|
||||
{ type: "warning" }
|
||||
);
|
||||
|
||||
const ids = multipleSelection.value.map(item => item.id!);
|
||||
const res = await deleteUserTemplatesBatchApi(ids);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("删除成功");
|
||||
multipleSelection.value = [];
|
||||
loadTableData();
|
||||
emits("refresh");
|
||||
} else {
|
||||
ElMessage.error(res.msg || "删除失败");
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== "cancel") {
|
||||
ElMessage.error("删除失败");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 保存模板
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
await editFormRef.value?.validate();
|
||||
|
||||
// 检查模板名称是否重复
|
||||
const nameExists = await checkTemplateNameApi(
|
||||
editForm.templateName,
|
||||
editForm.id
|
||||
);
|
||||
if (nameExists.data) {
|
||||
ElMessage.error("模板名称已存在");
|
||||
return;
|
||||
}
|
||||
|
||||
saveLoading.value = true;
|
||||
|
||||
const res = isEditMode.value
|
||||
? await updateUserTemplateApi(editForm)
|
||||
: await addUserTemplateApi(editForm);
|
||||
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(isEditMode.value ? "修改成功" : "新增成功");
|
||||
activeTab.value = "list";
|
||||
loadTableData();
|
||||
emits("refresh");
|
||||
} else {
|
||||
ElMessage.error(res.msg || "保存失败");
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error("保存失败");
|
||||
console.error(error);
|
||||
} finally {
|
||||
saveLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 验证模板语法
|
||||
const handleValidate = async () => {
|
||||
if (!editForm.templateContent.trim()) {
|
||||
ElMessage.warning("请先输入模板内容");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await validateTemplateApi(editForm.templateContent);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("模板语法验证通过");
|
||||
} else {
|
||||
ElMessage.error(`模板语法错误:${res.msg}`);
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error("验证失败");
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 返回列表
|
||||
const handleBack = () => {
|
||||
activeTab.value = "list";
|
||||
};
|
||||
|
||||
// 对话框关闭
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false;
|
||||
activeTab.value = "list";
|
||||
};
|
||||
|
||||
// 监听visible变化
|
||||
watch(
|
||||
() => props.visible,
|
||||
newVal => {
|
||||
if (newVal) {
|
||||
activeTab.value = "list";
|
||||
loadTableData();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="自定义模板管理"
|
||||
width="90%"
|
||||
:before-close="handleClose"
|
||||
destroy-on-close
|
||||
>
|
||||
<div class="custom-template-dialog">
|
||||
<!-- 标签页 -->
|
||||
<el-tabs v-model="activeTab" type="card">
|
||||
<!-- 模板列表 -->
|
||||
<el-tab-pane label="模板列表" name="list">
|
||||
<div class="template-list">
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-form mb-4">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="6">
|
||||
<el-select
|
||||
v-model="searchForm.templateGroup"
|
||||
placeholder="选择模板分组"
|
||||
clearable
|
||||
filterable
|
||||
>
|
||||
<el-option
|
||||
v-for="group in groupOptions"
|
||||
:key="group.value"
|
||||
:label="group.label"
|
||||
:value="group.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-select
|
||||
v-model="searchForm.isPublic"
|
||||
placeholder="选择公开状态"
|
||||
clearable
|
||||
>
|
||||
<el-option label="私有" :value="0" />
|
||||
<el-option label="公开" :value="1" />
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="search-actions">
|
||||
<el-button type="primary" @click="handleSearch"
|
||||
>搜索</el-button
|
||||
>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
<el-button type="success" @click="handleAdd"
|
||||
>新增模板</el-button
|
||||
>
|
||||
<el-button
|
||||
type="danger"
|
||||
:disabled="multipleSelection.length === 0"
|
||||
@click="handleBatchDelete"
|
||||
>
|
||||
批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tableData"
|
||||
@selection-change="handleSelectionChange"
|
||||
border
|
||||
stripe
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column
|
||||
prop="templateName"
|
||||
label="模板名称"
|
||||
min-width="150"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="templateGroup"
|
||||
label="模板分组"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="description"
|
||||
label="描述"
|
||||
min-width="200"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="isPublic" label="公开状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.isPublic === 1 ? 'success' : 'info'">
|
||||
{{ row.isPublic === 1 ? "公开" : "私有" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="useCount" label="使用次数" width="100" />
|
||||
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleEdit(row)"
|
||||
>编辑</el-button
|
||||
>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
@click="handleCopy(row)"
|
||||
>复制</el-button
|
||||
>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="handleDelete(row)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-wrapper mt-4">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.current"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 编辑模板 -->
|
||||
<el-tab-pane :label="isEditMode ? '编辑模板' : '新增模板'" name="edit">
|
||||
<div class="template-edit">
|
||||
<el-form
|
||||
ref="editFormRef"
|
||||
:model="editForm"
|
||||
:rules="editRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="模板名称" prop="templateName">
|
||||
<el-input
|
||||
v-model="editForm.templateName"
|
||||
placeholder="请输入模板名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="模板分组" prop="templateGroup">
|
||||
<el-input
|
||||
v-model="editForm.templateGroup"
|
||||
placeholder="请输入模板分组"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="公开状态">
|
||||
<el-radio-group v-model="editForm.isPublic">
|
||||
<el-radio :label="0">私有</el-radio>
|
||||
<el-radio :label="1">公开</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="版本号">
|
||||
<el-input
|
||||
v-model="editForm.version"
|
||||
placeholder="请输入版本号"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="模板描述">
|
||||
<el-input
|
||||
v-model="editForm.description"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入模板描述"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="模板内容" prop="templateContent">
|
||||
<div class="template-content-editor">
|
||||
<div class="editor-toolbar">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleValidate"
|
||||
>
|
||||
验证语法
|
||||
</el-button>
|
||||
<span class="ml-2 text-gray-500"
|
||||
>支持 FreeMarker 模板语法</span
|
||||
>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="editForm.templateContent"
|
||||
type="textarea"
|
||||
:rows="15"
|
||||
placeholder="请输入模板内容,支持 FreeMarker 语法..."
|
||||
class="mt-2"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="form-actions">
|
||||
<el-button @click="handleBack">返回</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="saveLoading"
|
||||
@click="handleSave"
|
||||
>
|
||||
{{ isEditMode ? "更新" : "保存" }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-template-dialog {
|
||||
.search-form {
|
||||
.search-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.template-edit {
|
||||
.template-content-editor {
|
||||
width: 100%;
|
||||
|
||||
.editor-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
padding-top: 24px;
|
||||
margin-top: 24px;
|
||||
border-top: 1px solid var(--el-border-color-light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__content) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
392
src/views/system/codegen/components/TemplateRepositoryDialog.vue
Normal file
392
src/views/system/codegen/components/TemplateRepositoryDialog.vue
Normal file
@ -0,0 +1,392 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, computed } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { Search } from "@element-plus/icons-vue";
|
||||
import {
|
||||
getAvailableTemplatesApi,
|
||||
getUserRepositoryListApi,
|
||||
addTemplateToRepositoryApi,
|
||||
removeTemplateFromRepositoryApi,
|
||||
toggleTemplateStatusApi,
|
||||
type TemplateRepository
|
||||
} from "@/api/system/codegen";
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: "update:visible", visible: boolean): void;
|
||||
(e: "refresh"): void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emits = defineEmits<Emits>();
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false);
|
||||
const availableTemplates = ref<TemplateRepository[]>([]);
|
||||
const userTemplates = ref<TemplateRepository[]>([]);
|
||||
const searchKeyword = ref("");
|
||||
|
||||
// 计算属性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: val => emits("update:visible", val)
|
||||
});
|
||||
|
||||
// 过滤后的可用模板
|
||||
const filteredAvailableTemplates = computed(() => {
|
||||
if (!searchKeyword.value) return availableTemplates.value;
|
||||
return availableTemplates.value.filter(
|
||||
template =>
|
||||
template.templateName
|
||||
.toLowerCase()
|
||||
.includes(searchKeyword.value.toLowerCase()) ||
|
||||
template.templateGroup
|
||||
.toLowerCase()
|
||||
.includes(searchKeyword.value.toLowerCase())
|
||||
);
|
||||
});
|
||||
|
||||
// 按分组组织模板
|
||||
const groupedTemplates = computed(() => {
|
||||
const groups: Record<string, TemplateRepository[]> = {};
|
||||
filteredAvailableTemplates.value.forEach(template => {
|
||||
if (!groups[template.templateGroup]) {
|
||||
groups[template.templateGroup] = [];
|
||||
}
|
||||
groups[template.templateGroup].push(template);
|
||||
});
|
||||
return groups;
|
||||
});
|
||||
|
||||
// 加载数据
|
||||
const loadData = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const [availableRes, userRes] = await Promise.all([
|
||||
getAvailableTemplatesApi(),
|
||||
getUserRepositoryListApi()
|
||||
]);
|
||||
|
||||
if (availableRes.code === 200) {
|
||||
availableTemplates.value = availableRes.data;
|
||||
}
|
||||
|
||||
if (userRes.code === 200) {
|
||||
userTemplates.value = userRes.data;
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error("加载模板数据失败");
|
||||
console.error(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 添加模板到仓库
|
||||
const addTemplate = async (template: TemplateRepository) => {
|
||||
try {
|
||||
const res = await addTemplateToRepositoryApi(template);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("添加成功");
|
||||
template.isEnabled = 1;
|
||||
await loadData();
|
||||
emits("refresh");
|
||||
} else {
|
||||
ElMessage.error(res.msg || "添加失败");
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error("添加失败");
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 从仓库移除模板
|
||||
const removeTemplate = async (template: TemplateRepository) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要从仓库中移除模板 "${template.templateName}" 吗?`,
|
||||
"确认移除",
|
||||
{ type: "warning" }
|
||||
);
|
||||
|
||||
const res = await removeTemplateFromRepositoryApi(
|
||||
template.templateSource,
|
||||
template.templateId
|
||||
);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("移除成功");
|
||||
template.isEnabled = 0;
|
||||
await loadData();
|
||||
emits("refresh");
|
||||
} else {
|
||||
ElMessage.error(res.msg || "移除失败");
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== "cancel") {
|
||||
ElMessage.error("移除失败");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 切换模板状态
|
||||
const toggleTemplate = async (template: TemplateRepository) => {
|
||||
const newStatus = template.isEnabled === 1 ? 0 : 1;
|
||||
|
||||
try {
|
||||
const res = await toggleTemplateStatusApi(
|
||||
template.templateSource,
|
||||
template.templateId,
|
||||
newStatus
|
||||
);
|
||||
if (res.code === 200) {
|
||||
template.isEnabled = newStatus;
|
||||
ElMessage.success(newStatus === 1 ? "已启用" : "已禁用");
|
||||
emits("refresh");
|
||||
} else {
|
||||
ElMessage.error(res.msg || "操作失败");
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error("操作失败");
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
// 对话框关闭时
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
// 组件挂载时加载数据
|
||||
onMounted(() => {
|
||||
if (props.visible) {
|
||||
loadData();
|
||||
}
|
||||
});
|
||||
|
||||
// 监听visible变化
|
||||
watch(
|
||||
() => props.visible,
|
||||
newVal => {
|
||||
if (newVal) {
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="模板仓库管理"
|
||||
width="80%"
|
||||
:before-close="handleClose"
|
||||
destroy-on-close
|
||||
>
|
||||
<div class="repository-dialog">
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-bar mb-4">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索模板名称或分组"
|
||||
clearable
|
||||
class="w-64"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-loading="loading" class="min-h-64">
|
||||
<!-- 模板列表 -->
|
||||
<div
|
||||
v-if="!loading && Object.keys(groupedTemplates).length > 0"
|
||||
class="template-groups"
|
||||
>
|
||||
<div
|
||||
v-for="(templates, groupName) in groupedTemplates"
|
||||
:key="groupName"
|
||||
class="template-group mb-6"
|
||||
>
|
||||
<div class="group-header mb-3">
|
||||
<el-tag type="primary" size="large" effect="dark">
|
||||
{{ groupName }}
|
||||
</el-tag>
|
||||
<span class="ml-2 text-gray-500"
|
||||
>{{ templates.length }} 个模板</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="template-grid">
|
||||
<div
|
||||
v-for="template in templates"
|
||||
:key="`${template.templateSource}-${template.templateId}`"
|
||||
class="template-item"
|
||||
:class="{ enabled: template.isEnabled === 1 }"
|
||||
>
|
||||
<div class="template-content">
|
||||
<div class="template-header">
|
||||
<h4 class="template-name">{{ template.templateName }}</h4>
|
||||
<div class="template-badges">
|
||||
<el-tag
|
||||
:type="
|
||||
template.templateSource === 'system'
|
||||
? 'success'
|
||||
: 'info'
|
||||
"
|
||||
size="small"
|
||||
>
|
||||
{{
|
||||
template.templateSource === "system" ? "系统" : "用户"
|
||||
}}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-if="template.isEnabled === 1"
|
||||
type="primary"
|
||||
size="small"
|
||||
>
|
||||
已启用
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="template-actions">
|
||||
<el-button
|
||||
v-if="template.isEnabled === 0"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="addTemplate(template)"
|
||||
>
|
||||
添加到仓库
|
||||
</el-button>
|
||||
|
||||
<template v-else>
|
||||
<el-button
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="toggleTemplate(template)"
|
||||
>
|
||||
{{ template.isEnabled === 1 ? "禁用" : "启用" }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="removeTemplate(template)"
|
||||
>
|
||||
移除
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="!loading" class="empty-state">
|
||||
<el-empty description="暂无可用模板" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">关闭</el-button>
|
||||
<el-button type="primary" @click="loadData">刷新</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.repository-dialog {
|
||||
.search-bar {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.template-groups {
|
||||
.template-group {
|
||||
.group-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.template-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 16px;
|
||||
|
||||
.template-item {
|
||||
padding: 16px;
|
||||
background: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
&.enabled {
|
||||
background: var(--el-color-primary-light-9);
|
||||
border-color: var(--el-color-primary-light-7);
|
||||
}
|
||||
|
||||
.template-content {
|
||||
.template-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.template-name {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
margin-right: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.template-badges {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.template-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
@ -5,6 +5,8 @@ import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import TemplateSelector from "./components/TemplateSelector.vue";
|
||||
import ConfigForm from "./components/ConfigForm.vue";
|
||||
import CodeEditor from "./components/CodeEditor.vue";
|
||||
import TemplateRepositoryDialog from "./components/TemplateRepositoryDialog.vue";
|
||||
import CustomTemplateDialog from "./components/CustomTemplateDialog.vue";
|
||||
|
||||
import Play from "@iconify-icons/ep/caret-right";
|
||||
import CopyDocument from "@iconify-icons/ep/document-copy";
|
||||
@ -45,6 +47,20 @@ const sqlCollapsed = ref(false);
|
||||
const configCollapsed = ref(false);
|
||||
const templateCollapsed = ref(false);
|
||||
|
||||
// 弹窗状态
|
||||
const templateRepositoryVisible = ref(false);
|
||||
const customTemplateVisible = ref(false);
|
||||
|
||||
// 打开模板仓库
|
||||
const openTemplateRepository = () => {
|
||||
templateRepositoryVisible.value = true;
|
||||
};
|
||||
|
||||
// 打开自定义模板
|
||||
const openCustomTemplate = () => {
|
||||
customTemplateVisible.value = true;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadAllTemplates();
|
||||
});
|
||||
@ -177,19 +193,37 @@ onMounted(() => {
|
||||
<!-- 模板选择区域 -->
|
||||
<el-card class="mb-4 collapsible-card" shadow="hover">
|
||||
<template #header>
|
||||
<div
|
||||
class="collapsible-header"
|
||||
@click="templateCollapsed = !templateCollapsed"
|
||||
>
|
||||
<h3 class="text-lg font-semibold">
|
||||
模板选择
|
||||
<el-icon
|
||||
class="collapse-arrow"
|
||||
:class="{ collapsed: templateCollapsed }"
|
||||
<div class="flex justify-between items-center">
|
||||
<div
|
||||
class="collapsible-header"
|
||||
@click="templateCollapsed = !templateCollapsed"
|
||||
>
|
||||
<h3 class="text-lg font-semibold">
|
||||
模板选择
|
||||
<el-icon
|
||||
class="collapse-arrow"
|
||||
:class="{ collapsed: templateCollapsed }"
|
||||
>
|
||||
<component :is="useRenderIcon(ArrowDown)" />
|
||||
</el-icon>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<el-button
|
||||
type="default"
|
||||
:icon="useRenderIcon(FolderOpened)"
|
||||
@click="openTemplateRepository"
|
||||
>
|
||||
<component :is="useRenderIcon(ArrowDown)" />
|
||||
</el-icon>
|
||||
</h3>
|
||||
模板仓库
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon(CopyDocument)"
|
||||
@click="openCustomTemplate"
|
||||
>
|
||||
自定义模板
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -248,6 +282,18 @@ onMounted(() => {
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 模板仓库弹窗 -->
|
||||
<TemplateRepositoryDialog
|
||||
v-model:visible="templateRepositoryVisible"
|
||||
@refresh="loadAllTemplates"
|
||||
/>
|
||||
|
||||
<!-- 自定义模板弹窗 -->
|
||||
<CustomTemplateDialog
|
||||
v-model:visible="customTemplateVisible"
|
||||
@refresh="loadAllTemplates"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user