refactor: 适配 3.0 前端代码生成模板,代码预览及生成

This commit is contained in:
Charles7c 2024-04-19 22:16:38 +08:00
parent be7e806533
commit 3dbe72fd57
19 changed files with 452 additions and 646 deletions

View File

@ -16,6 +16,7 @@
package top.charles7c.continew.admin.generator.config.properties;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.map.MapUtil;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ -71,5 +72,15 @@ public class GeneratorProperties {
* 排除字段
*/
private String[] excludeFields;
/**
* 扩展名
*/
private String extension = FileNameUtil.EXT_JAVA;
/**
* 是否为后端模板
*/
private boolean backend = true;
}
}

View File

@ -21,7 +21,6 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
@ -227,7 +226,7 @@ public class GeneratorServiceImpl implements GeneratorService {
.lastIndexOfIgnoreCase(packageName, StringConstants.DOT) + 1);
genConfigMap.put("apiModuleName", apiModuleName);
genConfigMap.put("apiName", StrUtil.lowerFirst(genConfig.getClassNamePrefix()));
// 渲染后端代码
// 渲染代码
String classNamePrefix = genConfig.getClassNamePrefix();
Map<String, TemplateConfig> templateConfigMap = generatorProperties.getTemplateConfigs();
for (Map.Entry<String, TemplateConfig> templateConfigEntry : templateConfigMap.entrySet()) {
@ -235,27 +234,22 @@ public class GeneratorServiceImpl implements GeneratorService {
String className = classNamePrefix + StrUtil.nullToEmpty(templateConfigEntry.getKey());
genConfigMap.put("className", className);
TemplateConfig templateConfig = templateConfigEntry.getValue();
String content = TemplateUtils.render(templateConfig.getTemplatePath(), genConfigMap);
boolean isBackend = templateConfig.isBackend();
String extension = templateConfig.getExtension();
GeneratePreviewResp generatePreview = new GeneratePreviewResp();
generatePreview.setFileName(className + FileNameUtil.EXT_JAVA);
generatePreview.setContent(content);
generatePreview.setBackend(true);
generatePreview.setBackend(isBackend);
generatePreviewList.add(generatePreview);
if (isBackend) {
generatePreview.setFileName(className + extension);
generatePreview.setContent(TemplateUtils.render(templateConfig.getTemplatePath(), genConfigMap));
} else {
generatePreview.setFileName(".vue".equals(extension) && "index".equals(templateConfigEntry.getKey())
? "index.vue"
: this.getFrontendFileName(classNamePrefix, className, extension));
genConfigMap.put("fieldConfigs", fieldConfigList);
generatePreview.setContent(TemplateUtils.render(templateConfig.getTemplatePath(), genConfigMap));
}
}
// 渲染前端代码
// api 代码
genConfigMap.put("fieldConfigs", fieldConfigList);
String apiContent = TemplateUtils.render("api.ftl", genConfigMap);
GeneratePreviewResp apiGeneratePreview = new GeneratePreviewResp();
apiGeneratePreview.setFileName(classNamePrefix.toLowerCase() + ".ts");
apiGeneratePreview.setContent(apiContent);
generatePreviewList.add(apiGeneratePreview);
// view 代码
String viewContent = TemplateUtils.render("index.ftl", genConfigMap);
GeneratePreviewResp viewGeneratePreview = new GeneratePreviewResp();
viewGeneratePreview.setFileName("index.vue");
viewGeneratePreview.setContent(viewContent);
generatePreviewList.add(viewGeneratePreview);
return generatePreviewList;
}
@ -265,45 +259,12 @@ public class GeneratorServiceImpl implements GeneratorService {
String tempDir = SystemUtil.getUserInfo().getTempDir();
// 删除旧代码
FileUtil.del(tempDir + projectProperties.getAppName());
tableNames.forEach(tableName -> {
// 初始化配置及数据
List<GeneratePreviewResp> generatePreviewList = this.preview(tableName);
GenConfigDO genConfig = genConfigMapper.selectById(tableName);
// 生成后端代码
Map<Boolean, List<GeneratePreviewResp>> generatePreviewListMap = generatePreviewList.stream()
.collect(Collectors.groupingBy(GeneratePreviewResp::isBackend));
this.generateBackendCode(generatePreviewListMap.get(true), genConfig);
// 生成前端代码
List<GeneratePreviewResp> frontendGeneratePreviewList = generatePreviewListMap.get(false);
String packageName = genConfig.getPackageName();
String moduleName = StrUtil.subSuf(packageName, StrUtil
.lastIndexOfIgnoreCase(packageName, StringConstants.DOT) + 1);
// 例如continew-admin-ui/src
String frontendBasicPackagePath = tempDir + String.join(File.separator, projectProperties
.getAppName(), projectProperties.getAppName() + "-ui", "src");
// 1生成 api 代码
GeneratePreviewResp apiGeneratePreview = frontendGeneratePreviewList.get(0);
// 例如continew-admin-ui/src/src/api/system
String apiPath = String.join(File.separator, frontendBasicPackagePath, "api", moduleName);
// 例如continew-admin-ui/src/api/system/user.ts
File apiFile = new File(apiPath, apiGeneratePreview.getFileName());
if (!apiFile.exists() || Boolean.TRUE.equals(genConfig.getIsOverride())) {
FileUtil.writeUtf8String(apiGeneratePreview.getContent(), apiFile);
}
// 2生成 view 代码
GeneratePreviewResp viewGeneratePreview = frontendGeneratePreviewList.get(1);
// 例如continew-admin-ui/src/views/system
String vuePath = String.join(File.separator, frontendBasicPackagePath, "views", moduleName, StrUtil
.lowerFirst(genConfig.getClassNamePrefix()));
// 例如continew-admin-ui/src/views/system/user/index.vue
File vueFile = new File(vuePath, viewGeneratePreview.getFileName());
if (!vueFile.exists() || Boolean.TRUE.equals(genConfig.getIsOverride())) {
FileUtil.writeUtf8String(viewGeneratePreview.getContent(), vueFile);
}
// 生成代码
this.generateCode(generatePreviewList, genConfigMapper.selectById(tableName));
});
// 打包下载
File tempDirFile = new File(tempDir, projectProperties.getAppName());
String zipFilePath = tempDirFile.getPath() + jodd.io.ZipUtil.ZIP_EXT;
@ -316,27 +277,46 @@ public class GeneratorServiceImpl implements GeneratorService {
}
/**
* 生成后端代码
* 生成代码
*
* @param generatePreviewList 生成预览列表
* @param genConfig 生成配置
*/
private void generateBackendCode(List<GeneratePreviewResp> generatePreviewList, GenConfigDO genConfig) {
private void generateCode(List<GeneratePreviewResp> generatePreviewList, GenConfigDO genConfig) {
// 获取前后端基础路径
String backendBasicPackagePath = this.buildBackendBasicPackagePath(genConfig);
String frontendBasicPackagePath = SystemUtil.getUserInfo().getTempDir() + String
.join(File.separator, projectProperties.getAppName(), projectProperties.getAppName() + "-ui");
String packageName = genConfig.getPackageName();
String moduleName = StrUtil.subSuf(packageName, StrUtil
.lastIndexOfIgnoreCase(packageName, StringConstants.DOT) + 1);
// 生成代码
Map<String, TemplateConfig> templateConfigMap = generatorProperties.getTemplateConfigs();
for (GeneratePreviewResp generatePreview : generatePreviewList) {
// 获取对应模板配置
TemplateConfig templateConfig = templateConfigMap.get(generatePreview.getFileName()
.replace(genConfig.getClassNamePrefix(), StringConstants.EMPTY)
.replace(FileNameUtil.EXT_JAVA, StringConstants.EMPTY));
// 例如continew-admin/continew-admin-system/src/main/java/top/charles7c/continew/admin/system/service/impl
String packagePath = String.join(File.separator, backendBasicPackagePath, templateConfig.getPackageName()
.replace(StringConstants.DOT, File.separator));
// 例如continew-admin/continew-admin-system/src/main/java/top/charles7c/continew/admin/system/service/impl/XxxServiceImpl.java
File classFile = new File(packagePath, generatePreview.getFileName());
TemplateConfig templateConfig = templateConfigMap.getOrDefault(StrUtil.subBefore(generatePreview
.getFileName(), StringConstants.DOT, true)
.replace(genConfig.getClassNamePrefix(), StringConstants.EMPTY), templateConfigMap.get("api"));
String packagePath;
if (generatePreview.isBackend()) {
// 例如continew-admin/continew-system/src/main/java/top/continew/admin/system/service/impl
packagePath = String.join(File.separator, backendBasicPackagePath, templateConfig.getPackageName()
.replace(StringConstants.DOT, File.separator));
} else {
// 例如continew-admin/continew-admin-ui/src/views/system
packagePath = String.join(File.separator, frontendBasicPackagePath, templateConfig.getPackageName()
.replace(StringConstants.SLASH, File.separator), moduleName);
// 例如continew-admin/continew-admin-ui/src/views/system/user
packagePath = ".vue".equals(templateConfig.getExtension())
? packagePath + File.separator + StrUtil.lowerFirst(genConfig.getClassNamePrefix())
: packagePath;
}
// 后端continew-admin/continew-system/src/main/java/top/continew/admin/system/service/impl/XxxServiceImpl.java
// 前端continew-admin/continew-admin-ui/src/views/system/user/index.vue
File file = new File(packagePath, generatePreview.getFileName());
// 如果已经存在且不允许覆盖则跳过
if (!classFile.exists() || Boolean.TRUE.equals(genConfig.getIsOverride())) {
FileUtil.writeUtf8String(generatePreview.getContent(), classFile);
if (!file.exists() || Boolean.TRUE.equals(genConfig.getIsOverride())) {
FileUtil.writeUtf8String(generatePreview.getContent(), file);
}
}
}
@ -348,13 +328,25 @@ public class GeneratorServiceImpl implements GeneratorService {
* @return 后端包路径
*/
private String buildBackendBasicPackagePath(GenConfigDO genConfig) {
// 例如continew-admin/continew-admin-system/src/main/java/top/charles7c/continew/admin/system
// 例如continew-admin/continew-system/src/main/java/top/continew/admin/system
return SystemUtil.getUserInfo().getTempDir() + String.join(File.separator, projectProperties
.getAppName(), projectProperties.getAppName(), genConfig.getModuleName(), "src", "main", "java", genConfig
.getPackageName()
.replace(StringConstants.DOT, File.separator));
}
/**
* 获取前端文件名
*
* @param classNamePrefix 类名前缀
* @param className 类名
* @param extension 扩展名
* @return 前端文件名
*/
private String getFrontendFileName(String classNamePrefix, String className, String extension) {
return (".ts".equals(extension) ? StrUtil.lowerFirst(classNamePrefix) : className) + extension;
}
/**
* 预处理生成配置
*

View File

@ -1,57 +0,0 @@
import axios from 'axios';
import qs from 'query-string';
const BASE_URL = '/${apiModuleName}/${apiName}';
export interface DataRecord {
<#if fieldConfigs??>
<#list fieldConfigs as fieldConfig>
${fieldConfig.fieldName}?: string;
</#list>
createUserString?: string;
updateUserString?: string;
</#if>
}
export interface ListParam {
<#if fieldConfigs??>
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
${fieldConfig.fieldName}?: string;
</#if>
</#list>
</#if>
page?: number;
size?: number;
sort?: Array<string>;
}
export interface ListRes {
list: DataRecord[];
total: number;
}
export function list(params: ListParam) {
return axios.get<ListRes>(`${'$'}{BASE_URL}`, {
params,
paramsSerializer: (obj) => {
return qs.stringify(obj);
},
});
}
export function get(id: string) {
return axios.get<DataRecord>(`${'$'}{BASE_URL}/${'$'}{id}`);
}
export function add(req: DataRecord) {
return axios.post(BASE_URL, req);
}
export function update(req: DataRecord, id: string) {
return axios.put(`${'$'}{BASE_URL}/${'$'}{id}`, req);
}
export function del(ids: string | Array<string>) {
return axios.delete(`${'$'}{BASE_URL}/${'$'}{ids}`);
}

View File

@ -0,0 +1,103 @@
<template>
<a-modal
v-model:visible="visible"
:title="title"
:mask-closable="false"
:esc-to-close="false"
:modal-style="{ maxWidth: '520px' }"
width="90%"
@before-ok="save"
@close="reset"
>
<GiForm ref="formRef" v-model="form" :options="options" :columns="columns" />
</a-modal>
</template>
<script setup lang="ts">
import { get${classNamePrefix}, add${classNamePrefix}, update${classNamePrefix} } from '@/apis'
import { Message } from '@arco-design/web-vue'
import { GiForm, type Columns } from '@/components/GiForm'
import { useForm } from '@/hooks'
const dataId = ref('')
const isUpdate = computed(() => !!dataId.value)
const title = computed(() => (isUpdate.value ? '修改${businessName}' : '新增${businessName}'))
const formRef = ref<InstanceType<typeof GiForm>>()
const options: Options = {
form: {},
col: { xs: 24, sm: 24, md: 24, lg: 24, xl: 24, xxl: 24 },
btns: { hide: true }
}
const columns: Columns = [
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInForm>
{
label: '${fieldConfig.comment}',
field: '${fieldConfig.fieldName}',
<#if fieldConfig.formType = 'TEXT'>
type: 'input',
<#elseif fieldConfig.formType = 'TEXT_AREA'>
type: 'textarea',
</#if>
<#if fieldConfig.isRequired>
rules: [{ required: true, message: '请输入${fieldConfig.comment}' }]
</#if>
},
</#if>
</#list>
]
const { form, resetForm } = useForm<({
// todo 待补充
})
// 重置
const reset = () => {
formRef.value?.formRef?.resetFields()
resetForm()
}
const visible = ref(false)
// 新增
const onAdd = () => {
reset()
dataId.value = ''
visible.value = true
}
// 修改
const onUpdate = async (id: string) => {
reset()
dataId.value = id
const res = await get${classNamePrefix}(id)
Object.assign(form, res.data)
visible.value = true
}
// 保存
const save = async () => {
try {
const isInvalid = await formRef.value?.formRef?.validate()
if (isInvalid) return false
if (isUpdate.value) {
await update${classNamePrefix}(form, dataId.value)
Message.success('修改成功')
} else {
await add${classNamePrefix}(form)
Message.success('新增成功')
}
emit('save-success')
return true
} catch (error) {
return false
}
}
const emit = defineEmits<{
(e: 'save-success'): void
}>()
defineExpose({ onAdd, onUpdate })
</script>

View File

@ -0,0 +1,41 @@
<template>
<a-drawer v-model:visible="visible" title="${businessName}详情" :width="width >= 580 ? 580 : '100%'" :footer="false">
<a-descriptions :column="2" size="large" class="general-description">
<#list fieldConfigs as fieldConfig>
<a-descriptions-item label="${fieldConfig.comment}">{{ dataDetail?.${fieldConfig.fieldName} }}</a-descriptions-item>
<#if fieldConfig.fieldName = 'createUser'>
<a-descriptions-item label="创建人">{{ dataDetail?.createUserString }}</a-descriptions-item>
<#elseif fieldConfig.fieldName = 'updateUser'>
<a-descriptions-item label="修改人">{{ dataDetail?.updateUserString }}</a-descriptions-item>
</#if>
</#list>
</a-descriptions>
</a-drawer>
</template>
<script lang="ts" setup>
import { get${classNamePrefix}, type ${classNamePrefix}DetailResp } from '@/apis'
import { useWindowSize } from '@vueuse/core'
const { width } = useWindowSize()
const visible = ref(false)
const dataId = ref('')
const dataDetail = ref<${classNamePrefix}DetailResp>()
// 查询详情
const getDataDetail = async () => {
const res = await get${classNamePrefix}(dataId.value)
dataDetail.value = res.data
}
// 打开详情
const onDetail = async (id: string) => {
dataId.value = id
await getDataDetail()
visible.value = true
}
defineExpose({ onDetail })
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,58 @@
import http from '@/utils/http'
const BASE_URL = '/${apiModuleName}/${apiName}'
export interface ${classNamePrefix}Resp {
<#if fieldConfigs??>
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInList>
${fieldConfig.fieldName}: string
</#if>
</#list>
createUserString: string
updateUserString: string
</#if>
}
export interface ${classNamePrefix}DetailResp {
<#if fieldConfigs??>
<#list fieldConfigs as fieldConfig>
${fieldConfig.fieldName}: string
</#list>
createUserString: string
updateUserString: string
</#if>
}
export interface ${classNamePrefix}Query extends PageQuery {
<#if fieldConfigs??>
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
${fieldConfig.fieldName}: string
</#if>
</#list>
</#if>
}
/** @desc 查询${businessName}列表 */
export function list${classNamePrefix}(query: ${classNamePrefix}Query) {
return http.get<PageRes<${classNamePrefix}Resp[]>>(`${'$'}{BASE_URL}`, query)
}
/** @desc 查询${businessName}详情 */
export function get${classNamePrefix}(id: string) {
return http.get<${classNamePrefix}DetailResp>(`${'$'}{BASE_URL}/${'$'}{id}`)
}
/** @desc 新增${businessName} */
export function add${classNamePrefix}(data: any) {
return http.post(`${'$'}{BASE_URL}`, data)
}
/** @desc 修改${businessName} */
export function update${classNamePrefix}(data: any, id: string) {
return http.put(`${'$'}{BASE_URL}/${'$'}{id}`, data)
}
/** @desc 删除${businessName} */
export function delete${classNamePrefix}(id: string) {
return http.del(`${'$'}{BASE_URL}/${'$'}{id}`)
}

View File

@ -0,0 +1,148 @@
<template>
<div class="gi_page">
<a-card title="${businessName}管理" class="general-card">
<GiTable
ref="tableRef"
row-key="id"
:data="dataList"
:columns="columns"
:loading="loading"
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
:pagination="pagination"
:disabledColumnKeys="['name']"
@refresh="search"
>
<template #custom-left>
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
<a-input v-model="queryForm.${fieldConfig.fieldName}" placeholder="请输入${fieldConfig.comment}" allow-clear @change="search">
<template #prefix><icon-search /></template>
</a-input>
</#if>
</#list>
<a-button @click="reset">重置</a-button>
</template>
<template #custom-right>
<a-button v-permission="['${apiModuleName}:${apiName}:add']" type="primary" @click="onAdd">
<template #icon><icon-plus /></template>
<span>新增</span>
</a-button>
<a-tooltip content="导出">
<a-button v-permission="['${apiModuleName}:${apiName}:export']" @click="onExport">
<template #icon>
<icon-download />
</template>
</a-button>
</a-tooltip>
</template>
<template #action="{ record }">
<a-space>
<a-link v-permission="['${apiModuleName}:${apiName}:update']" @click="onUpdate(record)">修改</a-link>
<a-link
v-permission="['${apiModuleName}:${apiName}:delete']"
status="danger"
:disabled="record.disabled"
@click="onDelete(record)"
>
删除
</a-link>
</a-space>
</template>
</GiTable>
</a-card>
<${classNamePrefix}AddModal ref="${classNamePrefix}AddModalRef" @save-success="search" />
<${classNamePrefix}DetailDrawer ref="${classNamePrefix}DetailDrawerRef" />
</div>
</template>
<script setup lang="ts">
import { list${classNamePrefix}, delete${classNamePrefix}, export${classNamePrefix}, type ${classNamePrefix}Resp, type ${classNamePrefix}Query } from '@/apis'
import ${classNamePrefix}AddModal from './${classNamePrefix}AddModal.vue'
import ${classNamePrefix}DetailDrawer from './${classNamePrefix}DetailDrawer.vue'
import { Message } from '@arco-design/web-vue'
import type { TreeInstance } from '@arco-design/web-vue'
import type { TableInstanceColumns } from '@/components/GiTable/type'
import { useTable, useDownload } from '@/hooks'
import { isMobile } from '@/utils'
import has from '@/utils/has'
defineOptions({ name: '${classNamePrefix}' })
const columns: TableInstanceColumns[] = [
<#if fieldConfigs??>
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInList>
{ title: '${fieldConfig.comment}', dataIndex: '${fieldConfig.fieldName}' },
</#if>
</#list>
</#if>
{
title: '操作',
slotName: 'action',
width: 130,
align: 'center',
fixed: !isMobile() ? 'right' : undefined,
show: has.hasPermOr(['${apiModuleName}:${apiName}:update', '${apiModuleName}:${apiName}:delete'])
}
]
const queryForm: ${classNamePrefix}Query = reactive({
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
${fieldConfig.fieldName}: undefined,
</#if>
</#list>
sort: ['createTime,desc']
})
const {
tableData: dataList,
loading,
pagination,
search,
handleDelete
} = useTable((p) => list${classNamePrefix}({ ...queryForm, page: p.page, size: p.size }), { immediate: true })
// 重置
const reset = () => {
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
queryForm.${fieldConfig.fieldName} = undefined
</#if>
</#list>
search()
}
// 删除
const onDelete = (item: ${classNamePrefix}Resp) => {
return handleDelete(() => delete${classNamePrefix}(item.id), {
content: `是否确定删除该条数据?`,
showModal: true
})
}
// 导出
const onExport = () => {
useDownload(() => export${classNamePrefix}(queryForm))
}
const ${classNamePrefix}AddModalRef = ref<InstanceType<typeof ${classNamePrefix}AddModal>>()
// 新增
const onAdd = () => {
${classNamePrefix}AddModalRef.value?.onAdd()
}
// 修改
const onUpdate = (item: ${classNamePrefix}Resp) => {
${classNamePrefix}AddModalRef.value?.onUpdate(item.id)
}
const ${classNamePrefix}DetailDrawerRef = ref<InstanceType<typeof ${classNamePrefix}DetailDrawer>>()
// 详情
const onDetail = (item: ${classNamePrefix}Resp) => {
${classNamePrefix}DetailDrawerRef.value?.onDetail(item.id)
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,511 +0,0 @@
<script lang="ts" setup>
import {
DataRecord,
ListParam,
list,
get,
add,
update,
del,
} from '@/api/${apiModuleName}/${apiName}';
import checkPermission from '@/utils/permission';
const { proxy } = getCurrentInstance() as any;
// const { dis_enable_status_enum } = proxy.useDict('dis_enable_status_enum');
const queryFormRef = ref();
const formRef = ref();
const dataList = ref<DataRecord[]>([]);
const dataDetail = ref<DataRecord>({
// TODO 待补充详情字段默认值
});
const total = ref(0);
const ids = ref<Array<string>>([]);
const title = ref('');
const single = ref(true);
const multiple = ref(true);
const showQuery = ref(true);
const loading = ref(false);
const detailLoading = ref(false);
const exportLoading = ref(false);
const visible = ref(false);
const detailVisible = ref(false);
const data = reactive({
// 查询参数
queryParams: {
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
${fieldConfig.fieldName}: undefined,
</#if>
</#list>
page: 1,
size: 10,
sort: ['createTime,desc'],
},
// 表单数据
form: {} as DataRecord,
// 表单验证规则
rules: {
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInForm && fieldConfig.isRequired>
${fieldConfig.fieldName}: [{ required: true, message: '${fieldConfig.comment}不能为空' }],
</#if>
</#list>
},
});
const { queryParams, form, rules } = toRefs(data);
/**
* 查询列表
*
* @param params 查询参数
*/
const getList = (params: ListParam = { ...queryParams.value }) => {
loading.value = true;
list(params)
.then((res) => {
dataList.value = res.data.list;
total.value = res.data.total;
})
.finally(() => {
loading.value = false;
});
};
getList();
/**
* 打开新增对话框
*/
const toAdd = () => {
reset();
title.value = '新增${businessName}';
visible.value = true;
};
/**
* 打开修改对话框
*
* @param id ID
*/
const toUpdate = (id: string) => {
reset();
get(id).then((res) => {
form.value = res.data;
title.value = '修改${businessName}';
visible.value = true;
});
};
/**
* 重置表单
*/
const reset = () => {
form.value = {
// TODO 待补充需要重置的字段默认值,详情请参考其他模块 index.vue
};
formRef.value.resetFields();
};
/**
* 取消
*/
const handleCancel = () => {
visible.value = false;
formRef.value.resetFields();
};
/**
* 确定
*/
const handleOk = () => {
formRef.value.validate((valid: any) => {
if (!valid) {
if (form.value.id !== undefined) {
update(form.value, form.value.id).then((res) => {
handleCancel();
getList();
proxy.$message.success(res.msg);
});
} else {
add(form.value).then((res) => {
handleCancel();
getList();
proxy.$message.success(res.msg);
});
}
}
});
};
/**
* 查看详情
*
* @param id ID
*/
const toDetail = async (id: string) => {
if (detailLoading.value) return;
detailLoading.value = true;
detailVisible.value = true;
get(id)
.then((res) => {
dataDetail.value = res.data;
})
.finally(() => {
detailLoading.value = false;
});
};
/**
* 关闭详情
*/
const handleDetailCancel = () => {
detailVisible.value = false;
};
/**
* 批量删除
*/
const handleBatchDelete = () => {
if (ids.value.length === 0) {
proxy.$message.info('请选择要删除的数据');
} else {
proxy.$modal.warning({
title: '警告',
titleAlign: 'start',
content: `是否确定删除所选的${r'${ids.value.length}'}条数据?`,
hideCancel: false,
onOk: () => {
handleDelete(ids.value);
},
});
}
};
/**
* 删除
*
* @param ids ID 列表
*/
const handleDelete = (ids: Array<string>) => {
del(ids).then((res) => {
proxy.$message.success(res.msg);
getList();
});
};
/**
* 已选择的数据行发生改变时触发
*
* @param rowKeys ID 列表
*/
const handleSelectionChange = (rowKeys: Array<any>) => {
ids.value = rowKeys;
single.value = rowKeys.length !== 1;
multiple.value = !rowKeys.length;
};
/**
* 导出
*/
const handleExport = () => {
if (exportLoading.value) return;
exportLoading.value = true;
proxy
.download('/${apiModuleName}/${apiName}/export', { ...queryParams.value }, '${businessName}数据')
.finally(() => {
exportLoading.value = false;
});
};
/**
* 查询
*/
const handleQuery = () => {
getList();
};
/**
* 重置
*/
const resetQuery = () => {
queryFormRef.value.resetFields();
handleQuery();
};
/**
* 切换页码
*
* @param current 页码
*/
const handlePageChange = (current: number) => {
queryParams.value.page = current;
getList();
};
/**
* 切换每页条数
*
* @param pageSize 每页条数
*/
const handlePageSizeChange = (pageSize: number) => {
queryParams.value.size = pageSize;
getList();
};
</script>
<script lang="ts">
export default {
name: '${classNamePrefix}',
};
</script>
<template>
<div class="app-container">
<Breadcrumb :items="['menu.${apiModuleName}', 'menu.${apiModuleName}.${apiName}.list']" />
<a-card class="general-card" :title="$t('menu.${apiModuleName}.${apiName}.list')">
<!-- 头部区域 -->
<div class="header">
<!-- 搜索栏 -->
<div v-if="showQuery" class="header-query">
<a-form ref="queryFormRef" :model="queryParams" layout="inline">
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInQuery>
<a-form-item field="${fieldConfig.fieldName}" hide-label>
<a-input
v-model="queryParams.${fieldConfig.fieldName}"
placeholder="输入${fieldConfig.comment}搜索"
allow-clear
style="width: 150px"
@press-enter="handleQuery"
/>
</a-form-item>
</#if>
</#list>
<a-form-item hide-label>
<a-space>
<a-button type="primary" @click="handleQuery">
<template #icon><icon-search /></template>查询
</a-button>
<a-button @click="resetQuery">
<template #icon><icon-refresh /></template>重置
</a-button>
</a-space>
</a-form-item>
</a-form>
</div>
<!-- 操作栏 -->
<div class="header-operation">
<a-row>
<a-col :span="12">
<a-space>
<a-button
v-permission="['${apiModuleName}:${apiName}:add']"
type="primary"
@click="toAdd"
>
<template #icon><icon-plus /></template>新增
</a-button>
<a-button
v-permission="['${apiModuleName}:${apiName}:update']"
type="primary"
status="success"
:disabled="single"
:title="single ? '请选择一条要修改的数据' : ''"
@click="toUpdate(ids[0])"
>
<template #icon><icon-edit /></template>修改
</a-button>
<a-button
v-permission="['${apiModuleName}:${apiName}:delete']"
type="primary"
status="danger"
:disabled="multiple"
:title="multiple ? '请选择要删除的数据' : ''"
@click="handleBatchDelete"
>
<template #icon><icon-delete /></template>删除
</a-button>
<a-button
v-permission="['${apiModuleName}:${apiName}:export']"
:loading="exportLoading"
type="primary"
status="warning"
@click="handleExport"
>
<template #icon><icon-download /></template>导出
</a-button>
</a-space>
</a-col>
<a-col :span="12">
<right-toolbar
v-model:show-query="showQuery"
@refresh="getList"
/>
</a-col>
</a-row>
</div>
</div>
<!-- 列表区域 -->
<a-table
row-key="id"
:data="dataList"
:loading="loading"
:row-selection="{
type: 'checkbox',
showCheckedAll: true,
onlyCurrent: false,
}"
:pagination="{
showTotal: true,
showPageSize: true,
total: total,
current: queryParams.page,
}"
:bordered="false"
column-resizable
stripe
size="large"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
@selection-change="handleSelectionChange"
>
<template #columns>
<#list fieldConfigs as fieldConfig>
<#if fieldConfig_index = 0>
<a-table-column title="${fieldConfig.comment}" data-index="${fieldConfig.fieldName}">
<template #cell="{ record }">
<a-link @click="toDetail(record.id)">{{ record.${fieldConfig.fieldName} }}</a-link>
</template>
</a-table-column>
<#else>
<#if fieldConfig.showInList>
<a-table-column title="${fieldConfig.comment}" data-index="${fieldConfig.fieldName}" />
</#if>
</#if>
</#list>
<a-table-column
v-if="checkPermission(['${apiModuleName}:${apiName}:update', '${apiModuleName}:${apiName}:delete'])"
title="操作"
align="center"
>
<template #cell="{ record }">
<a-button
v-permission="['${apiModuleName}:${apiName}:update']"
type="text"
size="small"
title="修改"
@click="toUpdate(record.id)"
>
<template #icon><icon-edit /></template>修改
</a-button>
<a-popconfirm
content="是否确定删除该数据?"
type="warning"
@ok="handleDelete([record.id])"
>
<a-button
v-permission="['${apiModuleName}:${apiName}:delete']"
type="text"
size="small"
title="删除"
:disabled="record.disabled"
>
<template #icon><icon-delete /></template>删除
</a-button>
</a-popconfirm>
</template>
</a-table-column>
</template>
</a-table>
<!-- 表单区域 -->
<a-modal
:title="title"
:visible="visible"
:mask-closable="false"
:esc-to-close="false"
unmount-on-close
render-to-body
@ok="handleOk"
@cancel="handleCancel"
>
<a-form ref="formRef" :model="form" :rules="rules" size="large">
<#list fieldConfigs as fieldConfig>
<#if fieldConfig.showInForm>
<a-form-item label="${fieldConfig.comment}" field="${fieldConfig.fieldName}">
<#if fieldConfig.formType = 'TEXT'>
<a-input v-model="form.${fieldConfig.fieldName}" placeholder="请输入${fieldConfig.comment}" />
<#elseif fieldConfig.formType = 'TEXT_AREA'>
<a-textarea
v-model="form.${fieldConfig.fieldName}"
:max-length="200"
placeholder="请输入${fieldConfig.comment}"
:auto-size="{
minRows: 3,
}"
show-word-limit
/>
<#elseif fieldConfig.formType = 'SELECT'>
<#--<a-select
v-model="form.${fieldConfig.fieldName}"
:options="${apiName}Options"
placeholder="请选择${fieldConfig.comment}"
:loading="${apiName}Loading"
multiple
allow-clear
:allow-search="{ retainInputValue: true }"
style="width: 416px"
/>-->
<#elseif fieldConfig.formType = 'RADIO'>
<#--<a-radio-group v-model="form.${fieldConfig.fieldName}" type="button">
</a-radio-group>-->
<#elseif fieldConfig.formType = 'DATE'>
<a-date-picker v-model="form.${fieldConfig.fieldName}" placeholder="请选择${fieldConfig.comment}"/>
<#elseif fieldConfig.formType = 'DATE_TIME'>
<a-date-picker
v-model="form.${fieldConfig.fieldName}"
placeholder="请选择${fieldConfig.comment}"
show-time
format="YYYY-MM-DD HH:mm:ss"
/>
</#if>
</a-form-item>
</#if>
</#list>
</a-form>
</a-modal>
<!-- 详情区域 -->
<a-drawer
title="${businessName}详情"
:visible="detailVisible"
:width="580"
:footer="false"
unmount-on-close
render-to-body
@cancel="handleDetailCancel"
>
<a-descriptions :column="2" bordered size="large">
<#list fieldConfigs as fieldConfig>
<a-descriptions-item label="${fieldConfig.comment}">
<a-skeleton v-if="detailLoading" :animation="true">
<a-skeleton-line :rows="1" />
</a-skeleton>
<#if fieldConfig.fieldName = 'createUser'>
<span v-else>{{ dataDetail.createUserString }}</span>
<#elseif fieldConfig.fieldName = 'updateUser'>
<span v-else>{{ dataDetail.updateUserString }}</span>
<#else>
<span v-else>{{ dataDetail.${fieldConfig.fieldName} }}</span>
</#if>
</a-descriptions-item>
</#list>
</a-descriptions>
</a-drawer>
</a-card>
</div>
</template>
<style scoped lang="less"></style>

View File

@ -43,6 +43,7 @@
<artifactId>continew-admin-generator</artifactId>
</dependency>
<!-- 系统管理模块(存放系统管理模块相关功能,例如:部门管理、角色管理、用户管理等) -->
<dependency>
<groupId>top.charles7c.continew</groupId>
<artifactId>continew-admin-system</artifactId>

View File

@ -42,7 +42,7 @@ generator:
templateConfigs:
DO:
# 模板路径
templatePath: Entity.ftl
templatePath: backend/Entity.ftl
# 包名称
packageName: model.entity
# 排除字段
@ -53,20 +53,20 @@ generator:
- updateUser
- updateTime
Query:
templatePath: Query.ftl
templatePath: backend/Query.ftl
packageName: model.query
Req:
templatePath: Req.ftl
templatePath: backend/Req.ftl
packageName: model.req
Resp:
templatePath: Resp.ftl
templatePath: backend/Resp.ftl
packageName: model.resp
excludeFields:
- id
- createUser
- createTime
DetailResp:
templatePath: DetailResp.ftl
templatePath: backend/DetailResp.ftl
packageName: model.resp
excludeFields:
- id
@ -75,14 +75,34 @@ generator:
- updateUser
- updateTime
Mapper:
templatePath: Mapper.ftl
templatePath: backend/Mapper.ftl
packageName: mapper
Service:
templatePath: Service.ftl
templatePath: backend/Service.ftl
packageName: service
ServiceImpl:
templatePath: ServiceImpl.ftl
templatePath: backend/ServiceImpl.ftl
packageName: service.impl
Controller:
templatePath: Controller.ftl
packageName: controller
templatePath: backend/Controller.ftl
packageName: controller
api:
templatePath: frontend/api.ftl
packageName: src/apis
extension: .ts
backend: false
index:
templatePath: frontend/index.ftl
packageName: src/views
extension: .vue
backend: false
AddModal:
templatePath: frontend/AddModal.ftl
packageName: src/views
extension: .vue
backend: false
DetailDrawer:
templatePath: frontend/DetailDrawer.ftl
packageName: src/views
extension: .vue
backend: false