Vue3 重构:基础设施 -> 代码生成

This commit is contained in:
dlarmor 2023-03-27 14:06:30 +08:00
parent 812300cc04
commit f33935868d
12 changed files with 966 additions and 490 deletions

View File

@ -2,56 +2,56 @@ import request from '@/config/axios'
import type { CodegenUpdateReqVO, CodegenCreateListReqVO } from './types'
// 查询列表代码生成表定义
export const getCodegenTablePageApi = (params) => {
export const getCodegenTablePage = (params) => {
return request.get({ url: '/infra/codegen/table/page', params })
}
// 查询详情代码生成表定义
export const getCodegenTableApi = (id: number) => {
export const getCodegenTable = (id: number) => {
return request.get({ url: '/infra/codegen/detail?tableId=' + id })
}
// 新增代码生成表定义
export const createCodegenTableApi = (data: CodegenCreateListReqVO) => {
export const createCodegenTable = (data: CodegenCreateListReqVO) => {
return request.post({ url: '/infra/codegen/create', data })
}
// 修改代码生成表定义
export const updateCodegenTableApi = (data: CodegenUpdateReqVO) => {
export const updateCodegenTable = (data: CodegenUpdateReqVO) => {
return request.put({ url: '/infra/codegen/update', data })
}
// 基于数据库的表结构,同步数据库的表和字段定义
export const syncCodegenFromDBApi = (id: number) => {
export const syncCodegenFromDB = (id: number) => {
return request.put({ url: '/infra/codegen/sync-from-db?tableId=' + id })
}
// 基于 SQL 建表语句,同步数据库的表和字段定义
export const syncCodegenFromSQLApi = (id: number, sql: string) => {
export const syncCodegenFromSQL = (id: number, sql: string) => {
return request.put({ url: '/infra/codegen/sync-from-sql?tableId=' + id + '&sql=' + sql })
}
// 预览生成代码
export const previewCodegenApi = (id: number) => {
export const previewCodegen = (id: number) => {
return request.get({ url: '/infra/codegen/preview?tableId=' + id })
}
// 下载生成代码
export const downloadCodegenApi = (id: number) => {
export const downloadCodegen = (id: number) => {
return request.download({ url: '/infra/codegen/download?tableId=' + id })
}
// 获得表定义
export const getSchemaTableListApi = (params) => {
export const getSchemaTableList = (params) => {
return request.get({ url: '/infra/codegen/db/table/list', params })
}
// 基于数据库的表结构,创建代码生成器的表定义
export const createCodegenListApi = (data) => {
export const createCodegenList = (data) => {
return request.post({ url: '/infra/codegen/create-list', data })
}
// 删除代码生成表定义
export const deleteCodegenTableApi = (id: number) => {
export const deleteCodegenTable = (id: number) => {
return request.delete({ url: '/infra/codegen/delete?tableId=' + id })
}

View File

@ -52,7 +52,7 @@ export type CodegenPreviewVO = {
code: string
}
export type CodegenUpdateReqVO = {
table: CodegenTableVO
table: CodegenTableVO | any
columns: CodegenColumnVO[]
}
export type CodegenCreateListReqVO = {

View File

@ -298,7 +298,8 @@ export default {
typeUpdate: '字典类型编辑',
dataCreate: '字典数据新增',
dataUpdate: '字典数据编辑',
fileUpload: '上传文件'
fileUpload: '上传文件',
back: '返回'
},
dialog: {
dialog: '弹窗',

View File

@ -1,67 +1,74 @@
<template>
<ContentWrap>
<ContentDetailWrap :title="title" @back="push('/infra/codegen')">
<el-tabs v-model="activeName">
<el-tab-pane label="基本信息" name="basicInfo">
<BasicInfoForm ref="basicInfoRef" :basicInfo="tableCurrentRow" />
</el-tab-pane>
<el-tab-pane label="字段信息" name="cloum">
<CloumInfoForm ref="cloumInfoRef" :info="cloumCurrentRow" />
</el-tab-pane>
</el-tabs>
<template #right>
<XButton
type="primary"
:title="t('action.save')"
:loading="loading"
@click="submitForm()"
/>
</template>
</ContentDetailWrap>
</ContentWrap>
<content-wrap v-loading="loading">
<el-tabs v-model="activeName">
<el-tab-pane label="基本信息" name="basicInfo">
<basic-info-form ref="basicInfoRef" :table="formData.table" />
</el-tab-pane>
<el-tab-pane label="字段信息" name="colum">
<colum-info-form ref="columInfoRef" :columns="formData.columns" />
</el-tab-pane>
<el-tab-pane label="生成信息" name="generateInfo">
<generate-info-form ref="generateInfoRef" :table="formData.table" />
</el-tab-pane>
</el-tabs>
<el-form label-width="100px">
<el-form-item style="text-align: center; margin-left: -100px; margin-top: 10px">
<el-button type="primary" @click="submitForm" :loading="submitLoading">
{{ t('action.save') }}
</el-button>
<el-button @click="close">{{ t('action.back') }}</el-button>
</el-form-item>
</el-form>
</content-wrap>
</template>
<script setup lang="ts">
import { BasicInfoForm, CloumInfoForm } from './components'
import { getCodegenTableApi, updateCodegenTableApi } from '@/api/infra/codegen'
import { CodegenTableVO, CodegenColumnVO, CodegenUpdateReqVO } from '@/api/infra/codegen/types'
import { BasicInfoForm, ColumInfoForm, GenerateInfoForm } from './components'
import * as CodegenApi from '@/api/infra/codegen'
import ContentWrap from '@/components/ContentWrap/src/ContentWrap.vue'
import { useTagsViewStore } from '@/store/modules/tagsView'
import { CodegenUpdateReqVO } from '@/api/infra/codegen/types'
const { t } = useI18n() //
const message = useMessage() //
const { push } = useRouter()
const { push, currentRoute } = useRouter()
const { query } = useRoute()
const { delView } = useTagsViewStore()
const loading = ref(false)
const title = ref('代码生成')
const submitLoading = ref(false)
const activeName = ref('basicInfo')
const cloumInfoRef = ref(null)
const tableCurrentRow = ref<CodegenTableVO>()
const cloumCurrentRow = ref<CodegenColumnVO[]>([])
const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>()
const columInfoRef = ref<ComponentRef<typeof ColumInfoForm>>()
const generateInfoRef = ref<ComponentRef<typeof GenerateInfoForm>>()
const formData = ref<CodegenUpdateReqVO>({
table: {},
columns: []
})
const getList = async () => {
const getDetail = async () => {
const id = query.id as unknown as number
if (id) {
loading.value = true
//
const res = await getCodegenTableApi(id)
title.value = '修改[ ' + res.table.tableName + ' ]生成配置'
tableCurrentRow.value = res.table
cloumCurrentRow.value = res.columns
formData.value = await CodegenApi.getCodegenTable(id)
loading.value = false
}
}
const submitForm = async () => {
const basicInfo = unref(basicInfoRef)
const basicForm = await basicInfo?.elFormRef?.validate()?.catch(() => {})
if (basicForm) {
const basicInfoData = (await basicInfo?.getFormData()) as CodegenTableVO
const genTable: CodegenUpdateReqVO = {
table: basicInfoData,
columns: cloumCurrentRow.value
}
await updateCodegenTableApi(genTable)
if (!unref(formData)) return
try {
await unref(basicInfoRef)?.validate()
await unref(generateInfoRef)?.validate()
await CodegenApi.updateCodegenTable(unref(formData))
message.success(t('common.updateSuccess'))
push('/infra/codegen')
}
} catch {}
}
/** 关闭按钮 */
const close = () => {
delView(unref(currentRoute))
push('/infra/codegen')
}
onMounted(() => {
getList()
getDetail()
})
</script>

View File

@ -1,183 +1,93 @@
<template>
<Form :rules="rules" @register="register" />
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
<el-row>
<el-col :span="12">
<el-form-item label="表名称" prop="tableName">
<el-input placeholder="请输入仓库名称" v-model="formData.tableName" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="表描述" prop="tableComment">
<el-input placeholder="请输入" v-model="formData.tableComment" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="className">
<template #label>
<span>
实体类名称
<el-tooltip
content="默认去除表名的前缀。如果存在重复,则需要手动添加前缀,避免 MyBatis 报 Alias 重复的问题。"
placement="top"
>
<Icon icon="ep:question-filled" class="" />
</el-tooltip>
</span>
</template>
<el-input placeholder="请输入" v-model="formData.className" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="作者" prop="author">
<el-input placeholder="请输入" v-model="formData.author" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input type="textarea" :rows="3" v-model="formData.remark" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script setup lang="ts">
import { useForm } from '@/hooks/web/useForm'
import { FormSchema } from '@/types/form'
import { CodegenTableVO } from '@/api/infra/codegen/types'
import { getIntDictOptions } from '@/utils/dict'
import { listSimpleMenusApi } from '@/api/system/menu'
import { handleTree, defaultProps } from '@/utils/tree'
import { PropType } from 'vue'
const emits = defineEmits(['update:basicInfo'])
const props = defineProps({
basicInfo: {
table: {
type: Object as PropType<Nullable<CodegenTableVO>>,
default: () => null
}
})
const templateTypeOptions = getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE)
const sceneOptions = getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE)
const menuOptions = ref<any>([]) //
const getTree = async () => {
const res = await listSimpleMenusApi()
menuOptions.value = handleTree(res)
}
const formRef = ref()
const formData = ref({
tableName: '',
tableComment: '',
className: '',
author: '',
remark: ''
})
const rules = reactive({
tableName: [required],
tableComment: [required],
className: [required],
author: [required],
templateType: [required],
scene: [required],
moduleName: [required],
businessName: [required],
businessPackage: [required],
classComment: [required]
})
const schema = reactive<FormSchema[]>([
{
label: '上级菜单',
field: 'parentMenuId',
component: 'TreeSelect',
componentProps: {
data: menuOptions,
props: defaultProps,
checkStrictly: true,
nodeKey: 'id'
},
labelMessage: '分配到指定菜单下,例如 系统管理',
colProps: {
span: 24
}
},
{
label: '表名称',
field: 'tableName',
component: 'Input',
colProps: {
span: 12
}
},
{
label: '表描述',
field: 'tableComment',
component: 'Input',
colProps: {
span: 12
}
},
{
label: '实体类名称',
field: 'className',
component: 'Input',
colProps: {
span: 12
}
},
{
label: '类名称',
field: 'className',
component: 'Input',
labelMessage: '类名称首字母大写例如SysUser、SysMenu、SysDictData 等等',
colProps: {
span: 12
}
},
{
label: '生成模板',
field: 'templateType',
component: 'Select',
componentProps: {
options: templateTypeOptions
},
colProps: {
span: 12
}
},
{
label: '生成场景',
field: 'scene',
component: 'Select',
componentProps: {
options: sceneOptions
},
colProps: {
span: 12
}
},
{
label: '模块名',
field: 'moduleName',
component: 'Input',
labelMessage: '模块名,即一级目录,例如 system、infra、tool 等等',
colProps: {
span: 12
}
},
{
label: '业务名',
field: 'businessName',
component: 'Input',
labelMessage: '业务名,即二级目录,例如 user、permission、dict 等等',
colProps: {
span: 12
}
},
{
label: '类描述',
field: 'classComment',
component: 'Input',
labelMessage: '用作类描述,例如 用户',
colProps: {
span: 12
}
},
{
label: '作者',
field: 'author',
component: 'Input',
colProps: {
span: 12
}
},
{
label: '备注',
field: 'remark',
component: 'Input',
componentProps: {
type: 'textarea',
rows: 4
},
colProps: {
span: 24
}
}
])
const { register, methods, elFormRef } = useForm({
schema
author: [required]
})
watch(
() => props.basicInfo,
(basicInfo) => {
if (!basicInfo) return
const { setValues } = methods
setValues(basicInfo)
() => props.table,
(table) => {
if (!table) return
formData.value = table
},
{
deep: true,
immediate: true
}
)
// ========== ==========
onMounted(async () => {
await getTree()
})
watch(
() => formData.value,
(val) => {
emits('update:basicInfo', val)
}
)
defineExpose({
elFormRef,
getFormData: methods.getFormData
validate: async () => unref(formRef)?.validate()
})
</script>

View File

@ -1,137 +0,0 @@
<template>
<vxe-table
ref="dragTable"
border
:data="info"
max-height="600"
stripe
class="xtable-scrollbar"
:column-config="{ resizable: true }"
>
<vxe-column title="字段列名" field="columnName" fixed="left" width="10%" />
<vxe-colgroup title="基础属性">
<vxe-column title="字段描述" field="columnComment" width="10%">
<template #default="{ row }">
<vxe-input v-model="row.columnComment" placeholder="请输入字段描述" />
</template>
</vxe-column>
<vxe-column title="物理类型" field="dataType" width="10%" />
<vxe-column title="Java类型" width="10%" field="javaType">
<template #default="{ row }">
<vxe-select v-model="row.javaType" placeholder="请选择Java类型">
<vxe-option label="Long" value="Long" />
<vxe-option label="String" value="String" />
<vxe-option label="Integer" value="Integer" />
<vxe-option label="Double" value="Double" />
<vxe-option label="BigDecimal" value="BigDecimal" />
<vxe-option label="LocalDateTime" value="LocalDateTime" />
<vxe-option label="Boolean" value="Boolean" />
</vxe-select>
</template>
</vxe-column>
<vxe-column title="java属性" width="8%" field="javaField">
<template #default="{ row }">
<vxe-input v-model="row.javaField" placeholder="请输入java属性" />
</template>
</vxe-column>
</vxe-colgroup>
<vxe-colgroup title="增删改查">
<vxe-column title="插入" width="40px" field="createOperation">
<template #default="{ row }">
<vxe-checkbox true-label="true" false-label="false" v-model="row.createOperation" />
</template>
</vxe-column>
<vxe-column title="编辑" width="40px" field="updateOperation">
<template #default="{ row }">
<vxe-checkbox true-label="true" false-label="false" v-model="row.updateOperation" />
</template>
</vxe-column>
<vxe-column title="列表" width="40px" field="listOperationResult">
<template #default="{ row }">
<vxe-checkbox true-label="true" false-label="false" v-model="row.listOperationResult" />
</template>
</vxe-column>
<vxe-column title="查询" width="40px" field="listOperation">
<template #default="{ row }">
<vxe-checkbox true-label="true" false-label="false" v-model="row.listOperation" />
</template>
</vxe-column>
<vxe-column title="允许空" width="40px" field="nullable">
<template #default="{ row }">
<vxe-checkbox true-label="true" false-label="false" v-model="row.nullable" />
</template>
</vxe-column>
<vxe-column title="查询方式" width="60px" field="listOperationCondition">
<template #default="{ row }">
<vxe-select v-model="row.listOperationCondition" placeholder="请选择查询方式">
<vxe-option label="=" value="=" />
<vxe-option label="!=" value="!=" />
<vxe-option label=">" value=">" />
<vxe-option label=">=" value=">=" />
<vxe-option label="<" value="<>" />
<vxe-option label="<=" value="<=" />
<vxe-option label="LIKE" value="LIKE" />
<vxe-option label="BETWEEN" value="BETWEEN" />
</vxe-select>
</template>
</vxe-column>
</vxe-colgroup>
<vxe-column title="显示类型" width="10%" field="htmlType">
<template #default="{ row }">
<vxe-select v-model="row.htmlType" placeholder="请选择显示类型">
<vxe-option label="文本框" value="input" />
<vxe-option label="文本域" value="textarea" />
<vxe-option label="下拉框" value="select" />
<vxe-option label="单选框" value="radio" />
<vxe-option label="复选框" value="checkbox" />
<vxe-option label="日期控件" value="datetime" />
<vxe-option label="图片上传" value="imageUpload" />
<vxe-option label="文件上传" value="fileUpload" />
<vxe-option label="富文本控件" value="editor" />
</vxe-select>
</template>
</vxe-column>
<vxe-column title="字典类型" width="10%" field="dictType">
<template #default="{ row }">
<vxe-select v-model="row.dictType" clearable filterable placeholder="请选择字典类型">
<vxe-option
v-for="dict in dictOptions"
:key="dict.id"
:label="dict.name"
:value="dict.type"
/>
</vxe-select>
</template>
</vxe-column>
<vxe-column title="示例" field="example">
<template #default="{ row }">
<vxe-input v-model="row.example" placeholder="请输入示例" />
</template>
</vxe-column>
</vxe-table>
</template>
<script setup lang="ts">
import { PropType } from 'vue'
import { DictTypeVO } from '@/api/system/dict/types'
import { CodegenColumnVO } from '@/api/infra/codegen/types'
import { listSimpleDictType } from '@/api/system/dict/dict.type'
const props = defineProps({
info: {
type: Array as unknown as PropType<CodegenColumnVO[]>,
default: () => null
}
})
/** 查询字典下拉列表 */
const dictOptions = ref<DictTypeVO[]>()
const getDictOptions = async () => {
const res = await listSimpleDictType()
dictOptions.value = res
}
onMounted(async () => {
await getDictOptions()
})
defineExpose({
info: props.info
})
</script>

View File

@ -0,0 +1,157 @@
<template>
<el-table ref="dragTable" :data="formData" row-key="columnId" :max-height="tableHeight">
<el-table-column
label="字段列名"
prop="columnName"
min-width="10%"
:show-overflow-tooltip="true"
/>
<el-table-column label="字段描述" min-width="10%">
<template #default="scope">
<el-input v-model="scope.row.columnComment" />
</template>
</el-table-column>
<el-table-column
label="物理类型"
prop="dataType"
min-width="10%"
:show-overflow-tooltip="true"
/>
<el-table-column label="Java类型" min-width="11%">
<template #default="scope">
<el-select v-model="scope.row.javaType">
<el-option label="Long" value="Long" />
<el-option label="String" value="String" />
<el-option label="Integer" value="Integer" />
<el-option label="Double" value="Double" />
<el-option label="BigDecimal" value="BigDecimal" />
<el-option label="LocalDateTime" value="LocalDateTime" />
<el-option label="Boolean" value="Boolean" />
</el-select>
</template>
</el-table-column>
<el-table-column label="java属性" min-width="10%">
<template #default="scope">
<el-input v-model="scope.row.javaField" />
</template>
</el-table-column>
<el-table-column label="插入" min-width="4%">
<template #default="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.createOperation" />
</template>
</el-table-column>
<el-table-column label="编辑" min-width="4%">
<template #default="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.updateOperation" />
</template>
</el-table-column>
<el-table-column label="列表" min-width="4%">
<template #default="scope">
<el-checkbox
true-label="true"
false-label="false"
v-model="scope.row.listOperationResult"
/>
</template>
</el-table-column>
<el-table-column label="查询" min-width="4%">
<template #default="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.listOperation" />
</template>
</el-table-column>
<el-table-column label="查询方式" min-width="10%">
<template #default="scope">
<el-select v-model="scope.row.listOperationCondition">
<el-option label="=" value="=" />
<el-option label="!=" value="!=" />
<el-option label=">" value=">" />
<el-option label=">=" value=">=" />
<el-option label="<" value="<>" />
<el-option label="<=" value="<=" />
<el-option label="LIKE" value="LIKE" />
<el-option label="BETWEEN" value="BETWEEN" />
</el-select>
</template>
</el-table-column>
<el-table-column label="允许空" min-width="5%">
<template #default="scope">
<el-checkbox true-label="true" false-label="false" v-model="scope.row.nullable" />
</template>
</el-table-column>
<el-table-column label="显示类型" min-width="12%">
<template #default="scope">
<el-select v-model="scope.row.htmlType">
<el-option label="文本框" value="input" />
<el-option label="文本域" value="textarea" />
<el-option label="下拉框" value="select" />
<el-option label="单选框" value="radio" />
<el-option label="复选框" value="checkbox" />
<el-option label="日期控件" value="datetime" />
<el-option label="图片上传" value="imageUpload" />
<el-option label="文件上传" value="fileUpload" />
<el-option label="富文本控件" value="editor" />
</el-select>
</template>
</el-table-column>
<el-table-column label="字典类型" min-width="12%">
<template #default="scope">
<el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择">
<el-option
v-for="dict in dictOptions"
:key="dict.id"
:label="dict.name"
:value="dict.type"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="示例" min-width="10%">
<template #default="scope">
<el-input v-model="scope.row.example" />
</template>
</el-table-column>
</el-table>
</template>
<script setup lang="ts">
import { PropType } from 'vue'
import { CodegenColumnVO } from '@/api/infra/codegen/types'
import { DictTypeVO, listSimpleDictType } from '@/api/system/dict/dict.type'
const emits = defineEmits(['update:columns'])
const props = defineProps({
columns: {
type: Array as unknown as PropType<CodegenColumnVO[]>,
default: () => null
}
})
const formData = ref<CodegenColumnVO[]>([])
const tableHeight = document.documentElement.scrollHeight - 350 + 'px'
/** 查询字典下拉列表 */
const dictOptions = ref<DictTypeVO[]>()
const getDictOptions = async () => {
dictOptions.value = await listSimpleDictType()
}
onMounted(async () => {
await getDictOptions()
})
watch(
() => props.columns,
(columns) => {
if (!columns) return
formData.value = columns
},
{
deep: true,
immediate: true
}
)
watch(
() => formData.value,
(val) => {
emits('update:columns', val)
}
)
</script>

View File

@ -0,0 +1,379 @@
<template>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="150px">
<el-row>
<el-col :span="12">
<el-form-item prop="templateType" label="生成模板">
<el-select v-model="formData.templateType" @change="tplSelectChange">
<el-option
v-for="dict in getDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE)"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="scene" label="生成场景">
<el-select v-model="formData.scene">
<el-option
v-for="dict in getDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE)"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select>
</el-form-item>
</el-col>
<!-- <el-col :span="12">-->
<!-- <el-form-item prop="packageName">-->
<!-- <span slot="label">-->
<!-- 生成包路径-->
<!-- <el-tooltip content="生成在哪个java包下例如 com.ruoyi.system" placement="top">-->
<!-- <i class="el-icon-question"></i>-->
<!-- </el-tooltip>-->
<!-- </span>-->
<!-- <el-input v-model="formData.packageName" />-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<el-col :span="12">
<el-form-item prop="moduleName">
<template #label>
<span>
模块名
<el-tooltip
content="模块名,即一级目录,例如 system、infra、tool 等等"
placement="top"
>
<Icon icon="ep:question-filled" />
</el-tooltip>
</span>
</template>
<el-input v-model="formData.moduleName" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="businessName">
<template #label>
<span>
业务名
<el-tooltip
content="业务名,即二级目录,例如 user、permission、dict 等等"
placement="top"
>
<Icon icon="ep:question-filled" />
</el-tooltip>
</span>
</template>
<el-input v-model="formData.businessName" />
</el-form-item>
</el-col>
<!-- <el-col :span="12">-->
<!-- <el-form-item prop="businessPackage">-->
<!-- <span slot="label">-->
<!-- 业务包-->
<!-- <el-tooltip content="业务包,自定义二级目录。例如说,我们希望将 dictType 和 dictData 归类成 dict 业务" placement="top">-->
<!-- <i class="el-icon-question"></i>-->
<!-- </el-tooltip>-->
<!-- </span>-->
<!-- <el-input v-model="formData.businessPackage" />-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<el-col :span="12">
<el-form-item prop="className">
<template #label>
<span>
类名称
<el-tooltip
content="类名称首字母大写例如SysUser、SysMenu、SysDictData 等等"
placement="top"
>
<Icon icon="ep:question-filled" />
</el-tooltip>
</span>
</template>
<el-input v-model="formData.className" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="classComment">
<template #label>
<span>
类描述
<el-tooltip content="用作类描述,例如 用户" placement="top">
<Icon icon="ep:question-filled" />
</el-tooltip>
</span>
</template>
<el-input v-model="formData.classComment" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template #label>
<span>
上级菜单
<el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top">
<Icon icon="ep:question-filled" />
</el-tooltip>
</span>
</template>
<el-tree-select
v-model="formData.parentMenuId"
placeholder="请选择系统菜单"
node-key="id"
check-strictly
:data="menus"
:props="menuTreeProps"
/>
</el-form-item>
</el-col>
<el-col :span="24" v-if="formData.genType === '1'">
<el-form-item prop="genPath">
<template #label>
<span>
自定义路径
<el-tooltip
content="填写磁盘绝对路径若不填写则生成到当前Web项目下"
placement="top"
>
<Icon icon="ep:question-filled" />
</el-tooltip>
</span>
</template>
<el-input v-model="formData.genPath">
<template #append>
<el-dropdown>
<el-button type="primary">
最近路径快速选择
<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="formData.genPath = '/'">
恢复默认的生成基础路径
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row v-show="formData.tplCategory === 'tree'">
<h4 class="form-header">其他信息</h4>
<el-col :span="12">
<el-form-item>
<template #label>
<span>
树编码字段
<el-tooltip content="树显示的编码字段名, 如dept_id" placement="top">
<Icon icon="ep:question-filled" />
</el-tooltip>
</span>
</template>
<el-select v-model="formData.treeCode" placeholder="请选择">
<el-option
v-for="(column, index) in formData.columns"
:key="index"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template #label>
<span>
树父编码字段
<el-tooltip content="树显示的父编码字段名, 如parent_Id" placement="top">
<Icon icon="ep:question-filled" />
</el-tooltip>
</span>
</template>
<el-select v-model="formData.treeParentCode" placeholder="请选择">
<el-option
v-for="(column, index) in formData.columns"
:key="index"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template #label>
<span>
树名称字段
<el-tooltip content="树节点的显示名称字段名, 如dept_name" placement="top">
<Icon icon="ep:question-filled" />
</el-tooltip>
</span>
</template>
<el-select v-model="formData.treeName" placeholder="请选择">
<el-option
v-for="(column, index) in formData.columns"
:key="index"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row v-show="formData.tplCategory === 'sub'">
<h4 class="form-header">关联信息</h4>
<el-col :span="12">
<el-form-item>
<template #label>
<span>
关联子表的表名
<el-tooltip content="关联子表的表名, 如sys_user" placement="top">
<Icon icon="ep:question-filled" />
</el-tooltip>
</span>
</template>
<el-select v-model="formData.subTableName" placeholder="请选择" @change="subSelectChange">
<el-option
v-for="(table0, index) in tables"
:key="index"
:label="table0.tableName + '' + table0.tableComment"
:value="table0.tableName"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template #label>
<span>
子表关联的外键名
<el-tooltip content="子表关联的外键名, 如user_id" placement="top">
<Icon icon="ep:question-filled" />
</el-tooltip>
</span>
</template>
<el-select v-model="formData.subTableFkName" placeholder="请选择">
<el-option
v-for="(column, index) in subColumns"
:key="index"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script setup lang="ts">
import { CodegenTableVO } from '@/api/infra/codegen/types'
import * as MenuApi from '@/api/system/menu'
import { PropType } from 'vue'
import { getDictOptions, DICT_TYPE } from '@/utils/dict'
import { handleTree } from '@/utils/tree'
const message = useMessage() //
const emits = defineEmits(['update:basicInfo'])
const props = defineProps({
table: {
type: Object as PropType<Nullable<CodegenTableVO>>,
default: () => null
}
})
const formRef = ref()
const formData = ref({
templateType: null,
scene: null,
moduleName: '',
businessName: '',
className: '',
classComment: '',
parentMenuId: null,
genPath: '',
treeCode: '',
treeParentCode: '',
treeName: '',
tplCategory: '',
subTableName: '',
subTableFkName: '',
genType: ''
})
const rules = reactive({
templateType: [required],
scene: [required],
moduleName: [required],
businessName: [required],
businessPackage: [required],
className: [required],
classComment: [required]
})
const tables = ref([])
const subColumns = ref([])
const menus = ref<any[]>([])
const menuTreeProps = {
label: 'name'
}
/** 选择子表名触发 */
const subSelectChange = () => {
formData.value.subTableFkName = ''
}
/** 选择生成模板触发 */
const tplSelectChange = (value) => {
if (value !== 1) {
// TODO
message.error(
'暂时不考虑支持【树形】和【主子表】的代码生成。原因是:导致 vm 模板过于复杂,不利于胖友二次开发'
)
return false
}
if (value !== 'sub') {
formData.value.subTableName = ''
formData.value.subTableFkName = ''
}
}
watch(
() => props.table,
(table) => {
if (!table) return
formData.value = table as any
},
{
deep: true,
immediate: true
}
)
watch(
() => formData.value,
(val) => {
emits('update:basicInfo', val)
}
)
onMounted(async () => {
try {
const resp = await MenuApi.listSimpleMenusApi()
menus.value = handleTree(resp)
} catch {}
})
defineExpose({
validate: async () => unref(formRef)?.validate()
})
</script>

View File

@ -1,9 +1,8 @@
<template>
<!-- 导入表 -->
<XModal title="导入表" v-model="visible">
<el-form :model="queryParams" ref="queryRef" :inline="true">
<Dialog :title="modelTitle" v-model="modelVisible">
<el-form :model="queryParams" ref="queryFormRef" :inline="true">
<el-form-item label="数据源" prop="dataSourceConfigId">
<el-select v-model="queryParams.dataSourceConfigId" placeholder="请选择数据源" clearable>
<el-select v-model="queryParams.dataSourceConfigId" placeholder="请选择数据源">
<el-option
v-for="config in dataSourceConfigs"
:key="config.id"
@ -13,52 +12,64 @@
</el-select>
</el-form-item>
<el-form-item label="表名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入表名称" clearable />
<el-input
v-model="queryParams.name"
placeholder="请输入表名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="表描述" prop="comment">
<el-input v-model="queryParams.comment" placeholder="请输入表描述" clearable />
<el-input
v-model="queryParams.comment"
placeholder="请输入表描述"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<XButton
type="primary"
preIcon="ep:search"
:title="t('common.query')"
@click="handleQuery()"
/>
<XButton preIcon="ep:refresh-right" :title="t('common.reset')" @click="resetQuery()" />
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
</el-form-item>
</el-form>
<vxe-table
ref="xTable"
:data="dbTableList"
v-loading="dbLoading"
:checkbox-config="{ highlight: true, range: true }"
height="260px"
class="xtable-scrollbar"
>
<vxe-column type="checkbox" width="60" />
<vxe-column field="name" title="表名称" />
<vxe-column field="comment" title="表描述" />
</vxe-table>
<el-row>
<el-table
v-loading="dbLoading"
@row-click="clickRow"
ref="tableRef"
:data="dbTableList"
@selection-change="handleSelectionChange"
height="260px"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="表名称" :show-overflow-tooltip="true" />
<el-table-column prop="comment" label="表描述" :show-overflow-tooltip="true" />
</el-table>
</el-row>
<template #footer>
<div class="dialog-footer">
<XButton type="primary" :title="t('action.import')" @click="handleImportTable()" />
<XButton :title="t('dialog.close')" @click="handleClose()" />
<el-button @click="handleImportTable" type="primary" :disabled="tables.length === 0">{{
t('action.import')
}}</el-button>
<el-button @click="handleClose">{{ t('dialog.close') }}</el-button>
</div>
</template>
</XModal>
</Dialog>
</template>
<script setup lang="ts">
import { VxeTableInstance } from 'vxe-table'
import type { DatabaseTableVO } from '@/api/infra/codegen/types'
import { getSchemaTableListApi, createCodegenListApi } from '@/api/infra/codegen'
import { getDataSourceConfigListApi, DataSourceConfigVO } from '@/api/infra/dataSourceConfig'
import * as CodegenApi from '@/api/infra/codegen'
import { getDataSourceConfigList, DataSourceConfigVO } from '@/api/infra/dataSourceConfig'
import { ElTable } from 'element-plus'
const { t } = useI18n() //
const message = useMessage() //
const emit = defineEmits(['ok'])
// ======== ========
const visible = ref(false)
const modelVisible = ref(false) //
const modelTitle = ref('导入表') //
const dbLoading = ref(true)
const queryParams = reactive({
name: undefined,
@ -67,10 +78,9 @@ const queryParams = reactive({
})
const dataSourceConfigs = ref<DataSourceConfigVO[]>([])
const show = async () => {
const res = await getDataSourceConfigListApi()
dataSourceConfigs.value = res
queryParams.dataSourceConfigId = dataSourceConfigs.value[0].id
visible.value = true
dataSourceConfigs.value = await getDataSourceConfigList()
queryParams.dataSourceConfigId = dataSourceConfigs.value[0].id as number
modelVisible.value = true
await getList()
}
/** 查询表数据 */
@ -79,8 +89,7 @@ const dbTableList = ref<DatabaseTableVO[]>([])
/** 查询表数据 */
const getList = async () => {
dbLoading.value = true
const res = await getSchemaTableListApi(queryParams)
dbTableList.value = res
dbTableList.value = await CodegenApi.getSchemaTableList(queryParams)
dbLoading.value = false
}
//
@ -91,23 +100,22 @@ const handleQuery = async () => {
const resetQuery = async () => {
queryParams.name = undefined
queryParams.comment = undefined
queryParams.dataSourceConfigId = 0
queryParams.dataSourceConfigId = dataSourceConfigs.value[0].id as number
await getList()
}
const xTable = ref<VxeTableInstance>()
const tableRef = ref<typeof ElTable>()
/** 多选框选中数据 */
const tables = ref<string[]>([])
const clickRow = (row) => {
unref(tableRef)?.toggleRowSelection(row)
}
//
const handleSelectionChange = (selection) => {
tables.value = selection.map((item) => item.name)
}
/** 导入按钮操作 */
const handleImportTable = async () => {
if (xTable.value?.getCheckboxRecords().length === 0) {
message.error('请选择要导入的表')
return
}
xTable.value?.getCheckboxRecords().forEach((item) => {
tables.value.push(item.name)
})
await createCodegenListApi({
await CodegenApi.createCodegenList({
dataSourceConfigId: queryParams.dataSourceConfigId,
tableNames: tables.value
})
@ -116,7 +124,7 @@ const handleImportTable = async () => {
handleClose()
}
const handleClose = () => {
visible.value = false
modelVisible.value = false
tables.value = []
}
defineExpose({

View File

@ -1,5 +1,11 @@
<template>
<XModal title="预览" v-model="preview.open">
<Dialog
:title="modelTitle"
v-model="modelVisible"
align-center
width="60%"
class="app-infra-codegen-preview-container"
>
<div class="flex">
<el-card class="w-1/4" :gutter="12" shadow="hover">
<el-scrollbar height="calc(100vh - 88px - 40px - 50px)">
@ -10,6 +16,7 @@
:expand-on-click-node="false"
highlight-current
@node-click="handleNodeClick"
default-expand-all
/>
</el-scrollbar>
</el-card>
@ -21,38 +28,34 @@
:name="item.filePath"
:key="item.filePath"
>
<XTextButton style="float: right" :title="t('common.copy')" @click="copy(item.code)" />
<el-button text type="primary" class="float-right" @click="copy(item.code)">
{{ t('common.copy') }}
</el-button>
<pre>{{ item.code }}</pre>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</XModal>
</Dialog>
</template>
<script setup lang="ts">
import { useClipboard } from '@vueuse/core'
import { handleTree2 } from '@/utils/tree'
import { previewCodegenApi } from '@/api/infra/codegen'
import { CodegenTableVO, CodegenPreviewVO } from '@/api/infra/codegen/types'
import * as CodegenApi from '@/api/infra/codegen'
import { CodegenPreviewVO } from '@/api/infra/codegen/types'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('代码预览') //
// ======== ========
const preview = reactive({
open: false,
titel: '代码预览',
fileTree: [],
activeName: ''
})
const previewCodegen = ref<CodegenPreviewVO[]>()
const show = async (row: CodegenTableVO) => {
const res = await previewCodegenApi(row.id)
let file = handleFiles(res)
previewCodegen.value = res
preview.fileTree = handleTree2(file, 'id', 'parentId', 'children', '/')
preview.activeName = res[0].filePath
preview.open = true
}
const handleNodeClick = async (data, node) => {
if (node && !node.isLeaf) {
return false
@ -132,14 +135,30 @@ const copy = async (text: string) => {
const { copy, copied, isSupported } = useClipboard({ source: text })
if (!isSupported) {
message.error(t('common.copyError'))
} else {
await copy()
if (unref(copied)) {
message.success(t('common.copySuccess'))
}
return
}
await copy()
if (unref(copied)) {
message.success(t('common.copySuccess'))
}
}
defineExpose({
show
})
/** 打开弹窗 */
const openModal = async (id: number) => {
modelVisible.value = true
const res = await CodegenApi.previewCodegen(id)
let file = handleFiles(res)
previewCodegen.value = res
preview.fileTree = handleTree2(file, 'id', 'parentId', 'children', '/')
preview.activeName = res[0].filePath
}
defineExpose({ openModal }) // openModal
</script>
<style lang="scss">
.app-infra-codegen-preview-container {
.el-scrollbar .el-scrollbar__wrap .el-scrollbar__view {
white-space: nowrap;
display: inline-block;
}
}
</style>

View File

@ -1,5 +1,6 @@
import BasicInfoForm from './BasicInfoForm.vue'
import CloumInfoForm from './CloumInfoForm.vue'
import ColumInfoForm from './ColumInfoForm.vue'
import GenerateInfoForm from './GenerateInfoForm.vue'
import ImportTable from './ImportTable.vue'
import Preview from './Preview.vue'
export { BasicInfoForm, CloumInfoForm, ImportTable, Preview }
export { BasicInfoForm, ColumInfoForm, GenerateInfoForm, ImportTable, Preview }

View File

@ -1,56 +1,124 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<template #toolbar_buttons>
<!-- 操作导入 -->
<XButton
type="primary"
preIcon="ep:zoom-in"
:title="t('action.import')"
v-hasPermi="['infra:codegen:create']"
@click="openImportTable()"
<!-- 搜索 -->
<content-wrap>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
<el-form-item label="表名称" prop="tableName">
<el-input
v-model="queryParams.tableName"
placeholder="请输入表名称"
clearable
@keyup.enter="handleQuery"
/>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作预览 -->
<XTextButton
preIcon="ep:view"
:title="t('action.preview')"
v-hasPermi="['infra:codegen:query']"
@click="handlePreview(row)"
</el-form-item>
<el-form-item label="表描述" prop="tableComment">
<el-input
v-model="queryParams.tableComment"
placeholder="请输入表描述"
clearable
@keyup.enter="handleQuery"
/>
<!-- 操作编辑 -->
<XTextButton
preIcon="ep:edit"
:title="t('action.edit')"
v-hasPermi="['infra:codegen:update']"
@click="handleUpdate(row.id)"
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="yyyy-MM-dd HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
/>
<!-- 操作删除 -->
<XTextButton
preIcon="ep:delete"
:title="t('action.del')"
v-hasPermi="['infra:codegen:delete']"
@click="deleteData(row.id)"
/>
<!-- 操作同步 -->
<XTextButton
preIcon="ep:refresh"
:title="t('action.sync')"
v-hasPermi="['infra:codegen:update']"
@click="handleSynchDb(row)"
/>
<!-- 操作生成 -->
<XTextButton
preIcon="ep:download"
:title="t('action.generate')"
v-hasPermi="['infra:codegen:download']"
@click="handleGenTable(row)"
/>
</template>
</XTable>
</ContentWrap>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
<el-button type="primary" v-hasPermi="['infra:codegen:create']" @click="openImportTable()">
<Icon icon="ep:zoom-in" class="mr-5px" />{{ t('action.import') }}
</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="数据源" align="center" :formatter="dataSourceConfigNameFormat" />
<el-table-column label="表名称" align="center" prop="tableName" width="200" />
<el-table-column
label="表描述"
align="center"
prop="tableComment"
:show-overflow-tooltip="true"
width="120"
/>
<el-table-column label="实体" align="center" prop="className" width="200" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column
label="更新时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column
label="操作"
align="center"
width="300px"
class-name="small-padding fixed-width"
>
<template #default="scope">
<el-button
link
type="primary"
@click="handlePreview(scope.row)"
v-hasPermi="['infra:codegen:preview']"
>预览</el-button
>
<el-button
link
type="primary"
@click="handleUpdate(scope.row.id)"
v-hasPermi="['infra:codegen:update']"
>编辑</el-button
>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['infra:codegen:delete']"
>删除</el-button
>
<el-button
link
type="primary"
@click="handleSynchDb(scope.row)"
v-hasPermi="['infra:codegen:update']"
>同步</el-button
>
<el-button
link
type="primary"
@click="handleGenTable(scope.row)"
v-hasPermi="['infra:codegen:download']"
>生成代码</el-button
>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</content-wrap>
<!-- 弹窗导入表 -->
<ImportTable ref="importRef" @ok="reload()" />
<!-- 弹窗预览代码 -->
@ -59,19 +127,70 @@
<script setup lang="ts" name="Codegen">
import download from '@/utils/download'
import * as CodegenApi from '@/api/infra/codegen'
import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'
import { CodegenTableVO } from '@/api/infra/codegen/types'
import { allSchemas } from './codegen.data'
import { ImportTable, Preview } from './components'
import ContentWrap from '@/components/ContentWrap/src/ContentWrap.vue'
import { DataSourceConfigVO } from '@/api/infra/dataSourceConfig'
import { dateFormatter } from '@/utils/formatTime'
const { t } = useI18n() //
const message = useMessage() //
const { t } = useI18n() //
const { push } = useRouter() //
//
const [registerTable, { reload, deleteData }] = useXTable({
allSchemas: allSchemas,
getListApi: CodegenApi.getCodegenTablePageApi,
deleteApi: CodegenApi.deleteCodegenTableApi
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
tableName: undefined,
tableComment: undefined,
createTime: []
})
const queryFormRef = ref() //
const dataSourceConfigs = ref<DataSourceConfigVO[]>([]) //
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
const data = await CodegenApi.getCodegenTablePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 初始化 **/
onMounted(async () => {
getList()
dataSourceConfigs.value = await DataSourceConfigApi.getDataSourceConfigList()
})
//
const dataSourceConfigNameFormat = (row) => {
for (const config of dataSourceConfigs.value) {
if (row.dataSourceConfigId === config.id) {
return config.name
}
}
return '未知【' + row.leaderUserId + '】'
}
//
const importRef = ref()
@ -81,27 +200,39 @@ const openImportTable = () => {
//
const previewRef = ref()
const handlePreview = (row: CodegenTableVO) => {
previewRef.value.show(row)
previewRef.value.openModal(row.id)
}
//
const handleUpdate = (rowId: number) => {
push('/codegen/edit?id=' + rowId)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await CodegenApi.deleteCodegenTable(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
//
const handleSynchDb = (row: CodegenTableVO) => {
const handleSynchDb = async (row: CodegenTableVO) => {
// DB
const tableName = row.tableName
message
.confirm('确认要强制同步' + tableName + '表结构吗?', t('common.reminder'))
.then(async () => {
await CodegenApi.syncCodegenFromDBApi(row.id)
message.success('同步成功')
})
try {
await message.confirm('确认要强制同步' + tableName + '表结构吗?', t('common.reminder'))
await CodegenApi.syncCodegenFromDB(row.id)
message.success('同步成功')
} catch {}
}
//
const handleGenTable = async (row: CodegenTableVO) => {
const res = await CodegenApi.downloadCodegenApi(row.id)
const res = await CodegenApi.downloadCodegen(row.id)
download.zip(res, 'codegen-' + row.className + '.zip')
}
</script>