物业代码生成
This commit is contained in:
218
apps/web-antd/src/views/tool/gen/code-preview-modal.vue
Normal file
218
apps/web-antd/src/views/tool/gen/code-preview-modal.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<script setup lang="ts">
|
||||
import type { Key } from 'ant-design-vue/es/vc-tree/interface';
|
||||
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import type { LanguageSupport } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { markRaw, ref } from 'vue';
|
||||
|
||||
import { CodeMirror, useVbenModal } from '@vben/common-ui';
|
||||
import {
|
||||
DefaultFileIcon,
|
||||
FolderIcon,
|
||||
JavaIcon,
|
||||
SqlIcon,
|
||||
TsIcon,
|
||||
VueIcon,
|
||||
XmlIcon,
|
||||
} from '@vben/icons';
|
||||
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { Alert, Skeleton, Tree } from 'ant-design-vue';
|
||||
|
||||
import { previewCode } from '#/api/tool/gen';
|
||||
|
||||
interface TreeNode {
|
||||
children: TreeNode[];
|
||||
title: string;
|
||||
key: string;
|
||||
icon: Component; // 树左边图标
|
||||
}
|
||||
|
||||
const treeData = ref<TreeNode[]>([]);
|
||||
/** modal标题 */
|
||||
const modalTitle = ref('代码预览');
|
||||
/** 代码内容 */
|
||||
const codeContent = ref('点击左侧树节点查看代码');
|
||||
/** code */
|
||||
const currentCodeData = ref<null | Recordable<any>>(null);
|
||||
|
||||
const [BasicModal, modalApi] = useVbenModal({
|
||||
async onOpenChange(isOpen) {
|
||||
if (!isOpen) {
|
||||
handleClose();
|
||||
return null;
|
||||
}
|
||||
modalApi.modalLoading(true);
|
||||
|
||||
const { tableId } = modalApi.getData() as { tableId: string };
|
||||
const data = await previewCode(tableId);
|
||||
currentCodeData.value = data;
|
||||
const tree = convertToTree(Object.keys(data));
|
||||
treeData.value = tree;
|
||||
|
||||
modalApi.modalLoading(false);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 文件路径数组转树结构
|
||||
* @param paths 文件路径数组
|
||||
*/
|
||||
function convertToTree(paths: string[]): TreeNode[] {
|
||||
const tree: TreeNode[] = [];
|
||||
|
||||
for (const path of paths) {
|
||||
const segments = path.split('/');
|
||||
let currentNode = tree;
|
||||
let currentPath = '';
|
||||
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
const segment = segments[i];
|
||||
currentPath += `${segment}`;
|
||||
if (i !== segments.length - 1) {
|
||||
currentPath += '/';
|
||||
}
|
||||
|
||||
const existingNode = currentNode.find((node) => node.title === segment);
|
||||
|
||||
if (existingNode) {
|
||||
currentNode = existingNode.children || [];
|
||||
} else {
|
||||
const title = (segment ?? '').replace('.vm', '');
|
||||
const newNode: TreeNode = {
|
||||
icon: findIcon(currentPath),
|
||||
key: currentPath,
|
||||
title,
|
||||
children: [],
|
||||
};
|
||||
currentNode.push(newNode);
|
||||
currentNode = newNode.children;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
const iconMap = [
|
||||
{ key: 'java', value: markRaw(JavaIcon) },
|
||||
{ key: 'xml', value: markRaw(XmlIcon) },
|
||||
{ key: 'sql', value: markRaw(SqlIcon) },
|
||||
{ key: 'ts', value: markRaw(TsIcon) },
|
||||
{ key: 'vue', value: markRaw(VueIcon) },
|
||||
{ key: 'folder', value: markRaw(FolderIcon) },
|
||||
];
|
||||
function findIcon(path: string) {
|
||||
const defaultFileIcon = DefaultFileIcon;
|
||||
const defaultFolderIcon = FolderIcon;
|
||||
if (path.endsWith('.vm')) {
|
||||
const realPath = path.slice(0, -3);
|
||||
// 是否为指定拓展名
|
||||
const icon = iconMap.find((item) => realPath.endsWith(item.key));
|
||||
if (icon) {
|
||||
return icon.value;
|
||||
}
|
||||
return defaultFileIcon;
|
||||
}
|
||||
// 其他的为文件夹
|
||||
return defaultFolderIcon;
|
||||
}
|
||||
|
||||
const language = ref<LanguageSupport>('html');
|
||||
function changeLanguageType(filename: string) {
|
||||
const typeList: { language: LanguageSupport; type: string }[] = [
|
||||
{ language: 'ts', type: '.ts' },
|
||||
{ language: 'java', type: '.java' },
|
||||
{ language: 'xml', type: '.xml' },
|
||||
{ language: 'sql', type: 'sql' },
|
||||
{ language: 'vue', type: '.vue' },
|
||||
];
|
||||
const type = typeList.find((item) => filename.includes(item.type));
|
||||
language.value = type ? type.language : 'html';
|
||||
}
|
||||
|
||||
function handleSelect(selectedKeys: Key[]) {
|
||||
const [currentFile = ''] = selectedKeys as string[];
|
||||
if (!currentCodeData.value) {
|
||||
return;
|
||||
}
|
||||
const currentCode =
|
||||
currentCodeData.value[currentFile as keyof typeof currentCodeData.value];
|
||||
if (currentCode) {
|
||||
// 设置代码type
|
||||
changeLanguageType(currentFile);
|
||||
// 内容
|
||||
codeContent.value = currentCode;
|
||||
// 修改标题
|
||||
modalTitle.value = `代码预览: ${currentFile.replace('.vm', '')}`;
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
currentCodeData.value = null;
|
||||
codeContent.value = '点击左侧树节点查看代码';
|
||||
modalTitle.value = '代码预览';
|
||||
language.value = 'html';
|
||||
}
|
||||
|
||||
const { copy } = useClipboard({ legacy: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasicModal
|
||||
:footer="false"
|
||||
:fullscreen="true"
|
||||
:fullscreen-button="false"
|
||||
:title="modalTitle"
|
||||
>
|
||||
<div v-if="currentCodeData" class="flex gap-[8px]">
|
||||
<div class="h-[calc(100vh-80px)] w-[300px] overflow-y-scroll">
|
||||
<Tree
|
||||
v-if="treeData.length > 0"
|
||||
:show-line="{ showLeafIcon: false }"
|
||||
:tree-data="treeData"
|
||||
:virtual="false"
|
||||
default-expand-all
|
||||
@select="handleSelect"
|
||||
>
|
||||
<template #title="{ title, icon }">
|
||||
<div class="flex items-center gap-[16px]">
|
||||
<component :is="icon" />
|
||||
<span>{{ title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Tree>
|
||||
<Alert
|
||||
class="mt-2"
|
||||
show-icon
|
||||
message="👆显示的名称为模板的文件名,非最终下载文件名..."
|
||||
/>
|
||||
</div>
|
||||
<CodeMirror
|
||||
v-model="codeContent"
|
||||
:language="language"
|
||||
class="h-[calc(100vh-80px)] w-full overflow-y-scroll text-[16px]"
|
||||
readonly
|
||||
/>
|
||||
<div class="fixed right-20 top-20">
|
||||
<a-button @click="copy(codeContent)">复制</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<Skeleton v-if="!currentCodeData" active />
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.ant-tree .ant-tree-switcher) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/** codeMirror 占满容器高度 即calc计算的高度 */
|
||||
:deep(.cm-editor) {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
60
apps/web-antd/src/views/tool/gen/data.tsx
Normal file
60
apps/web-antd/src/views/tool/gen/data.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { FormSchemaGetter } from '#/adapter/form';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
export const querySchema: FormSchemaGetter = () => [
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'dataName',
|
||||
label: '数据源',
|
||||
defaultValue: '',
|
||||
componentProps: {
|
||||
allowClear: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'tableName',
|
||||
label: '表名称',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'tableComment',
|
||||
label: '表描述',
|
||||
},
|
||||
{
|
||||
component: 'RangePicker',
|
||||
fieldName: 'createTime',
|
||||
label: '创建时间',
|
||||
},
|
||||
];
|
||||
|
||||
export const columns: VxeGridProps['columns'] = [
|
||||
{ type: 'checkbox', width: 60 },
|
||||
{
|
||||
field: 'tableName',
|
||||
title: '表名称',
|
||||
},
|
||||
{
|
||||
field: 'tableComment',
|
||||
title: '表描述',
|
||||
},
|
||||
{
|
||||
field: 'className',
|
||||
title: '实体类',
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '创建时间',
|
||||
},
|
||||
{
|
||||
field: 'updateTime',
|
||||
title: '更新时间',
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 300,
|
||||
},
|
||||
];
|
120
apps/web-antd/src/views/tool/gen/edit-gen.vue
Normal file
120
apps/web-antd/src/views/tool/gen/edit-gen.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<script setup lang="ts">
|
||||
import type { GenInfo } from '#/api/tool/gen/model';
|
||||
|
||||
import { onMounted, provide, ref, unref, useTemplateRef } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { useTabs } from '@vben/hooks';
|
||||
import { cloneDeep, safeParseNumber } from '@vben/utils';
|
||||
|
||||
import { Card, Skeleton, TabPane, Tabs } from 'ant-design-vue';
|
||||
|
||||
import { editSave, genInfo } from '#/api/tool/gen';
|
||||
|
||||
import { BasicSetting, GenConfig } from './edit-steps';
|
||||
|
||||
const { setTabTitle, closeCurrentTab } = useTabs();
|
||||
const routes = useRoute();
|
||||
// 获取路由参数
|
||||
const tableId = routes.params.tableId as string;
|
||||
|
||||
const genInfoData = ref<GenInfo['info']>();
|
||||
|
||||
provide('genInfoData', genInfoData);
|
||||
|
||||
onMounted(async () => {
|
||||
const resp = await genInfo(tableId);
|
||||
// 需要做菜单转换 严格相等 才能选中回显
|
||||
resp.info.parentMenuId = safeParseNumber(resp.info.parentMenuId);
|
||||
genInfoData.value = resp.info;
|
||||
setTabTitle(`生成配置: ${resp.info.tableName}`);
|
||||
});
|
||||
|
||||
const currentTab = ref<'fields' | 'setting'>('setting');
|
||||
const basicSettingRef = useTemplateRef('basicSettingRef');
|
||||
const genConfigRef = useTemplateRef('genConfigRef');
|
||||
|
||||
const router = useRouter();
|
||||
async function handleSave() {
|
||||
try {
|
||||
// 校验tab1
|
||||
const settingValidate = await basicSettingRef.value?.validateForm();
|
||||
if (!settingValidate) {
|
||||
currentTab.value = 'setting';
|
||||
return;
|
||||
}
|
||||
// 校验tab2
|
||||
const genConfigValidate = await genConfigRef.value?.validateTable();
|
||||
if (!genConfigValidate) {
|
||||
currentTab.value = 'fields';
|
||||
return;
|
||||
}
|
||||
const requestData = cloneDeep(unref(genInfoData)!);
|
||||
// 获取表单数据
|
||||
const formValues = await basicSettingRef.value?.getFormValues();
|
||||
// 合并
|
||||
Object.assign(requestData, formValues);
|
||||
// 从表格获取最新的
|
||||
requestData.columns = genConfigRef.value?.getTableRecords() ?? [];
|
||||
// 树表需要添加这个参数
|
||||
if (requestData && requestData.tplCategory === 'tree') {
|
||||
const { treeCode, treeName, treeParentCode } = requestData;
|
||||
requestData.params = {
|
||||
treeCode,
|
||||
treeName,
|
||||
treeParentCode,
|
||||
};
|
||||
}
|
||||
// 需要进行参数转化
|
||||
if (requestData) {
|
||||
const transform = (ret: boolean) => (ret ? '1' : '0');
|
||||
requestData.columns.forEach((column) => {
|
||||
const { edit, insert, query, required, list } = column;
|
||||
column.isInsert = transform(insert);
|
||||
column.isEdit = transform(edit);
|
||||
column.isList = transform(list);
|
||||
column.isQuery = transform(query);
|
||||
column.isRequired = transform(required);
|
||||
});
|
||||
// 需要手动添加父级菜单 弹窗类型
|
||||
requestData.params = {
|
||||
...requestData.params,
|
||||
parentMenuId: requestData.parentMenuId,
|
||||
popupComponent: requestData.popupComponent,
|
||||
formComponent: requestData.formComponent,
|
||||
};
|
||||
}
|
||||
// 保存
|
||||
await editSave(requestData);
|
||||
// 关闭 & 跳转
|
||||
await closeCurrentTab();
|
||||
router.push({ path: '/tool/gen', replace: true });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="true">
|
||||
<Card
|
||||
class="h-full"
|
||||
v-if="genInfoData"
|
||||
:body-style="{ padding: '0 16px 16px' }"
|
||||
>
|
||||
<Tabs v-model:active-key="currentTab" size="middle">
|
||||
<template #rightExtra>
|
||||
<a-button type="primary" @click="handleSave">保存配置</a-button>
|
||||
</template>
|
||||
<TabPane key="setting" tab="生成信息" :force-render="true">
|
||||
<BasicSetting ref="basicSettingRef" />
|
||||
</TabPane>
|
||||
<TabPane key="fields" tab="字段信息" :force-render="true">
|
||||
<GenConfig ref="genConfigRef" />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
<Skeleton v-else :active="true" />
|
||||
</Page>
|
||||
</template>
|
153
apps/web-antd/src/views/tool/gen/edit-steps/basic-setting.vue
Normal file
153
apps/web-antd/src/views/tool/gen/edit-steps/basic-setting.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { Column, GenInfo } from '#/api/tool/gen/model';
|
||||
|
||||
import { inject, onMounted } from 'vue';
|
||||
|
||||
import { useVbenForm } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { addFullName, listToTree } from '@vben/utils';
|
||||
|
||||
import { Col, Row } from 'ant-design-vue';
|
||||
|
||||
import { menuList } from '#/api/system/menu';
|
||||
|
||||
import { formSchema } from './basic';
|
||||
|
||||
/**
|
||||
* 从父组件注入
|
||||
*/
|
||||
const genInfoData = inject('genInfoData') as Ref<GenInfo['info']>;
|
||||
|
||||
const [BasicForm, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
formItemClass: 'col-span-1',
|
||||
},
|
||||
labelWidth: 150,
|
||||
},
|
||||
schema: formSchema(),
|
||||
showDefaultActions: false,
|
||||
wrapperClass: 'grid-cols-2',
|
||||
});
|
||||
|
||||
/**
|
||||
* 树表需要用到的数据
|
||||
*/
|
||||
async function initTreeSelect(columns: Column[]) {
|
||||
const options = columns.map((item) => {
|
||||
const label = `${item.columnName} | ${item.columnComment}`;
|
||||
return { label, value: item.columnName };
|
||||
});
|
||||
formApi.updateSchema([
|
||||
{
|
||||
componentProps: {
|
||||
options,
|
||||
},
|
||||
fieldName: 'treeCode',
|
||||
},
|
||||
{
|
||||
componentProps: {
|
||||
options,
|
||||
},
|
||||
fieldName: 'treeParentCode',
|
||||
},
|
||||
{
|
||||
componentProps: {
|
||||
options,
|
||||
},
|
||||
fieldName: 'treeName',
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载菜单选择
|
||||
*/
|
||||
async function initMenuSelect() {
|
||||
const list = await menuList();
|
||||
// support i18n
|
||||
list.forEach((item) => {
|
||||
item.menuName = $t(item.menuName);
|
||||
});
|
||||
const tree = listToTree(list, { id: 'menuId', pid: 'parentId' });
|
||||
const treeData = [
|
||||
{
|
||||
fullName: $t('menu.root'),
|
||||
menuId: 0,
|
||||
menuName: $t('menu.root'),
|
||||
children: tree,
|
||||
},
|
||||
];
|
||||
addFullName(treeData, 'menuName', ' / ');
|
||||
|
||||
formApi.updateSchema([
|
||||
{
|
||||
componentProps: {
|
||||
fieldNames: {
|
||||
label: 'menuName',
|
||||
value: 'menuId',
|
||||
},
|
||||
// 设置弹窗滚动高度 默认256
|
||||
listHeight: 300,
|
||||
treeData,
|
||||
treeDefaultExpandAll: false,
|
||||
// 默认展开的树节点
|
||||
treeDefaultExpandedKeys: [0],
|
||||
treeLine: { showLeafIcon: false },
|
||||
treeNodeLabelProp: 'fullName',
|
||||
},
|
||||
fieldName: 'parentMenuId',
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const info = genInfoData.value;
|
||||
await formApi.setValues(info);
|
||||
// 弹出框类型需要手动赋值
|
||||
if (info.options) {
|
||||
const { popupComponent, formComponent } = JSON.parse(info.options);
|
||||
if (popupComponent) {
|
||||
formApi.setFieldValue('popupComponent', popupComponent);
|
||||
}
|
||||
if (formComponent) {
|
||||
formApi.setFieldValue('formComponent', formComponent);
|
||||
}
|
||||
}
|
||||
await Promise.all([initTreeSelect(info.columns), initMenuSelect()]);
|
||||
});
|
||||
|
||||
/**
|
||||
* 校验表单
|
||||
*/
|
||||
async function validateForm() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表单值
|
||||
*/
|
||||
async function getFormValues() {
|
||||
return await formApi.getValues();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validateForm,
|
||||
getFormValues,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Row justify="center">
|
||||
<Col v-bind="{ xs: 24, sm: 24, md: 20, lg: 16, xl: 16 }">
|
||||
<BasicForm />
|
||||
</Col>
|
||||
</Row>
|
||||
</template>
|
212
apps/web-antd/src/views/tool/gen/edit-steps/basic.tsx
Normal file
212
apps/web-antd/src/views/tool/gen/edit-steps/basic.tsx
Normal file
@@ -0,0 +1,212 @@
|
||||
import type { FormSchemaGetter } from '#/adapter/form';
|
||||
|
||||
import { getPopupContainer } from '@vben/utils';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
|
||||
export const formSchema: FormSchemaGetter = () => [
|
||||
{
|
||||
component: 'Divider',
|
||||
componentProps: {
|
||||
orientation: 'left',
|
||||
},
|
||||
fieldName: 'divider1',
|
||||
formItemClass: 'col-span-2',
|
||||
label: '基本信息',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'tableName',
|
||||
label: '表名称',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'tableComment',
|
||||
label: '表描述',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'className',
|
||||
label: '实体类名称',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'functionAuthor',
|
||||
label: '作者',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Divider',
|
||||
componentProps: {
|
||||
orientation: 'left',
|
||||
},
|
||||
fieldName: 'divider2',
|
||||
formItemClass: 'col-span-2',
|
||||
label: '生成信息',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: false,
|
||||
getPopupContainer,
|
||||
options: [
|
||||
{ label: '单表(增删改查)', value: 'crud' },
|
||||
{ label: '树表(增删改查)', value: 'tree' },
|
||||
],
|
||||
},
|
||||
defaultValue: 'crud',
|
||||
fieldName: 'tplCategory',
|
||||
label: '模板类型',
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
getPopupContainer,
|
||||
},
|
||||
dependencies: {
|
||||
show: (values) => values.tplCategory === 'tree',
|
||||
triggerFields: ['tplCategory'],
|
||||
},
|
||||
fieldName: 'treeCode',
|
||||
helpMessage: '树节点显示的编码字段名, 如: dept_id (相当于id)',
|
||||
label: '树编码字段',
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: false,
|
||||
},
|
||||
dependencies: {
|
||||
show: (values) => values.tplCategory === 'tree',
|
||||
triggerFields: ['tplCategory'],
|
||||
},
|
||||
fieldName: 'treeParentCode',
|
||||
help: '树节点显示的父编码字段名, 如: parent_Id (相当于parentId)',
|
||||
label: '树父编码字段',
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: false,
|
||||
},
|
||||
dependencies: {
|
||||
show: (values) => values.tplCategory === 'tree',
|
||||
triggerFields: ['tplCategory'],
|
||||
},
|
||||
fieldName: 'treeName',
|
||||
help: '树节点的显示名称字段名, 如: dept_name (相当于label)',
|
||||
label: '树名称字段',
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'packageName',
|
||||
help: '生成在哪个java包下, 例如 com.ruoyi.system',
|
||||
label: '生成包路径',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'moduleName',
|
||||
help: '可理解为子系统名,例如 system',
|
||||
label: '生成模块名',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'businessName',
|
||||
help: '可理解为功能英文名,例如 user',
|
||||
label: '生成业务名',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'functionName',
|
||||
help: '用作类描述,例如 用户',
|
||||
label: '生成功能名',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'TreeSelect',
|
||||
componentProps: {
|
||||
allowClear: false,
|
||||
getPopupContainer,
|
||||
},
|
||||
defaultValue: 0,
|
||||
fieldName: 'parentMenuId',
|
||||
label: '上级菜单',
|
||||
},
|
||||
{
|
||||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
buttonStyle: 'solid',
|
||||
options: [
|
||||
{ label: 'modal弹窗', value: 'modal' },
|
||||
{ label: 'drawer抽屉', value: 'drawer' },
|
||||
],
|
||||
optionType: 'button',
|
||||
},
|
||||
help: '自定义功能, 需要后端支持',
|
||||
defaultValue: 'modal',
|
||||
fieldName: 'popupComponent',
|
||||
label: '弹窗组件类型',
|
||||
},
|
||||
{
|
||||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
buttonStyle: 'solid',
|
||||
options: [
|
||||
{ label: 'useVbenForm', value: 'useForm' },
|
||||
{ label: 'antd原生表单', value: 'native' },
|
||||
],
|
||||
optionType: 'button',
|
||||
},
|
||||
help: '自定义功能, 需要后端支持\n复杂(布局, 联动等)表单建议用antd原生表单',
|
||||
defaultValue: 'useForm',
|
||||
fieldName: 'formComponent',
|
||||
label: '生成表单类型',
|
||||
},
|
||||
{
|
||||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
buttonStyle: 'solid',
|
||||
options: [
|
||||
{ label: 'zip压缩包', value: '0' },
|
||||
{ label: '自定义路径', value: '1' },
|
||||
],
|
||||
optionType: 'button',
|
||||
},
|
||||
defaultValue: '0',
|
||||
fieldName: 'genType',
|
||||
help: '默认为zip压缩包下载, 也可以自定义生成路径',
|
||||
label: '生成代码方式',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
defaultValue: '/',
|
||||
dependencies: {
|
||||
show: (model) => model.genType === '1',
|
||||
triggerFields: ['genType'],
|
||||
},
|
||||
fieldName: 'genPath',
|
||||
help: '输入绝对路径, 不支持"./"相对路径',
|
||||
label: '代码生成路径',
|
||||
rules: z
|
||||
.string()
|
||||
.regex(/^(?:[a-z]:)?(?:\/|(?:\\|\/)[^\\/:*?"<>|\r\n]+)*(?:\\|\/)?$/i, {
|
||||
message: '请输入合法的路径',
|
||||
}),
|
||||
},
|
||||
{
|
||||
component: 'Textarea',
|
||||
fieldName: 'remark',
|
||||
formItemClass: 'col-span-2 items-baseline',
|
||||
label: '备注',
|
||||
},
|
||||
];
|
72
apps/web-antd/src/views/tool/gen/edit-steps/gen-config.vue
Normal file
72
apps/web-antd/src/views/tool/gen/edit-steps/gen-config.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { GenInfo } from '#/api/tool/gen/model';
|
||||
|
||||
import { inject } from 'vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
|
||||
import { validRules, vxeTableColumns } from './gen-data';
|
||||
|
||||
/**
|
||||
* 从父组件注入
|
||||
*/
|
||||
const genInfoData = inject('genInfoData') as Ref<GenInfo['info']>;
|
||||
|
||||
const gridOptions: VxeGridProps = {
|
||||
columns: vxeTableColumns,
|
||||
keepSource: true,
|
||||
editConfig: { trigger: 'click', mode: 'cell', showStatus: true },
|
||||
editRules: validRules,
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
isCurrent: true, // 高亮当前行
|
||||
},
|
||||
columnConfig: {
|
||||
resizable: true,
|
||||
},
|
||||
proxyConfig: {
|
||||
enabled: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
height: 'auto',
|
||||
pagerConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
data: genInfoData.value.columns,
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({ gridOptions });
|
||||
|
||||
/**
|
||||
* 校验表格数据
|
||||
*/
|
||||
async function validateTable() {
|
||||
const hasError = await tableApi.grid.validate();
|
||||
return !hasError;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表格数据
|
||||
*/
|
||||
function getTableRecords() {
|
||||
return tableApi?.grid?.getData?.() ?? [];
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validateTable,
|
||||
getTableRecords,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-[16px]">
|
||||
<div class="h-[calc(100vh-200px)] overflow-y-hidden">
|
||||
<BasicTable />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
326
apps/web-antd/src/views/tool/gen/edit-steps/gen-data.tsx
Normal file
326
apps/web-antd/src/views/tool/gen/edit-steps/gen-data.tsx
Normal file
@@ -0,0 +1,326 @@
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { reactive } from 'vue';
|
||||
|
||||
import { getPopupContainer } from '@vben/utils';
|
||||
|
||||
import { Checkbox, Input, Select } from 'ant-design-vue';
|
||||
|
||||
import { dictOptionSelectList } from '#/api/system/dict/dict-type';
|
||||
|
||||
const JavaTypes: string[] = [
|
||||
'Long',
|
||||
'String',
|
||||
'Integer',
|
||||
'Double',
|
||||
'BigDecimal',
|
||||
'Date',
|
||||
'Boolean',
|
||||
'LocalDate',
|
||||
'LocalDateTime',
|
||||
];
|
||||
|
||||
const queryTypeOptions = [
|
||||
{ label: '=', value: 'EQ' },
|
||||
{ label: '!=', value: 'NE' },
|
||||
{ label: '>', value: 'GT' },
|
||||
{ label: '>=', value: 'GE' },
|
||||
{ label: '<', value: 'LT' },
|
||||
{ label: '<=', value: 'LE' },
|
||||
{ label: 'LIKE', value: 'LIKE' },
|
||||
{ label: 'BETWEEN', value: 'BETWEEN' },
|
||||
];
|
||||
|
||||
const componentsOptions = [
|
||||
{ label: '文本框', value: 'input' },
|
||||
{ label: '文本域', value: 'textarea' },
|
||||
{ label: '下拉框', value: 'select' },
|
||||
{ label: '单选框', value: 'radio' },
|
||||
{ label: '复选框', value: 'checkbox' },
|
||||
{ label: '日期控件', value: 'datetime' },
|
||||
{ label: '图片上传', value: 'imageUpload' },
|
||||
{ label: '文件上传', value: 'fileUpload' },
|
||||
{ label: '富文本', value: 'editor' },
|
||||
];
|
||||
|
||||
const dictOptions = reactive<{ label: string; value: string }[]>([
|
||||
{ label: '未设置', value: '' },
|
||||
]);
|
||||
/**
|
||||
* 在这里初始化字典下拉框
|
||||
*/
|
||||
(async function init() {
|
||||
const ret = await dictOptionSelectList();
|
||||
|
||||
ret.forEach((dict) => {
|
||||
const option = {
|
||||
label: `${dict.dictName} | ${dict.dictType}`,
|
||||
value: dict.dictType,
|
||||
};
|
||||
dictOptions.push(option);
|
||||
});
|
||||
})();
|
||||
|
||||
function renderBooleanTag(row: Recordable<any>, field: string) {
|
||||
const value = row[field] ? '是' : '否';
|
||||
const className = row[field] ? 'text-green-500' : 'text-red-500';
|
||||
return <span class={className}>{value}</span>;
|
||||
}
|
||||
|
||||
function renderBooleanCheckbox(row: Recordable<any>, field: string) {
|
||||
return <Checkbox v-model:checked={row[field]}></Checkbox>;
|
||||
}
|
||||
|
||||
export const validRules: VxeGridProps['editRules'] = {
|
||||
columnComment: [{ required: true, message: '请输入' }],
|
||||
javaField: [{ required: true, message: '请输入' }],
|
||||
};
|
||||
|
||||
export const vxeTableColumns: VxeGridProps['columns'] = [
|
||||
{
|
||||
title: '序号',
|
||||
type: 'seq',
|
||||
fixed: 'left',
|
||||
width: '50',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '字段列名',
|
||||
field: 'columnName',
|
||||
showOverflow: 'tooltip',
|
||||
fixed: 'left',
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
title: '字段描述',
|
||||
field: 'columnComment',
|
||||
minWidth: 150,
|
||||
slots: {
|
||||
edit: ({ row }) => {
|
||||
return <Input v-model:value={row.columnComment}></Input>;
|
||||
},
|
||||
},
|
||||
editRender: {},
|
||||
},
|
||||
{
|
||||
title: 'db类型',
|
||||
field: 'columnType',
|
||||
minWidth: 120,
|
||||
showOverflow: 'tooltip',
|
||||
},
|
||||
{
|
||||
title: 'Java类型',
|
||||
field: 'javaType',
|
||||
minWidth: 150,
|
||||
slots: {
|
||||
edit: ({ row }) => {
|
||||
const javaTypeOptions = JavaTypes.map((type) => ({
|
||||
label: type,
|
||||
value: type,
|
||||
}));
|
||||
return (
|
||||
<Select
|
||||
class="w-full"
|
||||
getPopupContainer={getPopupContainer}
|
||||
options={javaTypeOptions}
|
||||
v-model:value={row.javaType}
|
||||
></Select>
|
||||
);
|
||||
},
|
||||
},
|
||||
editRender: {},
|
||||
},
|
||||
{
|
||||
title: 'Java属性名',
|
||||
field: 'javaField',
|
||||
minWidth: 150,
|
||||
showOverflow: 'tooltip',
|
||||
slots: {
|
||||
edit: ({ row }) => {
|
||||
return <Input v-model:value={row.javaField}></Input>;
|
||||
},
|
||||
},
|
||||
editRender: {},
|
||||
},
|
||||
{
|
||||
title: '插入',
|
||||
field: 'insert',
|
||||
minWidth: 80,
|
||||
showOverflow: 'tooltip',
|
||||
align: 'center',
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
return renderBooleanTag(row, 'insert');
|
||||
},
|
||||
edit: ({ row }) => {
|
||||
return renderBooleanCheckbox(row, 'insert');
|
||||
},
|
||||
},
|
||||
editRender: {},
|
||||
},
|
||||
{
|
||||
title: '编辑',
|
||||
field: 'edit',
|
||||
showOverflow: 'tooltip',
|
||||
align: 'center',
|
||||
minWidth: 80,
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
return renderBooleanTag(row, 'edit');
|
||||
},
|
||||
edit: ({ row }) => {
|
||||
return renderBooleanCheckbox(row, 'edit');
|
||||
},
|
||||
},
|
||||
editRender: {},
|
||||
},
|
||||
{
|
||||
title: '列表',
|
||||
field: 'list',
|
||||
showOverflow: 'tooltip',
|
||||
align: 'center',
|
||||
minWidth: 80,
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
return renderBooleanTag(row, 'list');
|
||||
},
|
||||
edit: ({ row }) => {
|
||||
return renderBooleanCheckbox(row, 'list');
|
||||
},
|
||||
},
|
||||
editRender: {},
|
||||
},
|
||||
{
|
||||
title: '查询',
|
||||
field: 'query',
|
||||
showOverflow: 'tooltip',
|
||||
align: 'center',
|
||||
minWidth: 80,
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
return renderBooleanTag(row, 'query');
|
||||
},
|
||||
edit: ({ row }) => {
|
||||
return renderBooleanCheckbox(row, 'query');
|
||||
},
|
||||
},
|
||||
editRender: {},
|
||||
},
|
||||
{
|
||||
title: '查询方式',
|
||||
field: 'queryType',
|
||||
showOverflow: 'tooltip',
|
||||
align: 'center',
|
||||
minWidth: 150,
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
const queryType = row.queryType;
|
||||
const found = queryTypeOptions.find((item) => item.value === queryType);
|
||||
if (found) {
|
||||
return found.label;
|
||||
}
|
||||
return queryType;
|
||||
},
|
||||
edit: ({ row }) => {
|
||||
return (
|
||||
<Select
|
||||
class="w-full"
|
||||
getPopupContainer={getPopupContainer}
|
||||
options={queryTypeOptions}
|
||||
v-model:value={row.queryType}
|
||||
></Select>
|
||||
);
|
||||
},
|
||||
},
|
||||
editRender: {},
|
||||
},
|
||||
{
|
||||
title: '必填',
|
||||
field: 'required',
|
||||
showOverflow: 'tooltip',
|
||||
align: 'center',
|
||||
minWidth: 80,
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
return renderBooleanTag(row, 'required');
|
||||
},
|
||||
edit: ({ row }) => {
|
||||
return renderBooleanCheckbox(row, 'required');
|
||||
},
|
||||
},
|
||||
editRender: {},
|
||||
},
|
||||
{
|
||||
title: '显示类型',
|
||||
field: 'htmlType',
|
||||
showOverflow: 'tooltip',
|
||||
minWidth: 150,
|
||||
align: 'center',
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
const htmlType = row.htmlType;
|
||||
const found = componentsOptions.find((item) => item.value === htmlType);
|
||||
if (found) {
|
||||
return found.label;
|
||||
}
|
||||
return htmlType;
|
||||
},
|
||||
edit: ({ row }) => {
|
||||
return (
|
||||
<Select
|
||||
class="w-full"
|
||||
getPopupContainer={getPopupContainer}
|
||||
options={componentsOptions}
|
||||
v-model:value={row.htmlType}
|
||||
></Select>
|
||||
);
|
||||
},
|
||||
},
|
||||
editRender: {},
|
||||
},
|
||||
{
|
||||
title: '字典类型',
|
||||
field: 'dictType',
|
||||
showOverflow: 'tooltip',
|
||||
minWidth: 230,
|
||||
align: 'center',
|
||||
titlePrefix: {
|
||||
message: `仅'下拉框', '单选框', '复选框'支持字典类型`,
|
||||
},
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
const dictType = row.dictType;
|
||||
const found = dictOptions.find((item) => item.value === dictType);
|
||||
if (found) {
|
||||
return found.label;
|
||||
}
|
||||
return dictType;
|
||||
},
|
||||
edit: ({ row }) => {
|
||||
// 清除的回调 需要设置为空字符串 否则不会提交
|
||||
const onDeselect = () => {
|
||||
row.dictType = '';
|
||||
};
|
||||
const disabled =
|
||||
row.htmlType !== 'select' &&
|
||||
row.htmlType !== 'radio' &&
|
||||
row.htmlType !== 'checkbox';
|
||||
return (
|
||||
<Select
|
||||
allowClear={true}
|
||||
class="w-full"
|
||||
disabled={disabled}
|
||||
getPopupContainer={getPopupContainer}
|
||||
onDeselect={onDeselect}
|
||||
options={dictOptions}
|
||||
placeholder="请选择字典类型"
|
||||
v-model:value={row.dictType}
|
||||
></Select>
|
||||
);
|
||||
},
|
||||
},
|
||||
editRender: {},
|
||||
},
|
||||
];
|
2
apps/web-antd/src/views/tool/gen/edit-steps/index.ts
Normal file
2
apps/web-antd/src/views/tool/gen/edit-steps/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as BasicSetting } from './basic-setting.vue';
|
||||
export { default as GenConfig } from './gen-config.vue';
|
10
apps/web-antd/src/views/tool/gen/editTable.vue
Normal file
10
apps/web-antd/src/views/tool/gen/editTable.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<!--
|
||||
后端版本>=5.4.0 这个从本地路由变为从后台返回
|
||||
-->
|
||||
<script setup lang="ts">
|
||||
import EditGenPage from './edit-gen.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EditGenPage />
|
||||
</template>
|
291
apps/web-antd/src/views/tool/gen/index.vue
Normal file
291
apps/web-antd/src/views/tool/gen/index.vue
Normal file
@@ -0,0 +1,291 @@
|
||||
<script setup lang="ts">
|
||||
import type { VbenFormProps } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
import { getVxePopupContainer } from '@vben/utils';
|
||||
|
||||
import { message, Modal, Popconfirm, Space } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { useVbenVxeGrid, vxeCheckboxChecked } from '#/adapter/vxe-table';
|
||||
import {
|
||||
batchGenCode,
|
||||
generatedList,
|
||||
genRemove,
|
||||
genWithPath,
|
||||
getDataSourceNames,
|
||||
syncDb,
|
||||
} from '#/api/tool/gen';
|
||||
import { downloadByData } from '#/utils/file/download';
|
||||
|
||||
import codePreviewModal from './code-preview-modal.vue';
|
||||
import { columns, querySchema } from './data';
|
||||
import tableImportModal from './table-import-modal.vue';
|
||||
|
||||
const formOptions: VbenFormProps = {
|
||||
schema: querySchema(),
|
||||
commonConfig: {
|
||||
labelWidth: 80,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
// 日期选择格式化
|
||||
fieldMappingTime: [
|
||||
[
|
||||
'createTime',
|
||||
['params[beginTime]', 'params[endTime]'],
|
||||
['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'],
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
const gridOptions: VxeGridProps = {
|
||||
checkboxConfig: {
|
||||
// 高亮
|
||||
highlight: true,
|
||||
// 翻页时保留选中状态
|
||||
reserve: true,
|
||||
// 点击行选中
|
||||
trigger: 'row',
|
||||
},
|
||||
columns,
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
pagerConfig: {},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues = {}) => {
|
||||
return await generatedList({
|
||||
pageNum: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'tableId',
|
||||
},
|
||||
id: 'tool-gen-index',
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||
formOptions,
|
||||
gridOptions,
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
// 获取数据源
|
||||
const ret = await getDataSourceNames();
|
||||
const dataSourceOptions = [{ label: '全部', value: '' }];
|
||||
const transOptions = ret.map((item) => ({ label: item, value: item }));
|
||||
dataSourceOptions.push(...transOptions);
|
||||
// 更新selectOptions
|
||||
tableApi.formApi.updateSchema([
|
||||
{
|
||||
fieldName: 'dataName',
|
||||
componentProps: {
|
||||
options: dataSourceOptions,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
const [CodePreviewModal, previewModalApi] = useVbenModal({
|
||||
connectedComponent: codePreviewModal,
|
||||
});
|
||||
|
||||
function handlePreview(record: Recordable<any>) {
|
||||
previewModalApi.setData({ tableId: record.tableId });
|
||||
previewModalApi.open();
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
function handleEdit(record: Recordable<any>) {
|
||||
router.push(`/tool/gen-edit/index/${record.tableId}`);
|
||||
}
|
||||
|
||||
async function handleSync(record: Recordable<any>) {
|
||||
await syncDb(record.tableId);
|
||||
await tableApi.query();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量生成代码
|
||||
*/
|
||||
async function handleBatchGen() {
|
||||
const rows = tableApi.grid.getCheckboxRecords();
|
||||
const ids = rows.map((row: any) => row.tableId);
|
||||
if (ids.length === 0) {
|
||||
message.info('请选择需要生成代码的表');
|
||||
return;
|
||||
}
|
||||
const hideLoading = message.loading('下载中...');
|
||||
try {
|
||||
const params = ids.join(',');
|
||||
const data = await batchGenCode(params);
|
||||
const timestamp = Date.now();
|
||||
downloadByData(data, `批量代码生成_${timestamp}.zip`);
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDownload(record: Recordable<any>) {
|
||||
const hideLoading = message.loading('加载中...');
|
||||
try {
|
||||
// 路径生成
|
||||
if (record.genType === '1' && record.genPath) {
|
||||
await genWithPath(record.tableId);
|
||||
message.success(`生成成功: ${record.genPath}`);
|
||||
return;
|
||||
}
|
||||
// zip生成
|
||||
const blob = await batchGenCode(record.tableId);
|
||||
const filename = `代码生成_${record.tableName}_${dayjs().valueOf()}.zip`;
|
||||
downloadByData(blob, filename);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param record
|
||||
*/
|
||||
async function handleDelete(record: Recordable<any>) {
|
||||
await genRemove(record.tableId);
|
||||
await tableApi.query();
|
||||
}
|
||||
|
||||
function handleMultiDelete() {
|
||||
const rows = tableApi.grid.getCheckboxRecords();
|
||||
const ids = rows.map((row: any) => row.tableId);
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
okType: 'danger',
|
||||
content: `确认删除选中的${ids.length}条记录吗?`,
|
||||
onOk: async () => {
|
||||
await genRemove(ids);
|
||||
await tableApi.query();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const [TableImportModal, tableImportModalApi] = useVbenModal({
|
||||
connectedComponent: tableImportModal,
|
||||
});
|
||||
|
||||
function handleImport() {
|
||||
tableImportModalApi.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="true">
|
||||
<BasicTable table-title="代码生成列表">
|
||||
<template #toolbar-tools>
|
||||
<a
|
||||
class="text-primary mr-2"
|
||||
href="https://dapdap.top/other/template.html"
|
||||
target="_blank"
|
||||
>👉关于代码生成模板
|
||||
</a>
|
||||
<Space>
|
||||
<a-button
|
||||
:disabled="!vxeCheckboxChecked(tableApi)"
|
||||
danger
|
||||
type="primary"
|
||||
v-access:code="['tool:gen:remove']"
|
||||
@click="handleMultiDelete"
|
||||
>
|
||||
{{ $t('pages.common.delete') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
:disabled="!vxeCheckboxChecked(tableApi)"
|
||||
v-access:code="['tool:gen:code']"
|
||||
@click="handleBatchGen"
|
||||
>
|
||||
{{ $t('pages.common.generate') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
v-access:code="['tool:gen:import']"
|
||||
@click="handleImport"
|
||||
>
|
||||
{{ $t('pages.common.import') }}
|
||||
</a-button>
|
||||
</Space>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<a-button
|
||||
size="small"
|
||||
type="link"
|
||||
v-access:code="['tool:gen:preview']"
|
||||
@click.stop="handlePreview(row)"
|
||||
>
|
||||
{{ $t('pages.common.preview') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
type="link"
|
||||
v-access:code="['tool:gen:edit']"
|
||||
@click.stop="handleEdit(row)"
|
||||
>
|
||||
{{ $t('pages.common.edit') }}
|
||||
</a-button>
|
||||
<Popconfirm
|
||||
:get-popup-container="getVxePopupContainer"
|
||||
:title="`确认同步[${row.tableName}]?`"
|
||||
placement="left"
|
||||
@confirm="handleSync(row)"
|
||||
>
|
||||
<a-button
|
||||
size="small"
|
||||
type="link"
|
||||
v-access:code="['tool:gen:edit']"
|
||||
@click.stop=""
|
||||
>
|
||||
{{ $t('pages.common.sync') }}
|
||||
</a-button>
|
||||
</Popconfirm>
|
||||
<a-button
|
||||
size="small"
|
||||
type="link"
|
||||
v-access:code="['tool:gen:code']"
|
||||
@click.stop="handleDownload(row)"
|
||||
>
|
||||
生成代码
|
||||
</a-button>
|
||||
<Popconfirm
|
||||
:get-popup-container="getVxePopupContainer"
|
||||
:title="`确认删除[${row.tableName}]?`"
|
||||
placement="left"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<a-button
|
||||
danger
|
||||
size="small"
|
||||
type="link"
|
||||
v-access:code="['tool:gen:remove']"
|
||||
@click.stop=""
|
||||
>
|
||||
{{ $t('pages.common.delete') }}
|
||||
</a-button>
|
||||
</Popconfirm>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<CodePreviewModal />
|
||||
<TableImportModal @reload="tableApi.query()" />
|
||||
</Page>
|
||||
</template>
|
143
apps/web-antd/src/views/tool/gen/table-import-modal.vue
Normal file
143
apps/web-antd/src/views/tool/gen/table-import-modal.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<script setup lang="ts">
|
||||
import type { VbenFormProps } from '@vben/common-ui';
|
||||
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
getDataSourceNames,
|
||||
importTable,
|
||||
readyToGenList,
|
||||
} from '#/api/tool/gen';
|
||||
|
||||
const emit = defineEmits<{ reload: [] }>();
|
||||
|
||||
const formOptions: VbenFormProps = {
|
||||
schema: [
|
||||
{
|
||||
label: '数据源',
|
||||
fieldName: 'dataName',
|
||||
component: 'Select',
|
||||
defaultValue: 'master',
|
||||
},
|
||||
{
|
||||
label: '表名称',
|
||||
fieldName: 'tableName',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '表描述',
|
||||
fieldName: 'tableComment',
|
||||
component: 'Input',
|
||||
},
|
||||
],
|
||||
commonConfig: {
|
||||
labelWidth: 60,
|
||||
},
|
||||
showCollapseButton: false,
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||
};
|
||||
|
||||
const gridOptions: VxeGridProps = {
|
||||
checkboxConfig: {
|
||||
highlight: true,
|
||||
reserve: true,
|
||||
trigger: 'row',
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
type: 'checkbox',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
title: '表名称',
|
||||
field: 'tableName',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: '表描述',
|
||||
field: 'tableComment',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
field: 'createTime',
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
field: 'updateTime',
|
||||
},
|
||||
],
|
||||
keepSource: true,
|
||||
size: 'small',
|
||||
minHeight: 400,
|
||||
pagerConfig: {},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues = {}) => {
|
||||
return await readyToGenList({
|
||||
pageNum: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'tableId',
|
||||
},
|
||||
toolbarConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({ formOptions, gridOptions });
|
||||
|
||||
const [BasicModal, modalApi] = useVbenModal({
|
||||
onOpenChange: async (isOpen) => {
|
||||
if (!isOpen) {
|
||||
tableApi.grid.clearCheckboxRow();
|
||||
return null;
|
||||
}
|
||||
const ret = await getDataSourceNames();
|
||||
const dataSourceOptions = ret.map((item) => ({ label: item, value: item }));
|
||||
tableApi.formApi.updateSchema([
|
||||
{
|
||||
fieldName: 'dataName',
|
||||
componentProps: {
|
||||
options: dataSourceOptions,
|
||||
},
|
||||
},
|
||||
]);
|
||||
},
|
||||
onConfirm: handleSubmit,
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
const records = tableApi.grid.getCheckboxRecords();
|
||||
const tables = records.map((item) => item.tableName);
|
||||
if (tables.length === 0) {
|
||||
modalApi.close();
|
||||
return;
|
||||
}
|
||||
modalApi.modalLoading(true);
|
||||
const { dataName } = await tableApi.formApi.getValues();
|
||||
await importTable(tables.join(','), dataName);
|
||||
emit('reload');
|
||||
modalApi.close();
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
} finally {
|
||||
modalApi.modalLoading(false);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasicModal class="w-[800px]" title="导入表">
|
||||
<BasicTable />
|
||||
</BasicModal>
|
||||
</template>
|
Reference in New Issue
Block a user