mirror of
https://github.com/continew-org/continew-admin-ui.git
synced 2025-09-09 20:57:17 +08:00
feat: 新增代码生成
This commit is contained in:
@@ -3,9 +3,11 @@ export * from './auth'
|
||||
export * from './common'
|
||||
export * from './monitor'
|
||||
export * from './system'
|
||||
export * from './tool'
|
||||
|
||||
export * from './area/type'
|
||||
export * from './auth/type'
|
||||
export * from './common/type'
|
||||
export * from './monitor/type'
|
||||
export * from './system/type'
|
||||
export * from './tool/type'
|
||||
|
38
src/apis/tool/generator.ts
Normal file
38
src/apis/tool/generator.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import http from '@/utils/http'
|
||||
import type * as Tool from './type'
|
||||
|
||||
const BASE_URL = '/generator'
|
||||
|
||||
/** @desc 查询代码生成列表 */
|
||||
export function listGenerator(query: Tool.TablePageQuery) {
|
||||
return http.get<PageRes<Tool.TableResp[]>>(`${BASE_URL}/table`, query)
|
||||
}
|
||||
|
||||
/** @desc 查询字段配置列表 */
|
||||
export function listFieldConfig(tableName: string, requireSync: boolean) {
|
||||
return http.get<Tool.FieldConfigResp[]>(`${BASE_URL}/field/${tableName}?requireSync=${requireSync}`)
|
||||
}
|
||||
|
||||
/** @desc 查询生成配置信息 */
|
||||
export function getGenConfig(tableName: string) {
|
||||
return http.get<Tool.GenConfigResp>(`${BASE_URL}/config/${tableName}`)
|
||||
}
|
||||
|
||||
/** @desc 保存配置信息 */
|
||||
export function saveGenConfig(tableName: string, req: Tool.GeneratorConfigResp) {
|
||||
return http.post(`${BASE_URL}/config/${tableName}`, req)
|
||||
}
|
||||
|
||||
/** @desc 生成预览 */
|
||||
export function genPreview(tableName: string) {
|
||||
return http.get<Tool.GeneratePreviewResp[]>(`${BASE_URL}/preview/${tableName}`)
|
||||
}
|
||||
|
||||
/** @desc 生成代码 */
|
||||
export function generate(tableNames: Array<string>) {
|
||||
return http.requestNative({
|
||||
url: `${BASE_URL}/${tableNames}`,
|
||||
method: 'post',
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
1
src/apis/tool/index.ts
Normal file
1
src/apis/tool/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './generator'
|
49
src/apis/tool/type.ts
Normal file
49
src/apis/tool/type.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/** 工具代码生成类型 */
|
||||
export interface TableResp {
|
||||
tableName: string
|
||||
comment?: string
|
||||
engine: string
|
||||
charset: string
|
||||
createTime?: string
|
||||
isConfiged: boolean
|
||||
disabled: boolean
|
||||
}
|
||||
export interface TableQuery {
|
||||
tableName?: string
|
||||
}
|
||||
export interface TablePageQuery extends PageQuery, TableQuery {}
|
||||
export interface FieldConfigResp {
|
||||
tableName: string
|
||||
columnName: string
|
||||
columnType: string
|
||||
fieldName: string
|
||||
fieldType: string
|
||||
fieldSort: number
|
||||
comment: string
|
||||
isRequired: boolean
|
||||
showInList: boolean
|
||||
showInForm: boolean
|
||||
showInQuery: boolean
|
||||
formType: string
|
||||
queryType: string
|
||||
createTime?: string
|
||||
}
|
||||
export interface GenConfigResp {
|
||||
tableName: string
|
||||
moduleName: string
|
||||
packageName: string
|
||||
businessName: string
|
||||
author: string
|
||||
tablePrefix: string
|
||||
isOverride: boolean
|
||||
createTime?: string
|
||||
updateTime?: string
|
||||
}
|
||||
export interface GeneratorConfigResp {
|
||||
genConfig: GenConfigResp
|
||||
fieldConfigs: FieldConfigResp[]
|
||||
}
|
||||
export interface GeneratePreviewResp {
|
||||
fileName: string
|
||||
content: string
|
||||
}
|
224
src/views/tool/generator/GenConfigDrawer.vue
Normal file
224
src/views/tool/generator/GenConfigDrawer.vue
Normal file
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
v-model:visible="visible"
|
||||
:title="title"
|
||||
:mask-closable="false"
|
||||
:esc-to-close="false"
|
||||
:width="width >= 1000 ? 1000 : '100%'"
|
||||
@before-ok="save"
|
||||
@close="reset"
|
||||
>
|
||||
<a-card title="字段配置" class="field-config">
|
||||
<template #extra>
|
||||
<a-popconfirm
|
||||
content="是否确定同步最新数据表结构?同步后只要不点击确定保存,则不影响原有配置数据。"
|
||||
type="warning"
|
||||
@ok="handleRefresh(form.tableName)"
|
||||
>
|
||||
<a-tooltip content="同步最新数据表结构">
|
||||
<a-button
|
||||
type="primary"
|
||||
status="success"
|
||||
size="small"
|
||||
title="同步"
|
||||
:disabled="dataList.length !== 0 && dataList[0].createTime === null"
|
||||
>
|
||||
<template #icon><icon-sync /></template>同步
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
<GiTable
|
||||
row-key="id"
|
||||
:data="dataList"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:scroll="{ y: 400 }"
|
||||
:pagination="false"
|
||||
:disabledTools="['setting', 'refresh']"
|
||||
:disabledColumnKeys="['tableName']"
|
||||
>
|
||||
<template #fieldType="{ record }">
|
||||
<span v-if="record.fieldType">{{ record.fieldType }}</span>
|
||||
<a-tooltip v-else content="请检查 generator.properties 配置">
|
||||
<icon-exclamation-circle-fill size="large" style="color: #f53f3f" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #comment="{ record }">
|
||||
<a-input v-model="record.comment" />
|
||||
</template>
|
||||
<template #showInList="{ record }">
|
||||
<a-checkbox v-model="record.showInList" value="true" />
|
||||
</template>
|
||||
<template #showInForm="{ record }">
|
||||
<a-checkbox v-model="record.showInForm" value="true" />
|
||||
</template>
|
||||
<template #isRequired="{ record }">
|
||||
<a-checkbox v-if="record.showInForm" v-model="record.isRequired" value="true" />
|
||||
<a-checkbox v-else disabled />
|
||||
</template>
|
||||
<template #showInQuery="{ record }">
|
||||
<a-checkbox v-model="record.showInQuery" value="true" />
|
||||
</template>
|
||||
<template #formType="{ record }">
|
||||
<a-select
|
||||
v-if="record.showInForm || record.showInQuery"
|
||||
v-model="record.formType"
|
||||
:options="form_type_enum"
|
||||
placeholder="请选择表单类型"
|
||||
/>
|
||||
<span v-else>无需设置</span>
|
||||
</template>
|
||||
<template #queryType="{ record }">
|
||||
<a-select
|
||||
v-if="record.showInQuery"
|
||||
v-model="record.queryType"
|
||||
:options="query_type_enum"
|
||||
placeholder="请选择查询方式"
|
||||
/>
|
||||
<span v-else>无需设置</span>
|
||||
</template>
|
||||
</GiTable>
|
||||
</a-card>
|
||||
<a-card title="生成配置" style="margin-top: 10px">
|
||||
<a-form ref="formRef" :model="form" :rules="rules" class="gen-config" size="large">
|
||||
<a-form-item label="作者名称" field="author">
|
||||
<a-input v-model="form.author" placeholder="请输入作者名称" />
|
||||
</a-form-item>
|
||||
<a-form-item label="业务名称" field="businessName">
|
||||
<a-input v-model="form.businessName" placeholder="自定义业务名称,例如:用户" />
|
||||
</a-form-item>
|
||||
<a-form-item label="所属模块" field="moduleName">
|
||||
<a-input v-model="form.moduleName" placeholder="项目模块名称,例如:continew-admin-system" />
|
||||
</a-form-item>
|
||||
<a-form-item label="模块包名" field="packageName">
|
||||
<a-input v-model="form.packageName" placeholder="项目模块包名,例如:top.charles7c.continew.admin.system" />
|
||||
</a-form-item>
|
||||
<a-form-item label="去表前缀" field="tablePrefix">
|
||||
<a-input v-model="form.tablePrefix" placeholder="数据库表前缀,例如:sys_" />
|
||||
</a-form-item>
|
||||
<a-form-item label="是否覆盖" field="isOverride">
|
||||
<a-radio-group v-model="form.isOverride" type="button">
|
||||
<a-radio :value="true">是</a-radio>
|
||||
<a-radio :value="false">否</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { listFieldConfig, getGenConfig, saveGenConfig, type FieldConfigResp, type GeneratorConfigResp } from '@/apis'
|
||||
import { Message, type FormInstance } from '@arco-design/web-vue'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
import { useForm } from '@/hooks'
|
||||
import { useDict } from '@/hooks/app'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
|
||||
const { width } = useWindowSize()
|
||||
const { form_type_enum, query_type_enum } = useDict('form_type_enum', 'query_type_enum')
|
||||
|
||||
// Table 字段配置
|
||||
const columns: TableInstanceColumns[] = [
|
||||
{ title: '名称', dataIndex: 'fieldName', width: 125, ellipsis: true, tooltip: true },
|
||||
{ title: '类型', dataIndex: 'fieldType' },
|
||||
{ title: '描述', slotName: 'comment', width: 170 },
|
||||
{ title: '列表', slotName: 'showInList', width: 60, align: 'center' },
|
||||
{ title: '表单', slotName: 'showInForm', width: 60, align: 'center' },
|
||||
{ title: '必填', slotName: 'isRequired', width: 60, align: 'center' },
|
||||
{ title: '查询', slotName: 'showInQuery', width: 60, align: 'center' },
|
||||
{ title: '表单类型', slotName: 'formType', width: 150 },
|
||||
{ title: '查询方式', slotName: 'queryType' }
|
||||
]
|
||||
|
||||
const dataList = ref<FieldConfigResp[]>([])
|
||||
const loading = ref(false)
|
||||
// 查询列表数据
|
||||
const getDataList = async (tableName: string, requireSync: boolean) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await listFieldConfig(tableName, requireSync)
|
||||
dataList.value = res.data
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Form 生成配置
|
||||
const formRef = ref<FormInstance>()
|
||||
const rules: FormInstance['rules'] = {
|
||||
author: [{ required: true, message: '请输入作者名称' }],
|
||||
moduleName: [{ required: true, message: '请输入所属模块' }],
|
||||
packageName: [{ required: true, message: '请输入模块包名' }],
|
||||
businessName: [{ required: true, message: '请输入业务名称' }]
|
||||
}
|
||||
|
||||
const { form, resetForm } = useForm({
|
||||
author: '',
|
||||
businessName: '',
|
||||
moduleName: '',
|
||||
packageName: '',
|
||||
tablePrefix: '',
|
||||
isOverride: false
|
||||
})
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
formRef.value?.resetFields()
|
||||
resetForm()
|
||||
}
|
||||
|
||||
const title = ref('')
|
||||
const visible = ref(false)
|
||||
// 配置
|
||||
const onConfig = async (tableName: string, comment: string) => {
|
||||
comment = comment ? `(${comment})` : ' '
|
||||
title.value = `${tableName}${comment}配置`
|
||||
visible.value = true
|
||||
// 查询字段配置
|
||||
await getDataList(tableName, false)
|
||||
// 查询生成配置
|
||||
const res = await getGenConfig(tableName)
|
||||
Object.assign(form, res.data)
|
||||
form.isOverride = false
|
||||
}
|
||||
|
||||
// 同步
|
||||
const handleRefresh = async (tableName: string) => {
|
||||
await getDataList(tableName, true)
|
||||
}
|
||||
|
||||
// 保存
|
||||
const save = async () => {
|
||||
try {
|
||||
const isInvalid = await formRef.value?.validate()
|
||||
if (isInvalid) return false
|
||||
await saveGenConfig(form.tableName, {
|
||||
genConfig: form,
|
||||
fieldConfigs: dataList.value
|
||||
} as GeneratorConfigResp)
|
||||
Message.success('保存成功')
|
||||
emit('save-success')
|
||||
return true
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'save-success'): void
|
||||
}>()
|
||||
|
||||
defineExpose({ onConfig })
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.field-config :deep(.arco-card-body) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.gen-config.arco-form) {
|
||||
width: 50%;
|
||||
}
|
||||
</style>
|
58
src/views/tool/generator/GenPreviewModal.vue
Normal file
58
src/views/tool/generator/GenPreviewModal.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible" title="生成预览" :mask-closable="false" :esc-to-close="false" width="90%">
|
||||
<div>
|
||||
<a-scrollbar style="height: 700px; overflow: auto">
|
||||
<a-link style="position: absolute; right: 20px; top: 50px; z-index: 999" @click="onCopy">
|
||||
<template #icon>
|
||||
<icon-copy size="large" />
|
||||
</template>
|
||||
复制
|
||||
</a-link>
|
||||
<a-tabs size="large" @tab-click="onTabClick">
|
||||
<a-tab-pane v-for="item in genPreviewList" :key="item.fileName" :title="item.fileName">
|
||||
<GiCodeView
|
||||
:type="'vue' === item.fileName.split('.')[1] ? 'vue' : 'javascript'"
|
||||
:code-json="item.content"
|
||||
></GiCodeView>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-scrollbar>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { genPreview, type GeneratePreviewResp } from '@/apis'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
|
||||
const { copy, copied } = useClipboard()
|
||||
const copyCodeContent = ref()
|
||||
const genPreviewList = ref<GeneratePreviewResp[]>([])
|
||||
|
||||
const visible = ref(false)
|
||||
// 打开
|
||||
const onPreview = async (tableName: string) => {
|
||||
const res = await genPreview(tableName)
|
||||
genPreviewList.value = res.data
|
||||
copyCodeContent.value = genPreviewList.value[0].content
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
// 点击 Tab
|
||||
const onTabClick = (key: any) => {
|
||||
copyCodeContent.value = genPreviewList.value.filter((p) => p.fileName === key)[0].content
|
||||
}
|
||||
|
||||
// 复制
|
||||
const onCopy = () => {
|
||||
copy(copyCodeContent.value)
|
||||
}
|
||||
watch(copied, () => {
|
||||
if (copied.value) {
|
||||
Message.success('复制成功')
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({ onPreview })
|
||||
</script>
|
127
src/views/tool/generator/index.vue
Normal file
127
src/views/tool/generator/index.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="gi_page">
|
||||
<a-card title="代码生成" class="general-card">
|
||||
<GiTable
|
||||
row-key="id"
|
||||
:data="dataList"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
|
||||
:pagination="pagination"
|
||||
:disabledTools="['setting']"
|
||||
:disabledColumnKeys="['tableName']"
|
||||
@refresh="search"
|
||||
>
|
||||
<template #custom-left>
|
||||
<a-input v-model="queryForm.tableName" placeholder="请输入表名称" allow-clear @change="search">
|
||||
<template #prefix><icon-search /></template>
|
||||
</a-input>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<a-space>
|
||||
<a-link @click="onConfig(record.tableName, record.comment)">配置</a-link>
|
||||
<a-link
|
||||
:title="record.isConfiged ? '生成预览' : '请先进行生成配置'"
|
||||
:disabled="!record.isConfiged"
|
||||
@click="onPreview(record.tableName)"
|
||||
>预览</a-link
|
||||
>
|
||||
<a-link
|
||||
:title="record.isConfiged ? '生成' : '请先进行生成配置'"
|
||||
:disabled="!record.isConfiged"
|
||||
@click="onGenerate([record.tableName])"
|
||||
>生成</a-link
|
||||
>
|
||||
</a-space>
|
||||
</template>
|
||||
</GiTable>
|
||||
</a-card>
|
||||
|
||||
<GenConfigDrawer ref="GenConfigDrawerRef" @save-success="search" />
|
||||
<GenPreviewModal ref="GenPreviewModalRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { listGenerator, generate } from '@/apis'
|
||||
import GenConfigDrawer from './GenConfigDrawer.vue'
|
||||
import GenPreviewModal from './GenPreviewModal.vue'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
import { useTable } from '@/hooks'
|
||||
import { isMobile } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'Generator' })
|
||||
|
||||
const columns: TableInstanceColumns[] = [
|
||||
{
|
||||
title: '序号',
|
||||
width: 66,
|
||||
align: 'center',
|
||||
render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize)
|
||||
},
|
||||
{ title: '表名称', dataIndex: 'tableName', width: 225 },
|
||||
{ title: '描述', dataIndex: 'comment', tooltip: true },
|
||||
{ title: '存储引擎', dataIndex: 'engine', align: 'center' },
|
||||
{ title: '字符集', dataIndex: 'charset' },
|
||||
{ title: '创建时间', dataIndex: 'createTime' },
|
||||
{ title: '操作', slotName: 'action', width: 180, align: 'center', fixed: !isMobile() ? 'right' : undefined }
|
||||
]
|
||||
|
||||
const queryForm = reactive({
|
||||
tableName: undefined,
|
||||
sort: ['createTime,desc']
|
||||
})
|
||||
|
||||
const {
|
||||
tableData: dataList,
|
||||
loading,
|
||||
pagination,
|
||||
search
|
||||
} = useTable((p) => listGenerator({ ...queryForm, page: p.page, size: p.size }), { immediate: true })
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
queryForm.tableName = undefined
|
||||
search()
|
||||
}
|
||||
|
||||
const GenConfigDrawerRef = ref<InstanceType<typeof GenConfigDrawer>>()
|
||||
// 配置
|
||||
const onConfig = (tableName: string, comment: string) => {
|
||||
GenConfigDrawerRef.value?.onConfig(tableName, comment)
|
||||
}
|
||||
|
||||
const GenPreviewModalRef = ref<InstanceType<typeof GenPreviewModal>>()
|
||||
// 预览
|
||||
const onPreview = (tableName: string) => {
|
||||
GenPreviewModalRef.value?.onPreview(tableName)
|
||||
}
|
||||
|
||||
// 生成
|
||||
const onGenerate = async (tableNames: Array<string>) => {
|
||||
const res = await generate(tableNames)
|
||||
const contentDisposition = res.headers['content-disposition']
|
||||
const pattern = new RegExp('filename=([^;]+\\.[^\\.;]+);*')
|
||||
const result = pattern.exec(contentDisposition) || ''
|
||||
// 对名字进行解码
|
||||
const fileName = window.decodeURI(result[1])
|
||||
// 创建下载的链接
|
||||
const blob = new Blob([res.data])
|
||||
const downloadElement = document.createElement('a')
|
||||
const href = window.URL.createObjectURL(blob)
|
||||
downloadElement.style.display = 'none'
|
||||
downloadElement.href = href
|
||||
// 下载后文件名
|
||||
downloadElement.download = fileName
|
||||
document.body.appendChild(downloadElement)
|
||||
// 点击下载
|
||||
downloadElement.click()
|
||||
// 下载完成,移除元素
|
||||
document.body.removeChild(downloadElement)
|
||||
// 释放掉 blob 对象
|
||||
window.URL.revokeObjectURL(href)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
Reference in New Issue
Block a user