瀏覽代碼

feat(代码生成器): 添加代码生成器模块

shizhongming 2 年之前
父節點
當前提交
6436a493c7
共有 56 個文件被更改,包括 6281 次插入6 次删除
  1. 1 0
      src/components/SmartTable/index.ts
  2. 40 0
      src/components/SmartTable/src/hooks/useVxeTableSortable.ts
  3. 64 0
      src/modules/codeGenerator/components/template/TemplateGroup.vue
  4. 8 0
      src/modules/codeGenerator/constants/Constants.ts
  5. 13 0
      src/modules/codeGenerator/constants/DatabaseConstants.ts
  6. 11 0
      src/modules/codeGenerator/router/CodeBasicRouter.ts
  7. 11 0
      src/modules/codeGenerator/views/codeCreate/CodeCreateSupport.ts
  8. 63 0
      src/modules/codeGenerator/views/codeCreate/CodeCreateView.vue
  9. 80 0
      src/modules/codeGenerator/views/codeCreate/CodeCreateViewOld.vue
  10. 30 0
      src/modules/codeGenerator/views/codeDesign/CodeDesignPage.api.ts
  11. 330 0
      src/modules/codeGenerator/views/codeDesign/CodeDesignPage.config.ts
  12. 250 0
      src/modules/codeGenerator/views/codeDesign/CodeDesignPage.vue
  13. 142 0
      src/modules/codeGenerator/views/codeDesign/CodeDesignPageHook.ts
  14. 54 0
      src/modules/codeGenerator/views/codeDesign/componenets/DatabaseSelect/DatabaseSelect.vue
  15. 210 0
      src/modules/codeGenerator/views/codeDesign/componenets/PageAddendumTableChoseModal.vue
  16. 222 0
      src/modules/codeGenerator/views/codeDesign/componenets/PageFromSetting/FormRuleSetModal.vue
  17. 362 0
      src/modules/codeGenerator/views/codeDesign/componenets/PageFromSetting/PageFormSetting.vue
  18. 380 0
      src/modules/codeGenerator/views/codeDesign/componenets/PageSearchSetting/PageSearchSetting.vue
  19. 174 0
      src/modules/codeGenerator/views/codeDesign/componenets/PageSettingSupport.ts
  20. 331 0
      src/modules/codeGenerator/views/codeDesign/componenets/PageTableSetting/PageTableSetting.vue
  21. 107 0
      src/modules/codeGenerator/views/codeDesign/componenets/TableFieldTable/TableFieldTable.vue
  22. 20 0
      src/modules/codeGenerator/views/codeDesign/lang/en_US.ts
  23. 20 0
      src/modules/codeGenerator/views/codeDesign/lang/zh_CN.ts
  24. 16 0
      src/modules/codeGenerator/views/codeList/CodeListView.api.ts
  25. 136 0
      src/modules/codeGenerator/views/codeList/CodeListView.config.tsx
  26. 175 0
      src/modules/codeGenerator/views/codeList/CodeListView.vue
  27. 187 0
      src/modules/codeGenerator/views/codeList/components/CodeCreateForm.vue
  28. 147 0
      src/modules/codeGenerator/views/codeList/components/CodeCreateModal.vue
  29. 9 0
      src/modules/codeGenerator/views/codeList/components/CodeListAddEditModal.vue
  30. 150 0
      src/modules/codeGenerator/views/codeList/components/TemplateSelectTable.vue
  31. 55 0
      src/modules/codeGenerator/views/codeList/hooks/useLoadDbData.ts
  32. 166 0
      src/modules/codeGenerator/views/codeList/lang/en_US.ts
  33. 167 0
      src/modules/codeGenerator/views/codeList/lang/zh_CN.ts
  34. 19 0
      src/modules/codeGenerator/views/database/DatabaseListHooks.ts
  35. 70 0
      src/modules/codeGenerator/views/database/DatabaseListView.api.ts
  36. 203 0
      src/modules/codeGenerator/views/database/DatabaseListView.data.ts
  37. 172 0
      src/modules/codeGenerator/views/database/DatabaseListView.vue
  38. 77 0
      src/modules/codeGenerator/views/database/components/DatabaseListViewAddEditModal.vue
  39. 84 0
      src/modules/codeGenerator/views/database/components/TemplateSelected.vue
  40. 97 0
      src/modules/codeGenerator/views/database/components/TemplateSelectedModal.vue
  41. 40 0
      src/modules/codeGenerator/views/database/lang/en_US.ts
  42. 40 0
      src/modules/codeGenerator/views/database/lang/zh_CN.ts
  43. 85 0
      src/modules/codeGenerator/views/document/TemplateDataDocumentView.vue
  44. 39 0
      src/modules/codeGenerator/views/template/CodeTemplateList.api.ts
  45. 160 0
      src/modules/codeGenerator/views/template/CodeTemplateList.config.ts
  46. 180 0
      src/modules/codeGenerator/views/template/CodeTemplateList.vue
  47. 248 0
      src/modules/codeGenerator/views/template/components/TemplateGroup.vue
  48. 171 0
      src/modules/codeGenerator/views/template/components/TemplateSetUserGroup.vue
  49. 37 0
      src/modules/codeGenerator/views/template/lang/en_US.ts
  50. 37 0
      src/modules/codeGenerator/views/template/lang/zh_CN.ts
  51. 42 0
      src/modules/system/views/accessSecret/SysAuthAccessSecretListView.api.ts
  52. 186 0
      src/modules/system/views/accessSecret/SysAuthAccessSecretListView.config.ts
  53. 98 0
      src/modules/system/views/accessSecret/SysAuthAccessSecretListView.vue
  54. 28 0
      src/modules/system/views/accessSecret/lang/zh_CN.ts
  55. 22 5
      src/utils/http/axios/Axios.ts
  56. 15 1
      src/utils/http/axios/index.ts

+ 1 - 0
src/components/SmartTable/index.ts

@@ -16,3 +16,4 @@ export * from './src/types/SmartTableColumnType';
 export * from './src/types/SmartTableAuthType';
 export * from './src/types/SmartTableToolbarConfigType';
 export * from './src/utils/TableCommon';
+export * from './src/hooks/useVxeTableSortable';

+ 40 - 0
src/components/SmartTable/src/hooks/useVxeTableSortable.ts

@@ -0,0 +1,40 @@
+import { onMounted, onUnmounted } from 'vue';
+import type { Ref } from 'vue';
+
+import Sortable from 'sortablejs';
+
+/**
+ * vxe支持行拖动
+ * @param tableRef
+ * @param handle css
+ * @param tableData 表格数据
+ */
+export const useVxeTableSortable = (tableRef: Ref, handle: string, tableData: Ref) => {
+  let sortable: any = null;
+  const handleRowDrop = () => {
+    if (sortable !== null) {
+      sortable.destroy();
+    }
+    sortable = Sortable.create(
+      tableRef.value.$el.querySelector('.body--wrapper>.vxe-table--body tbody'),
+      {
+        handle: handle,
+        animation: 150,
+        ghostClass: 'blue-background-class',
+        onEnd: ({ newIndex, oldIndex }: any) => {
+          const currentRow = tableData.value.splice(oldIndex, 1)[0];
+          tableData.value.splice(newIndex, 0, currentRow);
+        },
+      },
+    );
+  };
+  onUnmounted(() => {
+    if (sortable !== null) {
+      sortable.destroy();
+    }
+  });
+  onMounted(handleRowDrop);
+  return {
+    handleRowDrop,
+  };
+};

+ 64 - 0
src/modules/codeGenerator/components/template/TemplateGroup.vue

@@ -0,0 +1,64 @@
+<template>
+  <div>
+    <SmartTable
+      @register="registerTable"
+      v-bind="getTableProps"
+      @current-change="handleCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import type { SmartTableProps } from '@/components/SmartTable';
+
+  import { computed } from 'vue';
+
+  import { SmartTable, useSmartTable } from '@/components/SmartTable';
+  import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+  const props = defineProps({
+    tableProps: Object as PropType<SmartTableProps>,
+  });
+
+  const emit = defineEmits(['current-change']);
+
+  const handleCurrentChange = ({ row }) => {
+    emit('current-change', row);
+  };
+
+  const getTableProps = computed<SmartTableProps>(() => {
+    return {
+      rowConfig: {
+        isHover: true,
+        isCurrent: true,
+      },
+      columns: [
+        {
+          title: '{generator.views.template.title.templateGroup}',
+          field: 'groupName',
+        },
+      ],
+      height: 'auto',
+      proxyConfig: {
+        ajax: {
+          query: async (params) => {
+            const result = await defHttp.post({
+              service: ApiServiceEnum.SMART_CODE,
+              url: 'db/code/template/listGroup',
+              data: {
+                ...params.ajaxParameter,
+                sortName: 'seq',
+              },
+            });
+            return [{ groupName: 'ALL' }, ...result];
+          },
+        },
+      },
+      ...props.tableProps,
+    };
+  });
+
+  const [registerTable] = useSmartTable();
+</script>
+
+<style scoped></style>

+ 8 - 0
src/modules/codeGenerator/constants/Constants.ts

@@ -0,0 +1,8 @@
+export const extensionLanguageMap: { [index: string]: string } = {
+  'text/x-java': 'java',
+  xml: 'xml',
+  javascript: 'js',
+  html: 'html',
+  'text/x-vue': 'vue',
+  typescript: 'ts',
+};

+ 13 - 0
src/modules/codeGenerator/constants/DatabaseConstants.ts

@@ -0,0 +1,13 @@
+/**
+ * 模板类型
+ */
+export const TemplateType: any = {
+  TEMPLATE_CODE: {
+    value: 'TEMPLATE_CODE',
+    label: 'generator.views.template.label.templateType.templateCode',
+  },
+  TEMPLATE_DB_DICT: {
+    value: 'TEMPLATE_DB_DICT',
+    label: 'generator.views.template.label.templateType.templateDbDict',
+  },
+};

+ 11 - 0
src/modules/codeGenerator/router/CodeBasicRouter.ts

@@ -0,0 +1,11 @@
+import { AppRouteRecordRaw } from '@/router/types';
+
+export const CREATE_CODE_ROUTER: AppRouteRecordRaw = {
+  path: '/codeCreateView',
+  name: 'CodeCreateView',
+  meta: {
+    title: '生成代码',
+  },
+  component: () => import('/@/modules/codeGenerator/views/codeCreate/CodeCreateView.vue'),
+  props: (route) => route.query,
+};

+ 11 - 0
src/modules/codeGenerator/views/codeCreate/CodeCreateSupport.ts

@@ -0,0 +1,11 @@
+/**
+ * 扩展名类型映射
+ */
+export const extensionLanguageMap: any = {
+  'text/x-java': 'java',
+  xml: 'xml',
+  javascript: 'js',
+  html: 'html',
+  'text/x-vue': 'vue',
+  typescript: 'ts',
+};

+ 63 - 0
src/modules/codeGenerator/views/codeCreate/CodeCreateView.vue

@@ -0,0 +1,63 @@
+<template>
+  <div class="full-height page-container">
+    <div style="padding-bottom: 6px">
+      <a-button type="primary" @click="handleDownloadAll">下载全部</a-button>
+    </div>
+    <a-tabs class="code-container">
+      <a-tab-pane v-for="item in data" :key="item.templateId" :tab="item.templateName">
+        <CodeEditor read-only :mode="item.language" :code="item.code" />
+      </a-tab-pane>
+    </a-tabs>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref, useAttrs } from 'vue';
+  import { downloadByData } from '@/utils/file/download';
+  import { extensionLanguageMap } from './CodeCreateSupport';
+  import { CodeEditor } from '@/components/CodeEditor';
+  import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+  const attrs = useAttrs();
+  const data = ref<Array<any>>([]);
+  const dataLoading = ref(false);
+  /**
+   * 下载全部
+   */
+  const handleDownloadAll = () => {
+    data.value.forEach((item): any => {
+      const filename = `${item.filename}.${extensionLanguageMap[item.language]}`;
+      downloadByData(item.code, filename);
+    });
+  };
+
+  /**
+   * 加载数据函数
+   */
+  const loadData = async () => {
+    dataLoading.value = true;
+    try {
+      data.value = await defHttp.post({
+        service: ApiServiceEnum.SMART_CODE,
+        url: 'db/code/main/createCode',
+        data: Object.assign({}, attrs, {
+          templateIdList: attrs.templateIdList?.split(','),
+        }),
+      });
+    } finally {
+      dataLoading.value = false;
+    }
+  };
+  onMounted(loadData);
+</script>
+
+<style scoped lang="less">
+  .code-container {
+    height: calc(100% - 38px);
+    border: 1px solid gainsboro;
+
+    ::v-deep(.ant-tabs-content) {
+      height: calc(100% - 60px);
+    }
+  }
+</style>

+ 80 - 0
src/modules/codeGenerator/views/codeCreate/CodeCreateViewOld.vue

@@ -0,0 +1,80 @@
+<template>
+  <div class="full-height" style=" height: 100%;padding: 10px">
+    <div style="padding-bottom: 6px">
+      <a-button type="primary" @click="handleDownloadAll">下载全部</a-button>
+    </div>
+    <a-tabs class="code-container">
+      <a-tab-pane v-for="item in data" :key="item.templateId" :tab="item.templateName">
+        <CodeEditor read-only :mode="item.language" :code="item.code" />
+      </a-tab-pane>
+    </a-tabs>
+  </div>
+</template>
+
+<script lang="ts">
+  import { defineComponent, onMounted, ref } from 'vue';
+
+  import { CodeEditor } from '@/components/CodeEditor';
+
+  import { downloadByData } from '@/utils/file/download';
+
+  import { extensionLanguageMap } from './CodeCreateSupport';
+  import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+  /**
+   * 代码生成页面
+   */
+  export default defineComponent({
+    name: 'CodeCreateView',
+    components: {
+      CodeEditor,
+    },
+    // @ts-ignore
+    setup(props, { attrs }: any) {
+      const data = ref<Array<any>>([]);
+      const dataLoading = ref(false);
+      /**
+       * 加载数据函数
+       */
+      const loadData = async () => {
+        dataLoading.value = true;
+        try {
+          data.value = await defHttp.post({
+            service: ApiServiceEnum.SMART_CODE,
+            url: 'db/code/main/createCode',
+            data: Object.assign({}, attrs, {
+              templateIdList: attrs.templateIdList.split(','),
+            }),
+          });
+        } finally {
+          dataLoading.value = false;
+        }
+      };
+      /**
+       * 下载全部
+       */
+      const handleDownloadAll = () => {
+        data.value.forEach((item): any => {
+          const filename = `${item.filename}.${extensionLanguageMap[item.language]}`;
+          downloadByData(item.code, filename);
+        });
+      };
+      onMounted(loadData);
+      return {
+        data,
+        handleDownloadAll,
+      };
+    },
+  });
+</script>
+
+<style lang="less" scoped>
+  .code-container {
+    height: calc(100% - 38px);
+    border: 1px solid gainsboro;
+
+    ::v-deep(.ant-tabs-content) {
+      height: calc(100% - 60px);
+    }
+  }
+</style>

+ 30 - 0
src/modules/codeGenerator/views/codeDesign/CodeDesignPage.api.ts

@@ -0,0 +1,30 @@
+import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+enum Api {
+  queryDbTable = 'db/connection/queryDbTable',
+  getConfigById = 'db/code/main/getConfigById',
+  saveConfig = 'db/code/main/save',
+}
+
+/**
+ * 查询数据库信息
+ * @param connectionId
+ * @param tableName
+ */
+export const queryDbTableApi = (connectionId: number, tableName: number) =>
+  defHttp.post({
+    service: ApiServiceEnum.SMART_CODE,
+    url: Api.queryDbTable,
+    data: { dbConnectionId: connectionId, tableName },
+  });
+
+export const getConfigByIdApi = (configId: number) =>
+  defHttp.post({ service: ApiServiceEnum.SMART_CODE, url: Api.getConfigById, data: configId });
+
+export const saveConfigApi = (model: Recordable) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_CODE,
+    url: Api.saveConfig,
+    data: model,
+  });
+};

+ 330 - 0
src/modules/codeGenerator/views/codeDesign/CodeDesignPage.config.ts

@@ -0,0 +1,330 @@
+import type { FormSchema } from '@/components/Form';
+
+type ButtonType =
+  | 'SEARCH'
+  | 'RESET'
+  | 'ADD'
+  | 'EDIT'
+  | 'DELETE'
+  | 'EXCEL_IMPORT'
+  | 'EXCEL_EXPORT'
+  | 'COLUMN_SETTING'
+  | 'ZOOM'
+  | 'REFRESH'
+  | 'SHOW_SEARCH'
+  | 'PRINT';
+
+interface Button {
+  key: ButtonType;
+  value: string;
+}
+
+const tableTypeList = [
+  {
+    label: 'generator.views.code.title.tableType.single',
+    value: '10',
+  },
+  {
+    label: 'generator.views.code.title.tableType.main',
+    value: '20',
+  },
+  {
+    label: 'generator.views.code.title.tableType.addendum',
+    value: '30',
+  },
+];
+
+const yesNoList = [
+  {
+    label: 'Yes',
+    value: true,
+  },
+  {
+    label: 'No',
+    value: false,
+  },
+];
+
+export const formSchemas = (t: Function): FormSchema[] => {
+  return [
+    {
+      label: '',
+      field: 'systemId',
+      component: 'Input',
+      show: false,
+    },
+    {
+      label: t('generator.views.code.table.connectionName'),
+      field: 'connectionId',
+      slot: 'addEditForm-connectionId',
+      required: true,
+    },
+    {
+      label: t('generator.views.code.table.tableName'),
+      field: 'tableName',
+      component: 'Input',
+      required: true,
+    },
+    {
+      label: t('generator.views.code.table.configName'),
+      field: 'configName',
+      component: 'Input',
+      required: true,
+    },
+    {
+      label: t('generator.views.code.table.type'),
+      field: 'type',
+      component: 'Select',
+      defaultValue: '10',
+      componentProps: {
+        options: tableTypeList.map((item) => ({ label: t(item.label), value: item.value })),
+      },
+    },
+    // ------------ 第二行 ---------------------
+    {
+      label: t('generator.views.code.title.showCheckBox'),
+      field: 'showCheckbox',
+      component: 'RadioGroup',
+      defaultValue: true,
+      componentProps: {
+        options: yesNoList,
+      },
+    },
+    {
+      label: t('generator.views.code.title.isPage'),
+      field: 'page',
+      component: 'RadioGroup',
+      defaultValue: true,
+      componentProps: {
+        options: yesNoList,
+      },
+    },
+    {
+      label: t('generator.views.code.title.invented'),
+      field: 'invented',
+      component: 'RadioGroup',
+      defaultValue: false,
+      componentProps: {
+        options: yesNoList,
+      },
+    },
+    {
+      label: t('generator.views.code.title.columnSort'),
+      field: 'columnSort',
+      component: 'RadioGroup',
+      defaultValue: false,
+      componentProps: {
+        options: yesNoList,
+      },
+    },
+    // ------------ 第三行 ---------------------
+    {
+      label: t('generator.views.code.title.leftButton'),
+      field: 'leftButtonList',
+      component: 'Select',
+      defaultValue: ['ADD', 'DELETE'],
+      componentProps: {
+        mode: 'multiple',
+        options: letButtonList.map((item) => ({
+          label: item.value,
+          value: item.key,
+        })),
+      },
+    },
+    {
+      label: t('generator.views.code.title.rightButton'),
+      field: 'rightButtonList',
+      component: 'Select',
+      defaultValue: ['ZOOM', 'REFRESH', 'SHOW_SEARCH', 'COLUMN_SETTING'],
+      componentProps: {
+        mode: 'multiple',
+        options: rightButtonList.map((item) => ({
+          label: item.value,
+          value: item.key,
+        })),
+      },
+    },
+    {
+      label: t('generator.views.code.title.rowButtonType.title'),
+      field: 'rowButtonType',
+      component: 'Select',
+      defaultValue: 'NONE',
+      componentProps: {
+        options: rowButtonTypeList(t),
+      },
+    },
+    {
+      label: t('generator.views.code.title.rowButtonList'),
+      field: 'rowButtonList',
+      component: 'Select',
+      componentProps: {
+        mode: 'multiple',
+        options: rowButtonList.map((item) => ({
+          label: item.value,
+          value: item.key,
+        })),
+      },
+    },
+    // ------------ 第四行 ---------------------
+    {
+      label: t('generator.views.code.title.formColNum'),
+      field: 'formColNum',
+      component: 'Select',
+      defaultValue: 1,
+      componentProps: {
+        options: columnNumList(t, false),
+      },
+    },
+    {
+      label: t('generator.views.code.title.searchColNum'),
+      field: 'searchColNum',
+      component: 'Select',
+      defaultValue: 0,
+      componentProps: {
+        options: columnNumList(t),
+      },
+    },
+    {
+      label: t('common.table.remark'),
+      field: 'remark',
+      component: 'Input',
+    },
+    {
+      label: t('generator.views.code.title.i18nPrefix'),
+      field: 'i18nPrefix',
+      component: 'Input',
+    },
+    // ------------ 第五行 ---------------------
+    {
+      label: t('generator.views.code.title.relateTable'),
+      field: 'addendumTableList',
+      defaultValue: [],
+      slot: 'addEditForm-RelateTable',
+    },
+    {
+      label: '',
+      field: 'id',
+      slot: 'addEditForm-syncTable',
+    },
+  ];
+};
+
+/**
+ * 左侧按钮列表
+ */
+const letButtonList: Button[] = [
+  {
+    key: 'SEARCH',
+    value: '搜索',
+  },
+  {
+    key: 'RESET',
+    value: '重置',
+  },
+  {
+    key: 'ADD',
+    value: '添加',
+  },
+  {
+    key: 'EDIT',
+    value: '修改',
+  },
+  {
+    key: 'DELETE',
+    value: '删除',
+  },
+];
+
+const rightButtonList: Button[] = [
+  {
+    key: 'EXCEL_IMPORT',
+    value: 'Excel导入',
+  },
+  {
+    key: 'EXCEL_EXPORT',
+    value: 'Excel导出',
+  },
+  {
+    key: 'COLUMN_SETTING',
+    value: '列配置',
+  },
+  {
+    key: 'ZOOM',
+    value: '放大缩小',
+  },
+  {
+    key: 'REFRESH',
+    value: '刷新',
+  },
+  {
+    key: 'SHOW_SEARCH',
+    value: '显示搜索',
+  },
+  {
+    key: 'PRINT',
+    value: '打印',
+  },
+];
+
+/**
+ * 行按钮
+ */
+const rowButtonList = [
+  {
+    key: 'EDIT',
+    value: '修改',
+  },
+  {
+    key: 'DELETE',
+    value: '删除',
+  },
+];
+
+const columnNumList = (t: Function, hasZeroColumn = true) => {
+  const column = [
+    {
+      value: 1,
+      label: t('generator.views.code.title.colNum.one'),
+    },
+    {
+      value: 2,
+      label: t('generator.views.code.title.colNum.two'),
+    },
+    {
+      value: 3,
+      label: t('generator.views.code.title.colNum.three'),
+    },
+    {
+      value: 4,
+      label: t('generator.views.code.title.colNum.four'),
+    },
+  ];
+  if (hasZeroColumn) {
+    return [
+      {
+        value: 0,
+        label: t('generator.views.design.title.colNum.zero'),
+      },
+    ].concat(column);
+  }
+  return column;
+};
+
+const rowButtonTypeList = (t: Function) => [
+  {
+    label: t('generator.views.code.title.rowButtonType.none'),
+    value: 'NONE',
+  },
+  {
+    label: t('generator.views.code.title.rowButtonType.single'),
+    value: 'SINGLE',
+  },
+  {
+    label: t('generator.views.code.title.rowButtonType.more'),
+    value: 'MORE',
+  },
+  {
+    label: t('generator.views.code.title.rowButtonType.text'),
+    value: 'TEXT',
+  },
+];

+ 250 - 0
src/modules/codeGenerator/views/codeDesign/CodeDesignPage.vue

@@ -0,0 +1,250 @@
+<template>
+  <div class="full-height page-container">
+    <div class="spin">
+      <div class="form-container">
+        <a-spin :spinning="pageLoading">
+          <BasicForm @register="registerForm" :size="getFormSize">
+            <template #addEditForm-connectionId="{ model }">
+              <DatabaseSelect
+                v-model:value="model.connectionId"
+                :parameter="getDatabaseListParameter"
+              />
+            </template>
+            <template #addEditForm-RelateTable="{ model }">
+              <a-tag
+                v-for="(table, index) in model.addendumTableList"
+                :key="index"
+                style="display: inline-block"
+                @close="() => handleRemoveRelateTable(model.addendumTableList, index)"
+                closable
+              >
+                {{ table.configName }}
+              </a-tag>
+              <Icon
+                icon="ant-design:plus-outlined"
+                :style="{ cursor: 'pointer' }"
+                @click="() => openPageAddendumTableChoseModal(true, {})"
+              />
+              <PageAddendumTableChoseModal
+                :select-table-list="model.relatedTableList"
+                @ok="handleSetAddendumTable"
+                @register="registerPageAddendumTableChoseModal"
+              />
+            </template>
+            <template #addEditForm-syncTable>
+              <a-button type="primary" :size="getButtonSize" @click="handleSyncTableData">
+                {{ $t('generator.views.code.button.syncTableData') }}
+              </a-button>
+            </template>
+          </BasicForm>
+          <a-divider />
+          <a-tabs style="min-height: 400px" animated>
+            <a-tab-pane key="1" :tab="$t('generator.views.code.title.dbMessage')">
+              <TableFieldTable
+                class="full-height"
+                :data="computedTableData"
+                :loading="dbDataLoading"
+              />
+            </a-tab-pane>
+            <a-tab-pane key="2" :tab="$t('generator.views.code.title.tableSetting')">
+              <PageTableSetting
+                ref="pageTableSettingRef"
+                :edit-data="editConfigData.codePageConfigList"
+                :table-data="computedTableData"
+                :loading="dbDataLoading"
+              />
+            </a-tab-pane>
+            <a-tab-pane key="3" :tab="$t('generator.views.code.title.formSetting')">
+              <PageFormSetting
+                ref="pageFormSettingRef"
+                :edit-data="editConfigData.codeFormConfigList"
+                :table-data="computedTableData"
+                :loading="dbDataLoading"
+              />
+            </a-tab-pane>
+            <a-tab-pane key="4" :tab="$t('generator.views.code.title.searchSetting')">
+              <PageSearchSetting
+                ref="pageSearchSettingRef"
+                :edit-data="editConfigData.codeSearchConfigList"
+                :table-data="computedTableData"
+                :loading="dbDataLoading"
+              />
+            </a-tab-pane>
+          </a-tabs>
+        </a-spin>
+      </div>
+      <a-divider />
+      <div style="text-align: right">
+        <a-button @click="loadConfigData">
+          {{ $t('common.button.reload') }}
+        </a-button>
+        <a-button
+          style="margin-left: 5px"
+          :loading="saveLoading"
+          v-permission="'db:codeConfig:save'"
+          @click="handleSave"
+          type="primary"
+        >
+          {{ $t('common.button.save') }}
+        </a-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { ref, watch, onMounted } from 'vue';
+  import { useI18n } from '@/hooks/web/useI18n';
+  import { useRouter, useRoute } from 'vue-router';
+
+  import { BasicForm, useForm } from '@/components/Form';
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+  import { formSchemas } from './CodeDesignPage.config';
+  import { getConfigByIdApi } from './CodeDesignPage.api';
+  import { isString } from '@/utils/is';
+  import { toNumber } from 'lodash-es';
+  import { propTypes } from '@/utils/propTypes';
+  import { useModal } from '@/components/Modal';
+
+  import { useLoadDbData, useSaveConfig } from './CodeDesignPageHook';
+
+  import DatabaseSelect from './componenets/DatabaseSelect/DatabaseSelect.vue';
+  import TableFieldTable from './componenets/TableFieldTable/TableFieldTable.vue';
+  import PageTableSetting from './componenets/PageTableSetting/PageTableSetting.vue';
+  import PageFormSetting from './componenets/PageFromSetting/PageFormSetting.vue';
+  import PageSearchSetting from './componenets/PageSearchSetting/PageSearchSetting.vue';
+  import Icon from '@/components/Icon/src/Icon.vue';
+  import PageAddendumTableChoseModal from './componenets/PageAddendumTableChoseModal.vue';
+
+  const props = defineProps({
+    configId: propTypes.oneOfType([propTypes.string, propTypes.number]),
+    // 系统ID
+    systemId: propTypes.oneOfType([propTypes.string, propTypes.number]),
+  });
+  const { t } = useI18n();
+  const router = useRouter();
+  const route = useRoute();
+  const { getFormSize, getButtonSize } = useSizeSetting();
+
+  const pageLoading = ref(false);
+
+  const getDatabaseListParameter = () => {
+    return {
+      parameter: {
+        'systemId@=': props.systemId,
+      },
+    };
+  };
+
+  /**
+   * 代码配置页面
+   */
+  const [registerForm, { validate, setFieldsValue }] = useForm({
+    colon: true,
+    schemas: formSchemas(t),
+    showActionButtonGroup: false,
+    baseColProps: {
+      span: 6,
+    },
+    labelCol: {
+      span: 8,
+    },
+    wrapperCol: {
+      span: 15,
+    },
+  });
+
+  const handleSetAddendumTable = (tableData) => {
+    setFieldsValue({ addendumTableList: tableData });
+  };
+
+  const loadConfigData = async () => {
+    try {
+      pageLoading.value = true;
+      let configId = props.configId;
+      if (!configId) {
+        return;
+      }
+      if (isString(configId)) {
+        configId = toNumber(configId);
+      }
+      const result = await getConfigByIdApi(configId);
+      await setFieldsValue(result);
+      // 加载表格数据
+      handleSyncTableData();
+      const { codePageConfigList, codeFormConfigList, codeSearchConfigList } = result;
+      editConfigData.value = {
+        codePageConfigList,
+        codeFormConfigList,
+        codeSearchConfigList,
+      };
+    } finally {
+      pageLoading.value = false;
+    }
+  };
+
+  onMounted(() => {
+    /**
+     * 监控configId变化,更新数据
+     */
+    watch(
+      () => props.configId,
+      async (value) => {
+        if (value) {
+          loadConfigData();
+        } else {
+          setFieldsValue({
+            systemId: props.systemId,
+          });
+        }
+      },
+      {
+        immediate: true,
+      },
+    );
+  });
+
+  const { dbDataLoading, computedTableData, handleSyncTableData, isSyncRef, dbDataRef } =
+    useLoadDbData(validate);
+  const editConfigData = ref<any>({});
+
+  const handleRemoveRelateTable = (dataList: any[], index: number) => {
+    dataList.splice(index, 1);
+  };
+  const [registerPageAddendumTableChoseModal, { openModal: openPageAddendumTableChoseModal }] =
+    useModal();
+
+  const { handleSave, saveLoading, pageTableSettingRef, pageSearchSettingRef, pageFormSettingRef } =
+    useSaveConfig(t, isSyncRef, validate, dbDataRef, (configId) => {
+      const { fullPath, query, path } = route;
+      console.log(fullPath);
+      router.push({
+        path: path,
+        query: {
+          ...query,
+          configId,
+        },
+      });
+    });
+</script>
+
+<style scoped lang="less">
+  .page-container {
+    padding: 10px;
+
+    :deep(.ant-divider) {
+      margin: 5px 0;
+    }
+
+    .spin {
+      height: 100%;
+      padding: 10px;
+      background: white;
+    }
+
+    .form-container {
+      height: calc(100% - 42px);
+      overflow: auto;
+    }
+  }
+</style>

+ 142 - 0
src/modules/codeGenerator/views/codeDesign/CodeDesignPageHook.ts

@@ -0,0 +1,142 @@
+import { computed, createVNode, Ref, ref, unref } from 'vue';
+
+import { queryDbTableApi, saveConfigApi } from './CodeDesignPage.api';
+import { message, Modal } from 'ant-design-vue';
+import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
+
+/**
+ * 加载数据库数据
+ */
+export const useLoadDbData = (validate: Function) => {
+  // 数据库数据加载状态
+  const dbDataLoading = ref(false);
+  const dbDataRef = ref<Recordable>({});
+  const isSyncRef = ref(false);
+
+  /**
+   * 表格table计算属性
+   */
+  const computedTableData = computed(() => {
+    const dbData = unref(dbDataRef);
+    if (!dbData.tableName) {
+      return [];
+    }
+    const primaryKeyList = dbData.primaryKeyList || [];
+    const baseColumnList = dbData.baseColumnList || [];
+    return [...primaryKeyList, ...baseColumnList];
+  });
+
+  const handleSyncTableData = async () => {
+    const { connectionId, tableName } = await validate();
+    try {
+      dbDataLoading.value = true;
+      dbDataRef.value = await queryDbTableApi(connectionId, tableName);
+      isSyncRef.value = true;
+    } finally {
+      dbDataLoading.value = false;
+    }
+  };
+
+  return {
+    dbDataLoading,
+    dbDataRef,
+    computedTableData,
+    isSyncRef,
+    handleSyncTableData,
+  };
+};
+
+/**
+ * 保存操作hook
+ */
+export const useSaveConfig = (
+  t: Function,
+  isSync: Ref<boolean>,
+  validate: Function,
+  dbDataRef: Ref<Recordable>,
+  afterSave?: Function,
+) => {
+  const pageTableSettingRef = ref();
+  const pageSearchSettingRef = ref();
+  const pageFormSettingRef = ref();
+
+  const saveLoading = ref(false);
+
+  const handleSave = () => {
+    if (!unref(isSync)) {
+      message.warn(t('generator.views.code.validate.syncTable'));
+      return false;
+    }
+    if (!unref(pageTableSettingRef)) {
+      message.warn(t('generator.views.code.validate.tableSetting'));
+      return false;
+    }
+    if (!unref(pageFormSettingRef)) {
+      message.warn(t('generator.views.code.validate.formSetting'));
+      return false;
+    }
+    // 搜索配置实体
+    if (!unref(pageSearchSettingRef)) {
+      message.warn(t('generator.views.code.validate.searchSetting'));
+      return false;
+    }
+    // 验证必填字段是否设置表单
+    const pageFormSettingData = unref(pageFormSettingRef).getData() as Array<Recordable>;
+    const nonNullField: Array<string> = [];
+    pageFormSettingData.forEach((item) => {
+      if (item.nullable === 0 && (item.visible === false || item.used === false)) {
+        nonNullField.push(item.columnName);
+      }
+    });
+    if (nonNullField.length > 0) {
+      Modal.confirm({
+        title: t('common.notice.confirmSave'),
+        icon: createVNode(ExclamationCircleOutlined),
+        content: t('generator.views.code.message.saveConfirmContent', nonNullField.join(',')),
+        onCancel() {
+          return false;
+        },
+        onOk() {
+          doSave();
+        },
+      });
+    } else {
+      doSave();
+    }
+  };
+
+  const doSave = async () => {
+    const formModel = await validate();
+    const dbData = unref(dbDataRef);
+    const saveData = {
+      ...formModel,
+      codePageConfigList: unref(pageTableSettingRef).getData(),
+      codeFormConfigList: unref(pageFormSettingRef).getData(),
+      codeSearchConfigList: unref(pageSearchSettingRef).getData(),
+      className: dbData.className,
+      remarks: dbData.remarks,
+    };
+    try {
+      saveLoading.value = true;
+      const configId = await saveConfigApi(saveData);
+      afterSave && afterSave(configId);
+    } catch (e: any) {
+      if (e.code === 400) {
+        e.data.forEach((item: string) => {
+          message.error(item);
+        });
+      }
+      return false;
+    } finally {
+      saveLoading.value = false;
+    }
+  };
+
+  return {
+    handleSave,
+    pageTableSettingRef,
+    pageSearchSettingRef,
+    pageFormSettingRef,
+    saveLoading,
+  };
+};

+ 54 - 0
src/modules/codeGenerator/views/codeDesign/componenets/DatabaseSelect/DatabaseSelect.vue

@@ -0,0 +1,54 @@
+<template>
+  <a-select :size="formSizeConfig" v-bind="$attrs">
+    <a-select-option v-for="item in data" :key="item.key" :value="item.key">
+      {{ item.value }}
+    </a-select-option>
+  </a-select>
+</template>
+
+<script lang="ts">
+  import { defineComponent, ref, onMounted } from 'vue';
+  import type { PropType } from 'vue';
+
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+  import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+  /**
+   * 数据库连接下拉列
+   */
+  export default defineComponent({
+    name: 'DatabaseSelect',
+    props: {
+      parameter: {
+        type: Function as PropType<Function>,
+      },
+    },
+    setup(props) {
+      const data = ref<Array<any>>([]);
+      const loadData = async () => {
+        const result = await defHttp.post({
+          service: ApiServiceEnum.SMART_CODE,
+          url: '/db/connection/list',
+          data: Object.assign(
+            {
+              sortName: 'seq',
+            },
+            props.parameter && props.parameter(),
+          ),
+        });
+        data.value = result.map((item: any) => {
+          return {
+            key: item.id + '',
+            value: item.connectionName,
+          };
+        });
+      };
+      onMounted(loadData);
+      return {
+        data,
+        ...useSizeSetting(),
+      };
+    },
+  });
+</script>
+
+<style scoped></style>

+ 210 - 0
src/modules/codeGenerator/views/codeDesign/componenets/PageAddendumTableChoseModal.vue

@@ -0,0 +1,210 @@
+<template>
+  <BasicModal
+    @register="registerModal"
+    width="920px"
+    v-bind="$attrs"
+    :title="$t('generator.views.codeCreateForm.title.choseAddendum')"
+    @ok="handleOk"
+  >
+    <SmartTable class="smart-table" @register="registerTable">
+      <template #table-relatedColumn="{ row }">
+        <a-input v-model:value="row.relatedColumn" :size="getFormSize" />
+      </template>
+    </SmartTable>
+  </BasicModal>
+</template>
+
+<script lang="ts" setup>
+  import { BasicModal, useModalInner } from '@/components/Modal';
+  import { SmartColumn, SmartTable, useSmartTable } from '@/components/SmartTable';
+  import { FormSchema } from '@/components/Form';
+  import { useI18n } from '@/hooks/web/useI18n';
+  import { propTypes } from '@/utils/propTypes';
+  import { computed, PropType, toRefs, unref } from 'vue';
+  import { message } from 'ant-design-vue';
+  import { defHttp } from '@/utils/http/axios';
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+
+  const props = defineProps({
+    multiple: propTypes.bool.def(true),
+    selectTableList: {
+      type: Array as PropType<Array<any>>,
+      default: () => [],
+    },
+  });
+
+  const emit = defineEmits(['ok', 'register']);
+
+  const { selectTableList: selectTableListRef } = toRefs(props);
+
+  const { t } = useI18n();
+
+  const { getFormSize } = useSizeSetting();
+
+  const columns = [
+    {
+      title: '{generator.views.code.table.connectionName}',
+      field: 'connectionName',
+      width: 120,
+    },
+    {
+      title: '{generator.views.code.table.configName}',
+      field: 'configName',
+      width: 120,
+    },
+    {
+      title: '{generator.views.code.table.tableName}',
+      field: 'tableName',
+      width: 160,
+    },
+    {
+      title: '{generator.views.addendumTable.title.relatedColumn}',
+      field: 'relatedColumn',
+      width: 120,
+      slots: {
+        default: 'table-relatedColumn',
+      },
+    },
+    {
+      title: '{generator.views.code.table.remarks}',
+      field: 'remarks',
+      minWidth: 120,
+    },
+    {
+      title: '{common.table.remark}',
+      field: 'remark',
+      minWidth: 200,
+    },
+  ];
+
+  const searchSchemeList: FormSchema[] = [
+    {
+      label: t('generator.views.code.table.configName'),
+      field: 'configName',
+      component: 'Input',
+    },
+    {
+      label: t('generator.views.code.table.tableName'),
+      field: 'tableName',
+      component: 'Input',
+    },
+  ];
+
+  const computedColumns = computed<Partial<SmartColumn>[]>(() => {
+    const firstColumn: SmartColumn = {
+      type: 'checkbox',
+      width: 60,
+    };
+    if (!props.multiple) {
+      firstColumn.type = 'radio';
+    }
+    return [firstColumn, ...columns];
+  });
+
+  /**
+   * 确认操作
+   */
+  const handleOk = () => {
+    const data: any[] = [];
+    if (props.multiple) {
+      data.push(...getCheckboxRecords(false));
+    } else {
+      const row = getRadioRecord(false);
+      if (row) {
+        data.push(row);
+      }
+    }
+    const errorDataList = data.filter(
+      (item) =>
+        item.relatedColumn === undefined ||
+        item.relatedColumn == null ||
+        item.relatedColumn.trim() === '',
+    );
+    if (errorDataList.length > 0) {
+      errorDataList.forEach((item) => {
+        message.error(
+          t('generator.views.addendumTable.validate.relatedColumnWithConfig', item.configName),
+        );
+      });
+      return false;
+    }
+    const dealData = data.map((item) => {
+      return {
+        addendumId: item.id,
+        relatedColumn: item.relatedColumn,
+        configName: item.configName,
+      };
+    });
+    emit('ok', dealData);
+    // 关闭弹窗
+    closeModal();
+  };
+
+  const [registerModal, { closeModal }] = useModalInner(() => {
+    query();
+  });
+  const [
+    registerTable,
+    { getCheckboxRecords, getRadioRecord, query, setRadioRow, setCheckboxRow },
+  ] = useSmartTable({
+    columns: unref(computedColumns),
+    pagerConfig: false,
+    useSearchForm: true,
+    searchFormConfig: {
+      schemas: searchSchemeList,
+      layout: 'inline',
+      actionColOptions: {
+        span: undefined,
+      },
+      colon: true,
+    },
+    proxyConfig: {
+      autoLoad: false,
+      ajax: {
+        query: (params) =>
+          defHttp.post({
+            url: 'db/code/main/list',
+            data: {
+              ...params.ajaxParameter,
+              parameter: {
+                'type@=': '30',
+              },
+            },
+          }),
+      },
+      afterLoad: (result) => {
+        const selectTableList = unref(selectTableListRef);
+        if (selectTableList.length > 0) {
+          const selectTableMap: Map<number, any> = new Map<number, any>();
+          selectTableList.forEach((item) => {
+            selectTableMap.set(item.addendumId, item);
+          });
+          // 选中的行
+          const selectRowsList: Array<any> = [];
+          result.forEach((item) => {
+            if (selectTableMap.has(item.id)) {
+              selectRowsList.push(item);
+              item.relatedColumn = selectTableMap.get(item.id).relatedColumn;
+            }
+          });
+          if (selectRowsList.length > 0) {
+            if (!props.multiple) {
+              setRadioRow(selectRowsList[0]);
+            } else {
+              setCheckboxRow(selectRowsList, true);
+            }
+          }
+        }
+        return result;
+      },
+    },
+  });
+</script>
+
+<style lang="less" scoped>
+  .smart-table {
+    :deep(.ant-form-item) {
+      margin-bottom: 0;
+    }
+  }
+</style>

+ 222 - 0
src/modules/codeGenerator/views/codeDesign/componenets/PageFromSetting/FormRuleSetModal.vue

@@ -0,0 +1,222 @@
+<!--
+  设置表单验证规则
+-->
+<template>
+  <BasicModal
+    width="1000px"
+    v-bind="$attrs"
+    title="配置验证规则"
+    @ok="handleOk"
+    @register="registerModal"
+    class="form-rule-set-modal"
+  >
+    <SmartTable @register="registerTable" :data="ruleListRef" class="form-rule-set-table">
+      <template #table-options="{ row }">
+        <a-button size="small" @click="() => handleDeleteRow(row)">删除</a-button>
+      </template>
+      <template #table-tools>
+        <a-button size="small" :disabled="autoValidateRef" type="primary" @click="insertRow">
+          添加一行
+        </a-button>
+      </template>
+      <template #table-buttons>
+        <a-form-item label="是否自动校验">
+          <a-switch v-model:checked="autoValidateRef" />
+        </a-form-item>
+        <span style="margin-left: 10px">开启后,校验参数自动生成,配置的校验内容无效</span>
+      </template>
+    </SmartTable>
+  </BasicModal>
+</template>
+
+<script lang="ts" setup>
+  import type { SizeType } from 'vxe-table';
+
+  import { ref, unref } from 'vue';
+  import { useModalInner, BasicModal } from '@/components/Modal';
+  import { SmartTable, useSmartTable } from '@/components/SmartTable';
+  import { useI18n } from '@/hooks/web/useI18n';
+  import { errorMessage } from '@/utils/message/SystemNotice';
+
+  import { getRuleList } from '../PageSettingSupport';
+
+  const { t } = useI18n();
+
+  const sizeRef = ref('small');
+  // 是否自动配置
+  const autoValidateRef = ref(false);
+
+  const currentDataRef = ref<any>({});
+  const ruleListRef = ref<any[]>([]);
+
+  const [registerModal, { closeModal }] = useModalInner((data) => {
+    currentDataRef.value = data;
+    autoValidateRef.value = data.autoValidate;
+    ruleListRef.value = data.ruleList || [];
+  });
+
+  const insertRow = async () => {
+    const vxeGrid = getTableInstance();
+    const { row: newRow } = await vxeGrid.insertAt({}, -1);
+    await vxeGrid.setActiveCell(newRow, 'ruleType');
+  };
+
+  const handleDeleteRow = (row) => {
+    getTableInstance().remove(row);
+  };
+
+  const handleOk = async () => {
+    const vxeTable = getTableInstance();
+    const errMap = await vxeTable.fullValidate().catch((errMap) => errMap);
+    if (errMap) {
+      console.log(errMap);
+      errorMessage('校验不通过!');
+      return false;
+    }
+    const autoValidate = unref(autoValidateRef);
+    if (autoValidate) {
+      unref(currentDataRef).autoValidate = true;
+      unref(currentDataRef).ruleList = [];
+      closeModal();
+      return;
+    }
+    const { tableData } = vxeTable.getTableData();
+    const validateMessage = validateData(tableData);
+    if (validateMessage) {
+      errorMessage(validateMessage);
+    }
+    unref(currentDataRef).autoValidate = false;
+    unref(currentDataRef).ruleList = tableData;
+    closeModal();
+  };
+
+  const [registerTable, { getTableInstance }] = useSmartTable({
+    border: true,
+    size: unref(sizeRef) as SizeType,
+    editConfig: {
+      trigger: 'click',
+      mode: 'row',
+    },
+    editRules: {
+      message: [{ required: true, message: '请填写校验文案' }],
+      ruleTrigger: [{ required: true, message: '请选择触发时机' }],
+      ruleType: [{ required: true, message: '校验类型必须选择' }],
+    },
+    toolbarConfig: {
+      slots: {
+        tools: 'table-tools',
+        buttons: 'table-buttons',
+      },
+    },
+    columns: [
+      {
+        title: '校验类型',
+        field: 'ruleType',
+        width: 140,
+        editRender: {
+          name: '$select',
+          options: getRuleList(t),
+        },
+      },
+      {
+        title: '触发时机',
+        field: 'ruleTrigger',
+        width: 150,
+        editRender: {
+          name: '$select',
+          options: [
+            {
+              value: 'BLUR',
+              label: 'blur',
+            },
+            {
+              value: 'CHANGE',
+              label: 'change',
+            },
+          ],
+          props: {
+            multiple: true,
+          },
+        },
+      },
+      {
+        title: '长度',
+        field: 'len',
+        width: 120,
+        editRender: {
+          name: '$input',
+          props: { type: 'number' },
+        },
+      },
+      {
+        title: '最大程度',
+        field: 'max',
+        width: 120,
+        editRender: {
+          name: '$input',
+          props: { type: 'number' },
+        },
+      },
+      {
+        title: '最小长度',
+        field: 'min',
+        width: 120,
+        editRender: {
+          name: '$input',
+          props: { type: 'number' },
+        },
+      },
+      {
+        title: '校验文案',
+        field: 'message',
+        minWidth: 200,
+        editRender: {
+          name: '$input',
+        },
+      },
+      {
+        title: '正则表达式',
+        field: 'pattern',
+        width: 180,
+        editRender: {
+          name: '$input',
+        },
+      },
+      {
+        title: '操作',
+        field: 'options',
+        width: 120,
+        fixed: 'right',
+        slots: {
+          default: 'table-options',
+        },
+      },
+    ],
+  });
+
+  /**
+   * 校验数据
+   */
+  const validateData = (dataList: Array<any>) => {
+    // 验证类型是否重复
+    const hasType: Array<string> = [];
+    for (const { ruleType, pattern } of dataList) {
+      if (hasType.includes(ruleType)) {
+        return '校验类型不可重复:' + ruleType;
+      }
+      hasType.push(ruleType);
+      if (ruleType === 'regexp' && (pattern == null || pattern.trim() === '')) {
+        return '正则类型必须设置正则表达式';
+      }
+    }
+    return null;
+  };
+</script>
+
+<style lang="less" scoped>
+  .form-rule-set-table {
+    :deep(.ant-form-item) {
+      margin-bottom: 0;
+    }
+  }
+</style>

+ 362 - 0
src/modules/codeGenerator/views/codeDesign/componenets/PageFromSetting/PageFormSetting.vue

@@ -0,0 +1,362 @@
+<template>
+  <div class="full-height">
+    <vxe-grid
+      v-bind="$attrs"
+      ref="tableRef"
+      :size="tableSizeConfig"
+      :columns="columns"
+      row-key
+      highlight-hover-row
+      stripe
+      :data="data"
+      align="center"
+    >
+      <template #table-drop="{ rowIndex }">
+        <div class="table-drop" :data-id="rowIndex">
+          <MenuOutlined />
+        </div>
+      </template>
+      <template #table-title="{ row }">
+        <a-input v-model:value="row.title" :size="formSizeConfig" />
+      </template>
+      <template #table-visible="{ row }">
+        <a-checkbox v-model:checked="row.visible" :size="formSizeConfig" />
+      </template>
+      <template #table-hidden="{ row }">
+        <a-checkbox v-model:checked="row.hidden" :size="formSizeConfig" />
+      </template>
+      <template #table-readonly="{ row }">
+        <a-checkbox v-model:checked="row.readonly" :size="formSizeConfig" />
+      </template>
+      <template #table-useTableSearch="{ row }">
+        <a-checkbox v-model:checked="row.useTableSearch" :size="formSizeConfig" />
+      </template>
+      <template #table-tableName="{ row }">
+        <a-input
+          v-model:value="row.tableName"
+          :disabled="!row.useTableSearch"
+          :size="formSizeConfig"
+        />
+      </template>
+      <template #table-keyColumnName="{ row }">
+        <a-input
+          v-model:value="row.keyColumnName"
+          :disabled="!row.useTableSearch"
+          :size="formSizeConfig"
+        />
+      </template>
+      <template #table-valueColumnName="{ row }">
+        <a-input
+          v-model:value="row.valueColumnName"
+          :disabled="!row.useTableSearch"
+          :size="formSizeConfig"
+        />
+      </template>
+      <template #table-tableWhere="{ row }">
+        <a-input v-model:value="row.tableWhere" :size="formSizeConfig" />
+      </template>
+      <template #table-controlType="{ row }">
+        <a-select v-model:value="row.controlType" :size="formSizeConfig" style="width: 100px">
+          <a-select-option v-for="item in controlList" :key="item.key" :value="item.key">
+            {{ $t(item.value) }}
+          </a-select-option>
+        </a-select>
+        <a-tooltip v-if="row.controlType === 'SELECT_TABLE'" placement="top">
+          <template #title>
+            <span>选择表格</span>
+          </template>
+          <PlusOutlined
+            :style="{ cursor: 'pointer', 'margin-left': '5px' }"
+            @click="() => handleShowChoseSelectTable(row)"
+          />
+        </a-tooltip>
+      </template>
+      <template #table-rules="{ row }">
+        <Icon
+          v-if="row.autoValidate === true || (row.ruleList && row.ruleList.length > 0)"
+          color="red"
+          icon="ant-design:info-circle-outlined"
+        />
+        <a-button :size="tableButtonSizeConfig" @click="() => openRuleSetModal(true, row)">
+          设置规则
+        </a-button>
+      </template>
+      <template #table-visible-header="{ column }">
+        <a-checkbox v-model:checked="headerVisibleCheckboxChecked" :size="formSizeConfig" />
+        {{ $t(column.title.replace('{', '').replace('}', '')) }}
+      </template>
+      <template #table-hidden-header="{ column }">
+        <a-checkbox v-model:checked="headerHiddenCheckboxChecked" :size="formSizeConfig" />
+        {{ $t(column.title.replace('{', '').replace('}', '')) }}
+      </template>
+      <template #table-readonly-header="{ column }">
+        <a-checkbox v-model:checked="headerReadonlyCheckboxChecked" :size="formSizeConfig" />
+        {{ $t(column.title.replace('{', '').replace('}', '')) }}
+      </template>
+      <template #table-used-header="{ column }">
+        <a-checkbox v-model:checked="headerUseCheckboxChecked" :size="formSizeConfig" />
+        {{ $t(column.title.replace('{', '').replace('}', '')) }}
+      </template>
+      <template #table-used="{ row }">
+        <a-checkbox v-model:checked="row.used" :disabled="!row.visible" :size="formSizeConfig" />
+      </template>
+    </vxe-grid>
+    <!--  下拉表格设置  -->
+    <PageAddendumTableChoseModal
+      @ok="handleChoseTable"
+      :select-table-list="currentRow.selectTableList === null ? [] : currentRow.selectTableList"
+      @register="registerSelectTableModal"
+    />
+    <!--  验证规则  -->
+    <FormRuleSetModal @register="registerFormRuleSetModal" />
+  </div>
+</template>
+
+<script lang="ts">
+  import { defineComponent, ref, onMounted, watch, toRefs } from 'vue';
+  import type { Ref, PropType } from 'vue';
+  import { useI18n } from '@/hooks/web/useI18n';
+
+  import { MenuOutlined, PlusOutlined } from '@ant-design/icons-vue';
+
+  import FormRuleSetModal from './FormRuleSetModal.vue';
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+  import { useModal } from '@/components/Modal';
+  import PageAddendumTableChoseModal from '../PageAddendumTableChoseModal.vue';
+
+  import { useVxeTableSortable } from '@/components/SmartTable';
+  import {
+    controlList,
+    getRuleList,
+    vueTableHeaderCheckboxSupport,
+    vueChoseSelectTableSupport,
+  } from '../PageSettingSupport';
+  import Icon from '@/components/Icon/src/Icon.vue';
+
+  const copyField = [
+    'columnName',
+    'remarks',
+    'nullable',
+    'javaProperty',
+    'extType',
+    'javaType',
+    'simpleJavaType',
+  ];
+
+  /**
+   * 创建数据
+   */
+  const createDataFromTableData = (tableData: Array<any>, editData: Ref | undefined) => {
+    if (editData && editData.value) {
+      return editData.value;
+    }
+    return tableData.map((item) => {
+      const data: any = {};
+      copyField.forEach((field) => {
+        data[field] = item[field];
+      });
+      return Object.assign(data, {
+        title: data.remarks,
+        readonly: false,
+        visible: true,
+        hidden: false,
+        used: true,
+        controlType: 'INPUT',
+        rules: [],
+        useTableSearch: false,
+      });
+    });
+  };
+
+  export default defineComponent({
+    name: 'PageFormSetting',
+    components: {
+      Icon,
+      MenuOutlined,
+      PlusOutlined,
+      FormRuleSetModal,
+      PageAddendumTableChoseModal,
+    },
+    props: {
+      tableData: {
+        type: Array as PropType<Array<any>>,
+        default: () => [],
+      },
+      editData: {
+        type: Array as PropType<Array<any>>,
+      },
+    },
+    setup(props) {
+      const sizeConfigHoops = useSizeSetting();
+      const { t } = useI18n();
+      const { tableData, editData } = toRefs(props);
+      const tableRef = ref();
+      const data = ref<Array<any>>([]);
+      const currentRow = ref<any>({});
+
+      const [registerFormRuleSetModal, { openModal: openRuleSetModal }] = useModal();
+
+      watch(tableData, () => {
+        data.value = createDataFromTableData(tableData.value, editData);
+      });
+      onMounted(() => {
+        data.value = createDataFromTableData(tableData.value, editData);
+      });
+      const getData = () => {
+        return data.value;
+      };
+
+      const { checked } = vueTableHeaderCheckboxSupport(data, 'visible');
+      return {
+        data,
+        getData,
+        tableRef,
+        currentRow,
+        ...sizeConfigHoops,
+        openRuleSetModal,
+        registerFormRuleSetModal,
+        ...vueChoseSelectTableSupport(currentRow),
+        ...useVxeTableSortable(tableRef, '.table-drop', data),
+        ruleList: ref(getRuleList(t)),
+        controlList: ref(controlList),
+        headerVisibleCheckboxChecked: checked,
+        headerHiddenCheckboxChecked: vueTableHeaderCheckboxSupport(data, 'hidden', false).checked,
+        headerReadonlyCheckboxChecked: vueTableHeaderCheckboxSupport(data, 'readonly', false)
+          .checked,
+        headerUseCheckboxChecked: vueTableHeaderCheckboxSupport(data, 'used', true).checked,
+      };
+    },
+    data() {
+      return {
+        columns: [
+          {
+            title: '#',
+            field: 'drop',
+            width: 80,
+            slots: {
+              default: 'table-drop',
+            },
+          },
+          {
+            title: '{generator.views.tableField.title.columnName}',
+            field: 'columnName',
+            width: 160,
+            align: 'left',
+            headerAlign: 'center',
+          },
+          {
+            title: '{generator.views.tableSetting.title.title}',
+            field: 'title',
+            width: 160,
+            align: 'left',
+            headerAlign: 'center',
+            slots: {
+              default: 'table-title',
+            },
+          },
+          {
+            title: '{generator.views.formSetting.title.controlType}',
+            field: 'controlType',
+            width: 150,
+            slots: {
+              default: 'table-controlType',
+            },
+          },
+          {
+            title: '{generator.views.formSetting.title.readonly}',
+            field: 'readonly',
+            width: 110,
+            slots: {
+              default: 'table-readonly',
+              header: 'table-readonly-header',
+            },
+          },
+          {
+            title: '{generator.views.tableSetting.title.visible}',
+            field: 'visible',
+            width: 110,
+            slots: {
+              default: 'table-visible',
+              header: 'table-visible-header',
+            },
+          },
+          {
+            title: '{generator.views.tableSetting.title.hidden}',
+            field: 'hidden',
+            width: 110,
+            slots: {
+              default: 'table-hidden',
+              header: 'table-hidden-header',
+            },
+          },
+          {
+            title: '{generator.views.formSetting.title.used}',
+            field: 'used',
+            width: 110,
+            slots: {
+              default: 'table-used',
+              header: 'table-used-header',
+            },
+          },
+          {
+            title: '{generator.views.formSetting.title.useTableSearch}',
+            field: 'useTableSearch',
+            width: 110,
+            slots: {
+              default: 'table-useTableSearch',
+            },
+          },
+          {
+            title: '{generator.views.design.formSetting.title.tableName}',
+            field: 'tableName',
+            width: 120,
+            slots: {
+              default: 'table-tableName',
+            },
+          },
+          {
+            title: '{generator.views.formSetting.title.keyColumnName}',
+            field: 'keyColumnName',
+            width: 120,
+            slots: {
+              default: 'table-keyColumnName',
+            },
+          },
+          {
+            title: '{generator.views.formSetting.title.valueColumnName}',
+            field: 'valueColumnName',
+            width: 120,
+            slots: {
+              default: 'table-valueColumnName',
+            },
+          },
+          {
+            title: '{generator.views.formSetting.title.tableWhere}',
+            field: 'tableWhere',
+            minWidth: 180,
+            slots: {
+              default: 'table-tableWhere',
+            },
+          },
+          {
+            title: '{generator.views.formSetting.title.rules}',
+            field: 'rules',
+            width: 180,
+            slots: {
+              default: 'table-rules',
+            },
+          },
+          {
+            title: '{generator.views.code.table.remarks}',
+            field: 'remarks',
+            width: 160,
+            align: 'left',
+            headerAlign: 'center',
+          },
+        ],
+      };
+    },
+  });
+</script>
+
+<style scoped></style>

+ 380 - 0
src/modules/codeGenerator/views/codeDesign/componenets/PageSearchSetting/PageSearchSetting.vue

@@ -0,0 +1,380 @@
+<template>
+  <div class="full-height">
+    <vxe-grid
+      ref="tableRef"
+      row-key
+      :size="tableSizeConfig"
+      :columns="columns"
+      :data="data"
+      align="center"
+      highlight-hover-row
+      stripe
+      v-bind="$attrs"
+    >
+      <template #table-drop="{ rowIndex }">
+        <div class="table-drop" :data-id="rowIndex">
+          <MenuOutlined />
+        </div>
+      </template>
+      <template #table-title="{ row }">
+        <a-input v-model:value="row.title" :disabled="!row.visible" :size="formSizeConfig" />
+      </template>
+      <template #table-visible="{ row }">
+        <a-checkbox v-model:checked="row.visible" :size="formSizeConfig" />
+      </template>
+      <template #table-hidden="{ row }">
+        <a-checkbox v-model:checked="row.hidden" :disabled="!row.visible" :size="formSizeConfig" />
+      </template>
+      <template #table-readonly="{ row }">
+        <a-checkbox
+          v-model:checked="row.readonly"
+          :disabled="!row.visible"
+          :size="formSizeConfig"
+        />
+      </template>
+      <template #table-used="{ row }">
+        <a-checkbox v-model:checked="row.used" :disabled="!row.visible" :size="formSizeConfig" />
+      </template>
+      <template #table-searchSymbol="{ row }">
+        <a-select
+          v-model:value="row.searchSymbol"
+          :disabled="!row.visible"
+          :size="formSizeConfig"
+          style="width: 100px"
+        >
+          <a-select-option v-for="item in searchSymbolList" :key="item" :value="item">
+            {{ item }}
+          </a-select-option>
+        </a-select>
+      </template>
+      <template #table-useTableSearch="{ row }">
+        <a-checkbox
+          v-model:checked="row.useTableSearch"
+          :disabled="!row.visible"
+          :size="formSizeConfig"
+        />
+      </template>
+      <template #table-tableName="{ row }">
+        <a-input
+          v-model:value="row.tableName"
+          :disabled="!(row.useTableSearch && row.visible)"
+          :size="formSizeConfig"
+        />
+      </template>
+      <template #table-keyColumnName="{ row }">
+        <a-input
+          v-model:value="row.keyColumnName"
+          :disabled="!(row.useTableSearch && row.visible)"
+          :size="formSizeConfig"
+        />
+      </template>
+      <template #table-valueColumnName="{ row }">
+        <a-input
+          v-model:value="row.valueColumnName"
+          :disabled="!(row.useTableSearch && row.visible)"
+          :size="formSizeConfig"
+        />
+      </template>
+      <template #table-tableWhere="{ row }">
+        <a-input v-model:value="row.tableWhere" :size="formSizeConfig" />
+      </template>
+      <template #table-controlType="{ row }">
+        <a-select
+          v-model:value="row.controlType"
+          :disabled="!row.visible"
+          style="width: 100px"
+          :size="formSizeConfig"
+        >
+          <a-select-option v-for="item in controlList" :key="item.key" :value="item.key">
+            {{ $t(item.value) }}
+          </a-select-option>
+        </a-select>
+        <a-tooltip v-if="row.controlType === 'selectTable'" placement="top">
+          <template #title>
+            <span>选择表格</span>
+          </template>
+          <PlusOutlined
+            :style="{ cursor: 'pointer', 'margin-left': '5px' }"
+            @click="() => handleShowChoseSelectTable(row)"
+          />
+        </a-tooltip>
+      </template>
+      <template #table-rules="{ row }">
+        <a-select
+          v-model:value="row.rules"
+          :disabled="!row.visible"
+          mode="multiple"
+          :size="formSizeConfig"
+          style="width: 160px"
+        >
+          <a-select-option v-for="item in ruleList" :key="item.key" :value="item.key">
+            {{ item.value }}
+          </a-select-option>
+        </a-select>
+      </template>
+      <template #table-visible-header="{ column }">
+        <a-checkbox v-model:checked="headerVisibleCheckboxChecked" :size="formSizeConfig" />
+        {{ $t(column.title.replace('{', '').replace('}', '')) }}
+      </template>
+      <template #table-hidden-header="{ column }">
+        <a-checkbox v-model:checked="headerHiddenCheckboxChecked" :size="formSizeConfig" />
+        {{ $t(column.title.replace('{', '').replace('}', '')) }}
+      </template>
+      <template #table-readonly-header="{ column }">
+        <a-checkbox v-model:checked="headerReadonlyCheckboxChecked" :size="formSizeConfig" />
+        {{ $t(column.title.replace('{', '').replace('}', '')) }}
+      </template>
+      <template #table-used-header="{ column }">
+        <a-checkbox v-model:checked="headerUseCheckboxChecked" :size="formSizeConfig" />
+        {{ $t(column.title.replace('{', '').replace('}', '')) }}
+      </template>
+    </vxe-grid>
+    <PageAddendumTableChoseModal
+      @ok="handleChoseTable"
+      :select-table-list="currentRow.selectTableList == null ? [] : currentRow.selectTableList"
+      @register="registerSelectTableModal"
+    />
+  </div>
+</template>
+
+<script lang="ts">
+  import { defineComponent, onMounted, ref, toRefs, watch } from 'vue';
+  import type { Ref, PropType } from 'vue';
+
+  import { MenuOutlined, PlusOutlined } from '@ant-design/icons-vue';
+  import PageAddendumTableChoseModal from '../PageAddendumTableChoseModal.vue';
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+  import { useVxeTableSortable } from '@/components/SmartTable';
+
+  import {
+    controlList,
+    getRuleList,
+    searchSymbolList,
+    vueTableHeaderCheckboxSupport,
+    vueChoseSelectTableSupport,
+  } from '../PageSettingSupport';
+  import { useI18n } from '@/hooks/web/useI18n';
+
+  const copyField = [
+    'columnName',
+    'remarks',
+    'nullable',
+    'javaProperty',
+    'extType',
+    'javaType',
+    'simpleJavaType',
+  ];
+
+  /**
+   * 创建数据
+   */
+  const createDataFromTableData = (tableData: Array<any>, editData: Ref | undefined) => {
+    if (editData && editData.value) {
+      return editData.value;
+    }
+    return tableData.map((item) => {
+      const data: any = {};
+      copyField.forEach((field) => {
+        data[field] = item[field];
+      });
+      return Object.assign(data, {
+        title: data.remarks,
+        readonly: false,
+        visible: true,
+        hidden: false,
+        used: true,
+        controlType: 'INPUT',
+        searchSymbol: '=',
+        rules: [],
+        useTableSearch: false,
+      });
+    });
+  };
+
+  /**
+   * 搜索配置页面
+   */
+  export default defineComponent({
+    name: 'PageSearchSetting',
+    components: {
+      MenuOutlined,
+      PlusOutlined,
+      PageAddendumTableChoseModal,
+    },
+    props: {
+      tableData: {
+        type: Array as PropType<Array<any>>,
+        default: () => [],
+      },
+      editData: {
+        type: Array as PropType<Array<any>>,
+      },
+    },
+    setup(props) {
+      const { t } = useI18n();
+      const { tableData, editData } = toRefs(props);
+      const sizeConfigHoops = useSizeSetting();
+      const tableRef = ref();
+      const data = ref<Array<any>>([]);
+      const currentRow = ref<any>({});
+      watch(tableData, () => {
+        data.value = createDataFromTableData(tableData.value, editData);
+      });
+      onMounted(() => {
+        data.value = createDataFromTableData(tableData.value, editData);
+      });
+      const getData = () => {
+        return data.value;
+      };
+      return {
+        ...sizeConfigHoops,
+        data,
+        getData,
+        tableRef,
+        currentRow,
+        ...vueChoseSelectTableSupport(currentRow),
+        ...useVxeTableSortable(tableRef, '.table-drop', data),
+        ruleList: ref(getRuleList(t)),
+        controlList: ref(controlList),
+        searchSymbolList: ref(searchSymbolList),
+        headerVisibleCheckboxChecked: vueTableHeaderCheckboxSupport(data, 'visible').checked,
+        headerHiddenCheckboxChecked: vueTableHeaderCheckboxSupport(data, 'hidden', false).checked,
+        headerReadonlyCheckboxChecked: vueTableHeaderCheckboxSupport(data, 'readonly', false)
+          .checked,
+        headerUseCheckboxChecked: vueTableHeaderCheckboxSupport(data, 'used', true).checked,
+      };
+    },
+    data() {
+      return {
+        columns: [
+          {
+            title: '#',
+            field: 'drop',
+            width: 80,
+            slots: {
+              default: 'table-drop',
+            },
+          },
+          {
+            title: '{generator.views.tableField.title.columnName}',
+            field: 'columnName',
+            width: 160,
+            align: 'left',
+            headerAlign: 'center',
+          },
+          {
+            title: '{generator.views.tableSetting.title.title}',
+            field: 'title',
+            width: 160,
+            align: 'left',
+            headerAlign: 'center',
+            slots: {
+              default: 'table-title',
+            },
+          },
+          {
+            title: '{generator.views.formSetting.title.controlType}',
+            field: 'controlType',
+            width: 150,
+            slots: {
+              default: 'table-controlType',
+            },
+          },
+          {
+            title: '{generator.views.formSetting.title.readonly}',
+            field: 'readonly',
+            width: 110,
+            slots: {
+              default: 'table-readonly',
+              header: 'table-readonly-header',
+            },
+          },
+          {
+            title: '{generator.views.tableSetting.title.visible}',
+            field: 'visible',
+            width: 110,
+            slots: {
+              default: 'table-visible',
+              header: 'table-visible-header',
+            },
+          },
+          {
+            title: '{generator.views.tableSetting.title.hidden}',
+            field: 'hidden',
+            width: 110,
+            slots: {
+              default: 'table-hidden',
+              header: 'table-hidden-header',
+            },
+          },
+          {
+            title: '{generator.views.formSetting.title.used}',
+            field: 'used',
+            width: 110,
+            slots: {
+              default: 'table-used',
+              header: 'table-used-header',
+            },
+          },
+          {
+            title: '{generator.views.searchSetting.title.searchSymbol}',
+            field: 'searchSymbol',
+            width: 120,
+            slots: {
+              default: 'table-searchSymbol',
+            },
+          },
+          {
+            title: '{generator.views.formSetting.title.useTableSearch}',
+            field: 'useTableSearch',
+            width: 110,
+            slots: {
+              default: 'table-useTableSearch',
+            },
+          },
+          {
+            title: '{generator.views.code.table.tableName}',
+            field: 'tableName',
+            width: 120,
+            slots: {
+              default: 'table-tableName',
+            },
+          },
+          {
+            title: '{generator.views.formSetting.title.keyColumnName}',
+            field: 'keyColumnName',
+            width: 120,
+            slots: {
+              default: 'table-keyColumnName',
+            },
+          },
+          {
+            title: '{generator.views.formSetting.title.valueColumnName}',
+            field: 'valueColumnName',
+            width: 120,
+            slots: {
+              default: 'table-valueColumnName',
+            },
+          },
+          {
+            title: '{generator.views.formSetting.title.tableWhere}',
+            field: 'tableWhere',
+            minWidth: 180,
+            slots: {
+              default: 'table-tableWhere',
+            },
+          },
+          {
+            title: '{generator.views.code.table.remarks}',
+            field: 'remarks',
+            minWidth: 160,
+            align: 'left',
+            headerAlign: 'center',
+          },
+        ],
+      };
+    },
+  });
+</script>
+
+<style scoped></style>

+ 174 - 0
src/modules/codeGenerator/views/codeDesign/componenets/PageSettingSupport.ts

@@ -0,0 +1,174 @@
+import { ref, watch } from 'vue';
+import type { Ref } from 'vue';
+import { useModal } from '@/components/Modal';
+
+/**
+ * 空间列表
+ */
+export const controlList = [
+  {
+    key: 'INPUT',
+    value: 'generator.views.code.title.controlList.input',
+  },
+  {
+    key: 'TEXTAREA',
+    value: 'generator.views.code.title.controlList.textarea',
+  },
+  {
+    key: 'NUMBER',
+    value: 'generator.views.code.title.controlList.number',
+  },
+  {
+    key: 'PASSWORD',
+    value: 'generator.views.code.title.controlList.password',
+  },
+  {
+    key: 'SELECT',
+    value: 'generator.views.code.title.controlList.select',
+  },
+  {
+    key: 'TRANSFER',
+    value: 'generator.views.code.title.controlList.transfer',
+  },
+  {
+    key: 'SELECT_TABLE',
+    value: 'generator.views.code.title.controlList.selectTable',
+  },
+  {
+    key: 'RADIO',
+    value: 'generator.views.code.title.controlList.radio',
+  },
+  {
+    key: 'CHECKBOX',
+    value: 'generator.views.code.title.controlList.checkbox',
+  },
+  {
+    key: 'SWITCH_TYPE',
+    value: 'generator.views.code.title.controlList.switch_type',
+  },
+  {
+    key: 'DATE',
+    value: 'generator.views.code.title.controlList.date',
+  },
+  {
+    key: 'TIME',
+    value: 'generator.views.code.title.controlList.time',
+  },
+  {
+    key: 'DATETIME',
+    value: 'generator.views.code.title.controlList.datetime',
+  },
+  {
+    key: 'FILE',
+    value: 'generator.views.code.title.controlList.file',
+  },
+  {
+    key: 'DATA_DICT',
+    value: 'generator.views.design.title.controlList.dataDict',
+  },
+  {
+    key: 'CATEGORY_DICT',
+    value: 'generator.views.design.title.controlList.categoryDict',
+  },
+];
+
+/**
+ * rule列表
+ */
+const ruleList = [
+  {
+    value: 'NOT_EMPTY',
+    label: 'generator.views.code.title.ruleList.notEmpty',
+  },
+  {
+    value: 'PHONE',
+    label: 'generator.views.code.title.ruleList.PHONE',
+  },
+  {
+    value: 'EMAIL',
+    label: 'generator.views.code.title.ruleList.EMAIL',
+  },
+  {
+    value: 'NUMBER',
+    label: 'generator.views.code.title.ruleList.NUMBER',
+  },
+  {
+    value: 'REGEXP',
+    label: 'generator.views.code.title.ruleList.REGEXP',
+  },
+];
+
+export const getRuleList = (t: Function): Recordable[] => {
+  return ruleList.map((item) => {
+    return {
+      ...item,
+      label: t(item.label),
+    };
+  });
+};
+
+/**
+ * 查询标识列表
+ */
+export const searchSymbolList = [
+  '=',
+  'like',
+  '>',
+  '>=',
+  '<',
+  '<=',
+  'in',
+  'notIn',
+  'notLike',
+  'likeLeft',
+  'likeRight',
+];
+
+/**
+ * table header checkbox
+ * @param tableData
+ * @param field
+ * @param defaultValue
+ */
+export const vueTableHeaderCheckboxSupport = (
+  tableData: Ref,
+  field: string,
+  defaultValue = true,
+) => {
+  const checked = ref(defaultValue);
+  watch(checked, () => {
+    tableData.value.forEach((item: any) => {
+      item[field] = checked.value;
+    });
+  });
+  return {
+    checked,
+  };
+};
+
+/**
+ * 下拉表格支持
+ */
+export const vueChoseSelectTableSupport = (currentRow: Ref) => {
+  const [registerSelectTableModal, { openModal: openSelectTableModal }] = useModal();
+  /**
+   * 显示列选择
+   * @param row
+   */
+  const handleShowChoseSelectTable = (row: any) => {
+    currentRow.value = row;
+    openSelectTableModal(true, {});
+  };
+  /**
+   * 选择表格后
+   * @param tableList
+   */
+  const handleChoseTable = (tableList: Array<any>) => {
+    currentRow.value.selectTableList = tableList;
+  };
+  return {
+    registerSelectTableModal,
+    handleShowChoseSelectTable,
+    handleChoseTable,
+  };
+};

+ 331 - 0
src/modules/codeGenerator/views/codeDesign/componenets/PageTableSetting/PageTableSetting.vue

@@ -0,0 +1,331 @@
+<template>
+  <div class="full-height">
+    <vxe-grid
+      v-bind="$attrs"
+      ref="tableRef"
+      :size="tableSizeConfig"
+      :data="data"
+      row-key
+      align="center"
+      highlight-hover-row
+      stripe
+      :columns="columns"
+    >
+      <template #table-sortable="{ row }">
+        <a-checkbox v-model:checked="row.sortable" :size="formSizeConfig" />
+      </template>
+      <template #table-fixed="{ row }">
+        <a-select v-model:value="row.fixed" :size="formSizeConfig" style="width: 100px">
+          <a-select-option value="left">left</a-select-option>
+          <a-select-option value="right">right</a-select-option>
+        </a-select>
+      </template>
+      <template #table-resizable="{ row }">
+        <a-checkbox v-model:checked="row.resizable" :size="formSizeConfig" />
+      </template>
+      <template #table-visible="{ row }">
+        <a-checkbox v-model:checked="row.visible" :size="formSizeConfig" />
+      </template>
+      <template #table-hidden="{ row }">
+        <a-checkbox v-model:checked="row.hidden" :size="formSizeConfig" />
+      </template>
+      <template #table-align="{ row }">
+        <a-select v-model:value="row.align" :size="formSizeConfig" style="width: 100px">
+          <a-select-option value="left">left</a-select-option>
+          <a-select-option value="center">center</a-select-option>
+          <a-select-option value="right">right</a-select-option>
+        </a-select>
+      </template>
+      <template #table-title="{ row }">
+        <a-input v-model:value="row.title" :size="formSizeConfig" />
+      </template>
+      <template #table-width="{ row }">
+        <a-input v-model:value="row.width" :size="formSizeConfig" />
+      </template>
+      <template #table-format="{ row }">
+        <a-input v-model:value="row.format" :size="formSizeConfig" />
+      </template>
+      <template #table-drop="{ rowIndex }">
+        <div class="table-drop" :data-id="rowIndex">
+          <MenuOutlined />
+        </div>
+      </template>
+      <template #table-visible-header="{ column }">
+        <a-checkbox v-model:checked="headerVisibleCheckboxChecked" :size="formSizeConfig" />
+        {{ $t(column.title.replace('{', '').replace('}', '')) }}
+      </template>
+      <template #table-sortable-header="{ column }">
+        <a-checkbox v-model:checked="headerSortableCheckboxChecked" :size="formSizeConfig" />
+        {{ $t(column.title.replace('{', '').replace('}', '')) }}
+      </template>
+      <template #table-resizable-header="{ column }">
+        <a-checkbox v-model:checked="headerResizableCheckboxChecked" :size="formSizeConfig" />
+        {{ $t(column.title.replace('{', '').replace('}', '')) }}
+      </template>
+      <template #table-hidden-header="{ column }">
+        <a-checkbox v-model:checked="headerHiddenCheckboxChecked" :size="formSizeConfig" />
+        {{ $t(column.title.replace('{', '').replace('}', '')) }}
+      </template>
+      <!--   是否可编辑   -->
+      <template #table-editable="{ row }">
+        <a-checkbox v-model:checked="row.editable" :size="formSizeConfig" />
+      </template>
+      <template #table-editable-header="{ column }">
+        <a-checkbox v-model:checked="headerEditableCheckboxChecked" :size="formSizeConfig" />
+        {{ $t(column.title.replace('{', '').replace('}', '')) }}
+      </template>
+    </vxe-grid>
+  </div>
+</template>
+
+<script lang="ts">
+  import { defineComponent, ref, watch, toRefs, onMounted, unref } from 'vue';
+  import type { Ref, PropType } from 'vue';
+
+  import { MenuOutlined } from '@ant-design/icons-vue';
+
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+  import { useVxeTableSortable } from '@/components/SmartTable';
+  import { vueTableHeaderCheckboxSupport } from '../PageSettingSupport';
+
+  /**
+   * copy列
+   */
+  const copyField = [
+    'columnName',
+    'javaProperty',
+    'javaType',
+    'typeName',
+    'columnSize',
+    'decimalDigits',
+    'columnDef',
+    'nullable',
+    'remarks',
+    'primaryKey',
+    'indexed',
+    'tableName',
+    'extType',
+    'simpleJavaType',
+  ];
+
+  /**
+   * 创建数据
+   */
+  const createDataFromTableData = (
+    tableData: Array<any>,
+    editData: Ref<Array<any> | undefined>,
+  ) => {
+    if (editData && editData.value) {
+      const tableDataMap: Record<string, any> = {};
+      tableData.forEach((item) => {
+        tableDataMap[item.javaProperty] = item;
+      });
+      return unref(editData)!.map((item) => {
+        const itemData = {
+          ...item,
+        };
+        const tableDataItem = tableDataMap[item.javaProperty];
+        copyField.forEach((field) => {
+          itemData[field] = tableDataItem[field];
+        });
+        return itemData;
+      });
+    }
+    return tableData.map((item) => {
+      const data: any = {};
+      copyField.forEach((field) => {
+        data[field] = item[field];
+      });
+      // 获取align
+      let align = 'left';
+      const typeName = item.typeName;
+      if (['DATETIME', 'DATE', 'TIME'].includes(typeName)) {
+        align = 'center';
+      }
+      if (['INT', 'NUMBER', 'NUMERIC', 'LONG', 'BIGINT'].includes(typeName)) {
+        align = 'right';
+      }
+      return Object.assign(data, {
+        title: data.remarks && data.remarks.trim() !== '' ? data.remarks : data.javaProperty,
+        sortable: false,
+        fixed: null,
+        width: 120,
+        align: align,
+        resizable: false,
+        visible: true,
+        hidden: false,
+        format: '',
+        // 是否可编辑
+        editable: false,
+      });
+    });
+  };
+
+  /**
+   * 页面表格配置组件
+   */
+  export default defineComponent({
+    name: 'PageTableSetting',
+    components: {
+      MenuOutlined,
+    },
+    props: {
+      tableData: {
+        type: Array as PropType<Array<any>>,
+        default: () => [],
+      },
+      editData: {
+        type: Array as PropType<Array<any> | undefined>,
+      },
+    },
+    setup(props) {
+      const tableRef = ref();
+      const { tableData, editData } = toRefs(props);
+      const data = ref<Array<any>>([]);
+      const tableSortableVue = useVxeTableSortable(tableRef, '.table-drop', data);
+      watch([tableData, editData], () => {
+        data.value = createDataFromTableData(tableData.value, editData);
+        console.log(data.value);
+      });
+      onMounted(() => {
+        data.value = createDataFromTableData(tableData.value, editData);
+      });
+      const getData = () => {
+        return data.value;
+      };
+      const sizeConfigHoops = useSizeSetting();
+      return {
+        ...sizeConfigHoops,
+        ...tableSortableVue,
+        data,
+        getData,
+        tableRef,
+        headerVisibleCheckboxChecked: vueTableHeaderCheckboxSupport(data, 'visible').checked,
+        headerSortableCheckboxChecked: vueTableHeaderCheckboxSupport(data, 'sortable', false)
+          .checked,
+        headerResizableCheckboxChecked: vueTableHeaderCheckboxSupport(data, 'resizable', false)
+          .checked,
+        headerHiddenCheckboxChecked: vueTableHeaderCheckboxSupport(data, 'hidden', false).checked,
+        headerEditableCheckboxChecked: vueTableHeaderCheckboxSupport(data, 'editable', false)
+          .checked,
+      };
+    },
+    data() {
+      return {
+        columns: [
+          {
+            title: '#',
+            field: 'drop',
+            width: 80,
+            slots: {
+              default: 'table-drop',
+            },
+          },
+          {
+            title: '{generator.views.tableField.title.columnName}',
+            field: 'columnName',
+            width: 160,
+            align: 'left',
+            headerAlign: 'center',
+          },
+          {
+            title: '{generator.views.tableField.title.remarks}',
+            field: 'remarks',
+            width: 160,
+            align: 'left',
+            headerAlign: 'center',
+          },
+          {
+            title: '{generator.views.tableSetting.title.title}',
+            field: 'title',
+            width: 160,
+            align: 'left',
+            headerAlign: 'center',
+            slots: {
+              default: 'table-title',
+            },
+          },
+          {
+            title: '{generator.views.tableSetting.title.sortable}',
+            field: 'sortable',
+            width: 110,
+            slots: {
+              default: 'table-sortable',
+              header: 'table-sortable-header',
+            },
+          },
+          {
+            title: '{generator.views.tableSetting.title.fixed}',
+            field: 'fixed',
+            width: 120,
+            slots: {
+              default: 'table-fixed',
+            },
+          },
+          {
+            title: '{generator.views.tableSetting.title.width}',
+            field: 'width',
+            width: 120,
+            slots: {
+              default: 'table-width',
+            },
+          },
+          {
+            title: '{generator.views.tableSetting.title.align}',
+            field: 'align',
+            width: 120,
+            slots: {
+              default: 'table-align',
+            },
+          },
+          {
+            title: '{generator.views.tableSetting.title.resizable}',
+            field: 'resizable',
+            width: 110,
+            slots: {
+              default: 'table-resizable',
+              header: 'table-resizable-header',
+            },
+          },
+          {
+            title: '{generator.views.tableSetting.title.visible}',
+            field: 'visible',
+            width: 110,
+            slots: {
+              default: 'table-visible',
+              header: 'table-visible-header',
+            },
+          },
+          {
+            title: '{generator.views.tableSetting.title.hidden}',
+            field: 'hidden',
+            width: 110,
+            slots: {
+              default: 'table-hidden',
+              header: 'table-hidden-header',
+            },
+          },
+          {
+            title: '{generator.views.tableSetting.title.editable}',
+            field: 'editable',
+            width: 110,
+            slots: {
+              default: 'table-editable',
+              header: 'table-editable-header',
+            },
+          },
+          // {
+          //   title: '{generator.views.tableSetting.title.format}',
+          //   field: 'format',
+          //   width: 120,
+          //   slots: {
+          //     default: 'table-format'
+          //   }
+          // }
+        ],
+      };
+    },
+  });
+</script>
+
+<style scoped></style>

+ 107 - 0
src/modules/codeGenerator/views/codeDesign/componenets/TableFieldTable/TableFieldTable.vue

@@ -0,0 +1,107 @@
+<template>
+  <div>
+    <vxe-grid
+      align="center"
+      stripe
+      v-bind="$attrs"
+      :size="tableSizeConfig"
+      border
+      :columns="columns"
+    >
+      <template #table-nullable="{ row }">
+        <a-switch :size="formSizeConfig" disabled :checked="row.nullable === 1" />
+      </template>
+      <template #table-primaryKey="{ row }">
+        <a-switch :size="formSizeConfig" disabled :checked="row.primaryKey" />
+      </template>
+      <template #table-indexed="{ row }">
+        <a-switch :size="formSizeConfig" disabled :checked="row.indexed" />
+      </template>
+    </vxe-grid>
+  </div>
+</template>
+
+<script lang="ts">
+  import { defineComponent } from 'vue';
+
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+
+  /**
+   * 数据库字段信息
+   */
+  export default defineComponent({
+    name: 'TableFieldTable',
+    setup() {
+      const sizeConfigHoops = useSizeSetting();
+      return {
+        ...sizeConfigHoops,
+      };
+    },
+    data() {
+      return {
+        columns: [
+          {
+            field: 'columnName',
+            title: '{generator.views.tableField.title.columnName}',
+            width: 160,
+            align: 'left',
+            headerAlign: 'center',
+          },
+          {
+            field: 'typeName',
+            title: '{generator.views.tableField.title.typeName}',
+            width: 120,
+          },
+          {
+            field: 'columnSize',
+            title: '{generator.views.tableField.title.columnSize}',
+            width: 120,
+          },
+          {
+            field: 'decimalDigits',
+            title: '{generator.views.tableField.title.decimalDigits}',
+            width: 120,
+          },
+          {
+            field: 'columnDef',
+            title: '{generator.views.tableField.title.columnDef}',
+            width: 120,
+          },
+          {
+            field: 'nullable',
+            title: '{generator.views.tableField.title.nullable}',
+            width: 120,
+            slots: {
+              default: 'table-nullable',
+            },
+          },
+          {
+            field: 'remarks',
+            title: '{generator.views.tableField.title.remarks}',
+            minWidth: 120,
+            align: 'left',
+            headerAlign: 'center',
+          },
+          {
+            field: 'primaryKey',
+            title: '{generator.views.tableField.title.primaryKey}',
+            width: 120,
+            slots: {
+              default: 'table-primaryKey',
+            },
+          },
+          {
+            field: 'indexed',
+            title: '{generator.views.tableField.title.indexed}',
+            width: 120,
+            slots: {
+              default: 'table-indexed',
+            },
+          },
+        ],
+      };
+    },
+  });
+</script>
+
+<style scoped></style>

+ 20 - 0
src/modules/codeGenerator/views/codeDesign/lang/en_US.ts

@@ -0,0 +1,20 @@
+export default {
+  trans: true,
+  key: 'generator.views.design',
+  data: {
+    title: {
+      controlList: {
+        dataDict: 'Data dict',
+        categoryDict: 'Category dict',
+      },
+      colNum: {
+        zero: 'inline',
+      },
+    },
+    formSetting: {
+      title: {
+        tableName: 'Model class',
+      },
+    },
+  },
+};

+ 20 - 0
src/modules/codeGenerator/views/codeDesign/lang/zh_CN.ts

@@ -0,0 +1,20 @@
+export default {
+  trans: true,
+  key: 'generator.views.design',
+  data: {
+    title: {
+      controlList: {
+        dataDict: '数据字典',
+        categoryDict: '分类字典',
+      },
+      colNum: {
+        zero: '单行',
+      },
+    },
+    formSetting: {
+      title: {
+        tableName: '实体类名',
+      },
+    },
+  },
+};

+ 16 - 0
src/modules/codeGenerator/views/codeList/CodeListView.api.ts

@@ -0,0 +1,16 @@
+import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+enum Api {
+  listBySystem = 'db/code/main/listBySystem',
+  delete = 'db/code/main/batchDeleteById',
+}
+
+export const listBySystemApi = (parameter) =>
+  defHttp.post({ service: ApiServiceEnum.SMART_CODE, url: Api.listBySystem, data: parameter });
+
+export const deleteApi = (data) =>
+  defHttp.post({
+    service: ApiServiceEnum.SMART_CODE,
+    url: Api.delete,
+    data: data.map((item: any) => item.id),
+  });

+ 136 - 0
src/modules/codeGenerator/views/codeList/CodeListView.config.tsx

@@ -0,0 +1,136 @@
+import type { SmartColumn, SmartSearchFormSchema } from '@/components/SmartTable';
+
+const tableTypeList = [
+  {
+    label: 'generator.views.code.title.tableType.single',
+    value: '10',
+    color: 'green',
+  },
+  {
+    label: 'generator.views.code.title.tableType.main',
+    value: '20',
+    color: 'blue',
+  },
+  {
+    label: 'generator.views.code.title.tableType.addendum',
+    value: '30',
+    color: 'purple',
+  },
+];
+
+export const tableColumns = (t: Function): SmartColumn[] => {
+  return [
+    {
+      type: 'checkbox',
+      width: 60,
+      fixed: 'left',
+    },
+    {
+      title: '{generator.views.code.table.connectionName}',
+      field: 'connectionName',
+      width: 160,
+      fixed: 'left',
+    },
+    {
+      title: '{generator.views.code.table.configName}',
+      field: 'configName',
+      width: 160,
+      fixed: 'left',
+    },
+    {
+      title: '{generator.views.code.table.tableName}',
+      field: 'tableName',
+      width: 160,
+      fixed: 'left',
+    },
+    {
+      title: '{generator.views.code.table.type}',
+      field: 'type',
+      width: 120,
+      slots: {
+        default: ({ row }) => {
+          const value = row.type;
+          if (value) {
+            const filterList = tableTypeList.filter((item) => item.value === value);
+            if (filterList.length > 0) {
+              const data = filterList[0];
+              return <a-tag color={data.color}>{t(data.label)}</a-tag>;
+            }
+          }
+          return '';
+        },
+      },
+    },
+    {
+      title: '{generator.views.code.table.remarks}',
+      field: 'remarks',
+      minWidth: 200,
+    },
+    {
+      title: '{common.table.remark}',
+      field: 'remark',
+      minWidth: 200,
+    },
+    {
+      title: '{common.table.createTime}',
+      field: 'createTime',
+      width: 165,
+      sortable: true,
+    },
+    {
+      title: '{common.table.createUser}',
+      field: 'createBy',
+      width: 120,
+    },
+    {
+      title: '{common.table.updateTime}',
+      field: 'updateTime',
+      width: 165,
+      sortable: true,
+    },
+    {
+      title: '{common.table.updateUser}',
+      field: 'updateBy',
+      width: 120,
+    },
+    {
+      title: '{common.table.operation}',
+      field: 'operation',
+      width: 140,
+      fixed: 'right',
+      slots: {
+        default: 'table-operation',
+      },
+    },
+  ];
+};
+
+export const searchFormColumns = (t: Function): SmartSearchFormSchema[] => {
+  return [
+    {
+      field: 'tableName',
+      label: '',
+      component: 'Input',
+      componentProps: {
+        placeholder: t('generator.views.code.table.tableName'),
+      },
+    },
+    {
+      field: 'type',
+      label: '',
+      component: 'Select',
+      componentProps: {
+        style: {
+          width: '100px',
+        },
+        placeholder: t('generator.views.code.table.type'),
+        options: tableTypeList.map((item) => {
+          return {
+            ...item,
+            label: t(item.label),
+          };
+        }),
+      },
+    },
+  ];
+};

+ 175 - 0
src/modules/codeGenerator/views/codeList/CodeListView.vue

@@ -0,0 +1,175 @@
+<template>
+  <div class="full-height page-container">
+    <LayoutSeparate first-size="240px" :show-line="false" class="full-height">
+      <template #first>
+        <div class="full-height system-container">
+          <SystemSimpleList
+            @current-change="handleSelectSystemChange"
+            :row-config="{ isHover: true, isCurrent: true }"
+            height="auto"
+          />
+        </div>
+      </template>
+      <template #second>
+        <SmartTable @register="registerTable" :size="getTableSize">
+          <template #table-operation="{ row }">
+            <SmartVxeTableAction :actions="getTableAction(row)" />
+          </template>
+        </SmartTable>
+      </template>
+    </LayoutSeparate>
+    <CodeCreateModal @register="registerCodeCreateModal" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import type { ActionItem } from '@/components/SmartTable';
+
+  import { tableColumns, searchFormColumns } from './CodeListView.config';
+  import { listBySystemApi, deleteApi } from './CodeListView.api';
+  import { useRouter } from 'vue-router';
+  import { buildUUID } from '@/utils/uuid';
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+  import { useModal } from '@/components/Modal';
+  import { useI18n } from '@/hooks/web/useI18n';
+
+  import { SmartVxeTableAction, SmartTable, useSmartTable } from '@/components/SmartTable';
+  import CodeCreateModal from './components/CodeCreateModal.vue';
+  import { LayoutSeparate } from '@/components/LayoutSeparate';
+  import SystemSimpleList from '@/modules/system/components/system/SystemSimpleList.vue';
+  import { errorMessage } from '@/utils/message/SystemNotice';
+
+  const { t } = useI18n();
+  const router = useRouter();
+  const { getTableSize } = useSizeSetting();
+
+  const toDesign = (configId?: number) => {
+    if (!currentSystem?.id) {
+      errorMessage(t('generator.views.code.message.noSelectSystem'));
+      return false;
+    }
+    router.push({
+      path: '/code/codeDesign',
+      query: {
+        setKey: buildUUID(),
+        configId,
+        systemId: currentSystem.id,
+      },
+    });
+  };
+
+  let currentSystem: Recordable = {};
+  const handleSelectSystemChange = (row) => {
+    currentSystem = row;
+    reload();
+  };
+
+  const getTableAction = (row): ActionItem[] => {
+    return [
+      {
+        label: t('common.button.edit'),
+        onClick: () => toDesign(row.id),
+      },
+      {
+        label: t('generator.views.code.button.createCode'),
+        onClick: () => openCodeCreateModal(true, row),
+      },
+    ];
+  };
+
+  // 生成代码弹窗
+  const [registerCodeCreateModal, { openModal: openCodeCreateModal }] = useModal();
+
+  const [registerTable, { reload }] = useSmartTable({
+    searchFormConfig: {
+      searchWithSymbol: true,
+      layout: 'inline',
+      schemas: searchFormColumns(t),
+      actionColOptions: {
+        span: undefined,
+      },
+      baseRowStyle: {
+        width: '100%',
+      },
+      labelAlign: 'left',
+    },
+    height: 'auto',
+    columns: tableColumns(t),
+    useSearchForm: true,
+    pagerConfig: true,
+    sortConfig: {
+      remote: true,
+    },
+    columnConfig: {
+      resizable: true,
+    },
+    proxyConfig: {
+      autoLoad: false,
+      ajax: {
+        query: (params) => {
+          const queryParameter = {
+            ...params.ajaxParameter,
+            systemId: currentSystem.id,
+          };
+          return listBySystemApi(queryParameter);
+        },
+        delete: ({ body }) => deleteApi(body.removeRecords),
+      },
+    },
+    toolbarConfig: {
+      refresh: true,
+      buttons: [
+        {
+          code: 'ModalAdd',
+          props: {
+            onClick: () => toDesign(),
+          },
+        },
+        {
+          code: 'delete',
+        },
+      ],
+    },
+  });
+</script>
+
+<style lang="less" scoped>
+  .system-container {
+    margin-right: 5px;
+  }
+
+  .code-container {
+    ::v-deep(.ant-modal-content) {
+      height: 100%;
+    }
+
+    ::v-deep(.ant-modal) {
+      max-width: 100%;
+    }
+
+    ::v-deep(.ant-form-item) {
+      margin-bottom: 0;
+    }
+
+    ::v-deep(.ant-list-item) {
+      padding: 8px 0;
+    }
+
+    ::v-deep(.ant-modal-body) {
+      padding: 10px;
+    }
+
+    ::v-deep(.ant-tabs-top-content) {
+      height: calc(100% - 65px);
+    }
+
+    ::v-deep(.ant-spin-nested-loading) {
+      height: 100%;
+    }
+
+    ::v-deep(.ant-spin-container) {
+      height: 100%;
+      overflow: auto;
+    }
+  }
+</style>

+ 187 - 0
src/modules/codeGenerator/views/codeList/components/CodeCreateForm.vue

@@ -0,0 +1,187 @@
+<template>
+  <a-form
+    ref="formRef"
+    class="create-code-form"
+    :model="formModel"
+    :rules="rules"
+    :wrapper-col="wrapperCol"
+    :label-col="labelCol"
+  >
+    <a-form-item :label="$t('generator.views.codeCreateForm.title.description')">
+      <a-input v-model:value="formModel.description" />
+    </a-form-item>
+    <a-form-item :label="$t('generator.views.codeCreateForm.title.tableName')">
+      <a-input v-model:value="formModel.tableName" disabled />
+    </a-form-item>
+    <a-form-item name="className" :label="$t('generator.views.codeCreateForm.title.className')">
+      <a-input v-model:value="formModel.className" />
+    </a-form-item>
+    <a-form-item name="packages" :label="$t('generator.views.codeCreateForm.title.packages')">
+      <a-input v-model:value="formModel.packages" />
+    </a-form-item>
+    <a-form-item :label="$t('generator.views.codeCreateForm.title.extPackages')">
+      <a-input v-model:value="formModel.extPackages" />
+    </a-form-item>
+    <a-form-item :label="$t('generator.views.codeCreateForm.title.controllerBasePath')">
+      <a-input v-model:value="formModel.controllerBasePath" />
+    </a-form-item>
+    <div>
+      <a-transfer
+        :target-keys="targetKeysModel"
+        :render="(item) => item.title"
+        :data-source="transDataSource"
+        show-search
+        @change="handleTransChange"
+      />
+    </div>
+  </a-form>
+</template>
+
+<script lang="ts">
+  import { defineComponent, reactive, toRefs, watch, ref, onMounted, type PropType } from 'vue';
+  import { useI18n } from 'vue-i18n';
+
+  import { message } from 'ant-design-vue';
+
+  import { TemplateType } from '@/modules/codeGenerator/constants/DatabaseConstants';
+
+  import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+  const modelData = {
+    description: '',
+    packages: '',
+    tableName: '',
+    className: '',
+    controllerBasePath: '',
+    extPackages: '',
+  };
+
+  /**
+   * 代码生成form
+   */
+  export default defineComponent({
+    name: 'CodeCreateForm',
+    props: {
+      codeData: {
+        type: Object as PropType<any>,
+        default: () => ({}) as any,
+      },
+    },
+    setup(props) {
+      const { t } = useI18n();
+      const { codeData } = toRefs(props);
+      const formRef = ref();
+      const formModel = reactive(modelData);
+      const transDataSource = ref([]);
+      const dataLoading = ref(false);
+      const targetKeysModel = ref<Array<string>>([]);
+      watch(codeData, () => {
+        formModel.description = codeData.value.remarks;
+        formModel.tableName = codeData.value.tableName;
+        formModel.className = codeData.value.className;
+        targetKeysModel.value = [];
+      });
+      const loadTemplateData = async () => {
+        dataLoading.value = true;
+        try {
+          const result = await defHttp.post({
+            service: ApiServiceEnum.SMART_CODE,
+            url: 'db/code/template/list',
+            data: {
+              parameter: {
+                'templateType@=': TemplateType.TEMPLATE_CODE.value,
+              },
+            },
+          });
+          transDataSource.value = result.map((item: any) => {
+            return {
+              key: item.templateId + '',
+              title: item.name,
+            };
+          });
+        } finally {
+          dataLoading.value = false;
+        }
+      };
+      const handleTransChange = (targetKeys: Array<string>) => {
+        targetKeysModel.value = targetKeys;
+      };
+      onMounted(loadTemplateData);
+      /**
+       * 获取form的值
+       */
+      const getFormData = () => {
+        return {
+          mainId: codeData.value.id,
+          ...formModel,
+          templateIdList: targetKeysModel.value,
+        };
+      };
+      /**
+       * 验证表单
+       */
+      const validate = (): Promise<any> => {
+        return formRef.value.validate().then(() => {
+          if (targetKeysModel.value.length === 0) {
+            const errorMessage = t('generator.views.codeCreateForm.message.choseTemplate');
+            message.error(errorMessage);
+            return Promise.reject(new Error(errorMessage));
+          }
+        });
+      };
+      return {
+        formModel,
+        transDataSource,
+        dataLoading,
+        handleTransChange,
+        targetKeysModel,
+        getFormData,
+        formRef,
+        validate,
+      };
+    },
+    data() {
+      return {
+        labelCol: {
+          span: 6,
+        },
+        wrapperCol: {
+          span: 17,
+        },
+        rules: {
+          packages: [
+            {
+              required: true,
+              message: this.$t('generator.views.codeCreateForm.validate.packages'),
+              trigger: 'blur',
+            },
+          ],
+          className: [
+            {
+              required: true,
+              message: this.$t('generator.views.codeCreateForm.validate.className'),
+              trigger: 'blur',
+            },
+          ],
+          controllerBasePath: [
+            {
+              required: true,
+              message: this.$t('generator.views.codeCreateForm.validate.controllerBasePath'),
+              trigger: 'blur',
+            },
+          ],
+        },
+      };
+    },
+  });
+</script>
+
+<style lang="less" scoped>
+  .create-code-form {
+    ::v-deep(.ant-transfer-list) {
+      flex: none;
+      width: 46%;
+      height: 450px;
+    }
+  }
+</style>

+ 147 - 0
src/modules/codeGenerator/views/codeList/components/CodeCreateModal.vue

@@ -0,0 +1,147 @@
+<template>
+  <BasicModal
+    @register="registerModal"
+    width="800px"
+    @ok="handleOk"
+    style="top: 15px"
+    :title="$t('generator.views.code.button.createCode')"
+  >
+    <BasicForm @register="registerForm">
+      <template #form-templateIdList="{ model }">
+        <SmartTableSelect
+          v-model:value="model.templateIdList"
+          :table-props="{}"
+          title="选择模板"
+          defaultFullscreen
+          multiple
+          :list-api="listByIdApi"
+          label-field="name"
+          value-field="templateId"
+        >
+          <template #table="{ addSelectData, removeSelectData, selectData }">
+            <TemplateSelectTable
+              :add-select-data="addSelectData"
+              :remove-select-data="removeSelectData"
+              :select-data="selectData"
+            />
+          </template>
+        </SmartTableSelect>
+      </template>
+    </BasicForm>
+  </BasicModal>
+</template>
+
+<script lang="ts" setup>
+  import { useI18n } from '@/hooks/web/useI18n';
+  import { useRouter } from 'vue-router';
+  import { message } from 'ant-design-vue';
+  import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+  import { BasicModal, useModalInner } from '@/components/Modal';
+  import { BasicForm, useForm, SmartTableSelect } from '@/components/Form';
+  import TemplateSelectTable from './TemplateSelectTable.vue';
+
+  const router = useRouter();
+  const { t } = useI18n();
+
+  const handleOk = async () => {
+    const model = await validate();
+    const templateIdList = model.templateIdList;
+    if (!templateIdList || templateIdList.length === 0) {
+      message.warn(t('generator.views.codeCreateForm.message.choseTemplate'));
+    }
+    const url = router.resolve({
+      path: '/codeCreateView',
+      query: {
+        ...model,
+        templateIdList: templateIdList.join(','),
+      },
+    });
+    window.open(url.href, '_blank');
+  };
+
+  const [registerModal] = useModalInner((codeConfigData: Recordable) => {
+    const { remarks, tableName, className, id } = codeConfigData;
+    setFieldsValue({
+      description: remarks,
+      tableName,
+      className,
+      mainId: id,
+    });
+  });
+
+  const listByIdApi = (ids) => {
+    return defHttp.post({
+      service: ApiServiceEnum.SMART_CODE,
+      url: 'db/code/template/listById',
+      data: ids,
+    });
+  };
+
+  const [registerForm, { setFieldsValue, validate }] = useForm({
+    labelCol: {
+      span: 6,
+    },
+    wrapperCol: {
+      span: 17,
+    },
+    baseColProps: {
+      span: 24,
+    },
+    showActionButtonGroup: false,
+    schemas: [
+      {
+        label: '',
+        field: 'mainId',
+        component: 'Input',
+        show: false,
+      },
+      {
+        label: t('generator.views.codeCreateForm.title.description'),
+        field: 'description',
+        component: 'Input',
+      },
+      {
+        label: t('generator.views.codeCreateForm.title.tableName'),
+        field: 'tableName',
+        component: 'Input',
+        componentProps: {
+          disabled: true,
+        },
+      },
+      {
+        label: t('generator.views.codeCreateForm.title.className'),
+        field: 'className',
+        component: 'Input',
+        required: true,
+      },
+      {
+        label: t('generator.views.codeCreateForm.title.packages'),
+        field: 'packages',
+        component: 'Input',
+        required: true,
+      },
+      {
+        label: t('generator.views.codeCreateForm.title.controllerBasePath'),
+        field: 'controllerBasePath',
+        component: 'Input',
+        required: true,
+      },
+      {
+        label: t('generator.views.codeCreateForm.title.customConfig'),
+        field: 'customConfig',
+        component: 'InputTextArea',
+        componentProps: {
+          placeholder: t('generator.views.codeCreateForm.message.customConfig'),
+        },
+      },
+      {
+        label: t('generator.views.codeCreateForm.title.templateList'),
+        field: 'templateIdList',
+        slot: 'form-templateIdList',
+      },
+    ],
+  });
+</script>
+
+<style scoped></style>

+ 9 - 0
src/modules/codeGenerator/views/codeList/components/CodeListAddEditModal.vue

@@ -0,0 +1,9 @@
+<template>
+  <BasicModal />
+</template>
+
+<script lang="ts" setup>
+  import BasicModal from '@/components/Modal/src/BasicModal.vue';
+</script>
+
+<style scoped></style>

+ 150 - 0
src/modules/codeGenerator/views/codeList/components/TemplateSelectTable.vue

@@ -0,0 +1,150 @@
+<template>
+  <LayoutSeparate first-size="200px" :show-line="false" class="full-height">
+    <template #first>
+      <TemplateGroup class="full-height" @current-change="handleCurrentChange" />
+    </template>
+    <template #second>
+      <SmartTable
+        @register="registerTable"
+        @checkbox-change="handleCheckboxChange"
+        @proxy-query="resetCheckbox"
+      />
+    </template>
+  </LayoutSeparate>
+</template>
+
+<script lang="ts" setup>
+  import { useI18n } from '@/hooks/web/useI18n';
+  import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+  import { merge } from 'lodash-es';
+
+  import { SmartTable, useSmartTable } from '@/components/SmartTable';
+  import { LayoutSeparate } from '@/components/LayoutSeparate';
+  import { TemplateType as templateTypeConstants } from '@/modules/codeGenerator/constants/DatabaseConstants';
+  import TemplateGroup from '@/modules/codeGenerator/components/template/TemplateGroup.vue';
+  import { watch } from 'vue';
+
+  const props = defineProps({
+    addSelectData: {
+      type: Function as PropType<Function>,
+      required: true,
+    },
+    removeSelectData: {
+      type: Function as PropType<Function>,
+      required: true,
+    },
+    selectData: Array,
+  });
+  const { t } = useI18n();
+
+  let currentGroup: Recordable = {};
+
+  const handleCurrentChange = (row) => {
+    currentGroup = row || {};
+    query();
+  };
+
+  const handleCheckboxChange = ({ checked, row }) => {
+    if (checked) {
+      props.addSelectData([row]);
+    } else {
+      props.removeSelectData([row]);
+    }
+  };
+
+  const resetCheckbox = async () => {
+    // 数据重新加载后,设置选中的数据
+    await getTableInstance().setAllCheckboxRow(false);
+    await setCheckboxRow(props.selectData, true);
+  };
+
+  watch(
+    () => props.selectData,
+    () => {
+      resetCheckbox();
+    },
+  );
+
+  const [registerTable, { query, getTableInstance, setCheckboxRow }] = useSmartTable({
+    useSearchForm: true,
+    height: 'auto',
+    pagerConfig: true,
+    rowConfig: {
+      keyField: 'templateId',
+    },
+    proxyConfig: {
+      ajax: {
+        query: (params) => {
+          const parameter = merge(params.ajaxParameter, {
+            parameter: {
+              'groupId@=': currentGroup.groupId,
+            },
+          });
+          return defHttp.post({
+            service: ApiServiceEnum.SMART_CODE,
+            url: 'db/code/template/list',
+            data: parameter,
+          });
+        },
+      },
+    },
+    searchFormConfig: {
+      colon: true,
+      layout: 'inline',
+      baseColProps: {
+        span: 12,
+      },
+      actionColOptions: {
+        span: 12,
+      },
+      schemas: [
+        {
+          label: t('generator.views.template.table.name'),
+          field: 'name',
+          component: 'Input',
+        },
+      ],
+    },
+    columns: [
+      {
+        type: 'checkbox',
+        width: 60,
+        fixed: 'left',
+      },
+      {
+        field: 'name',
+        title: '{generator.views.template.table.name}',
+        width: 200,
+        fixed: 'left',
+        align: 'left',
+        headerAlign: 'center',
+      },
+      {
+        field: 'templateType',
+        title: '{generator.views.template.table.templateType}',
+        width: 140,
+        formatter: ({ row }: any) => {
+          const templateType = templateTypeConstants[row.templateType];
+          if (templateType) {
+            return t(templateType.label);
+          }
+          return '';
+        },
+      },
+      {
+        field: 'language',
+        title: '{generator.views.template.table.language}',
+        width: 200,
+      },
+      {
+        field: 'remark',
+        title: '{generator.views.template.table.remark}',
+        minWidth: 200,
+        align: 'left',
+        headerAlign: 'center',
+      },
+    ],
+  });
+</script>
+
+<style scoped></style>

+ 55 - 0
src/modules/codeGenerator/views/codeList/hooks/useLoadDbData.ts

@@ -0,0 +1,55 @@
+import { computed, ref } from 'vue';
+import { isEmpty } from '@/utils/is';
+import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+export const useLoadDbData = ({ validateAddEdit }) => {
+  // 数据库数据加载状态
+  const dbDataLoading = ref(false);
+  const dbData = ref<Recordable>({});
+
+  /**
+   * 加载数据库数据
+   */
+  const loadDbData = async () => {
+    const { connectionId, tableName } = await validateAddEdit();
+    if (!isEmpty(connectionId) && !isEmpty(tableName)) {
+      dbDataLoading.value = true;
+      try {
+        dbData.value = await defHttp.post({
+          service: ApiServiceEnum.SMART_CODE,
+          url: 'db/connection/queryDbTable',
+          data: {
+            dbConnectionId: connectionId,
+            tableName: tableName,
+          },
+        });
+      } finally {
+        dbDataLoading.value = false;
+      }
+    }
+  };
+  /**
+   * 表格table计算属性
+   */
+  const computedTableData = computed(() => {
+    if (!dbData.value.tableName) {
+      return [];
+    }
+    const primaryKeyList = dbData.value.primaryKeyList || [];
+    const baseColumnList = dbData.value.baseColumnList || [];
+    return [...primaryKeyList, ...baseColumnList];
+  });
+  /**
+   * 同步表
+   */
+  const handleSyncTableData = () => {
+    loadDbData();
+  };
+  return {
+    dbData,
+    dbDataLoading,
+    computedTableData,
+    handleSyncTableData,
+    loadDbData,
+  };
+};

+ 166 - 0
src/modules/codeGenerator/views/codeList/lang/en_US.ts

@@ -0,0 +1,166 @@
+export default {
+  generator: {
+    views: {
+      code: {
+        table: {
+          connectionName: 'Connection name',
+          configName: 'Config name',
+          tableName: 'Table name',
+          type: 'Type',
+          remarks: 'Table remark',
+        },
+        title: {
+          dbMessage: 'DB information',
+          tableSetting: 'Table setting',
+          formSetting: 'Form setting',
+          searchSetting: 'Search setting',
+          design: 'Design',
+          showCheckBox: 'Show check box',
+          isPage: 'Pagination',
+          invented: 'Virtual scrolling',
+          columnSort: 'Order adjustable',
+          leftButton: 'Left button',
+          rightButton: 'Right button',
+          rowButtonType: {
+            title: 'Row button type',
+            none: 'none',
+            single: 'unified',
+            more: 'more',
+            text: 'text',
+          },
+          rowButtonList: 'Row button',
+          formColNum: 'Form col num',
+          searchColNum: 'Search col num',
+          relateTable: 'Relate addendum',
+          tableType: {
+            single: 'single',
+            main: 'main',
+            addendum: 'addendum',
+          },
+          colNum: {
+            one: 'One column',
+            two: 'Two column',
+            three: 'Three column',
+            four: 'Four column',
+          },
+          controlList: {
+            input: 'INPUT',
+            textarea: 'TEXTAREA',
+            number: 'NUMBER',
+            password: 'PASSWORD',
+            select: 'SELECT',
+            transfer: 'TRANSFER',
+            selectTable: 'SELECT_TABLE',
+            radio: 'RADIO',
+            checkbox: 'CHECKBOX',
+            switch_type: 'SWITCH',
+            date: 'DATE',
+            time: 'TIME',
+            datetime: 'DATETIME',
+            file: 'FILE',
+          },
+          ruleList: {
+            notEmpty: 'NOT_EMPTY',
+            PHONE: 'PHONE',
+            EMAIL: 'EMAIL',
+            NUMBER: 'NUMBER',
+            REGEXP: 'REGEXP',
+          },
+          i18nPrefix: 'I18n prefix',
+        },
+        button: {
+          createCode: 'Generate code',
+          syncTableData: 'Sync table information',
+        },
+        validate: {
+          connectionName: 'Please select a database connection',
+          tableName: 'Please enter a table name',
+          configName: 'Please enter the configuration name',
+          syncTable: 'Please synchronize table information first',
+          tableSetting: 'Please configure the table',
+          formSetting: 'Please configure the form',
+          searchSetting: 'Please configure the search',
+          i18nPrefix: 'Please enter the i18n prefix',
+        },
+        message: {
+          saveConfirmContent: 'Non empty field: [{0}] has no form set',
+          noSelectSystem: 'Please select system',
+        },
+      },
+      codeCreateForm: {
+        title: {
+          description: 'Function description',
+          tableName: 'Table name',
+          className: 'Model class name',
+          packages: 'packages',
+          extPackages: 'ext packages',
+          controllerBasePath: 'controller path',
+          choseAddendum: 'Select addendum',
+          customConfig: 'Custom config',
+        },
+        message: {
+          choseTemplate: 'Please select a template',
+          customConfig: 'Please enter json',
+        },
+        validate: {
+          packages: 'Please enter package',
+          className: 'Please enter model class name',
+          controllerBasePath: 'Path cannot be empty',
+        },
+      },
+      tableField: {
+        title: {
+          columnName: 'Column name',
+          typeName: 'Column type',
+          columnSize: 'Column size',
+          decimalDigits: 'Decimal digits',
+          columnDef: 'Default value',
+          nullable: 'Nullable',
+          remarks: 'Table remark',
+          primaryKey: 'Primary key',
+          indexed: 'Indexed',
+        },
+      },
+      tableSetting: {
+        title: {
+          title: 'Title',
+          sortable: 'Sortable',
+          fixed: 'Fixed',
+          width: 'Width',
+          align: 'Align',
+          resizable: 'Resizable',
+          visible: 'Render',
+          hidden: 'Hidden',
+          editable: 'Editable',
+          format: 'Format',
+        },
+      },
+      formSetting: {
+        title: {
+          controlType: 'Control type',
+          readonly: 'Readonly',
+          used: 'Transfer background',
+          useTableSearch: 'Query DB',
+          keyColumnName: 'Key column',
+          valueColumnName: 'Value column',
+          tableWhere: 'Query criteria/Dict code',
+          rules: 'Rules',
+        },
+      },
+      searchSetting: {
+        title: {
+          searchSymbol: 'Search identity',
+        },
+      },
+      addendumTable: {
+        title: {
+          relatedColumn: 'Associated field',
+        },
+        validate: {
+          relatedColumn: 'Please select an associated field',
+          relatedColumnWithConfig: 'Please set the associated field, configuration: {0}',
+        },
+      },
+    },
+  },
+};

+ 167 - 0
src/modules/codeGenerator/views/codeList/lang/zh_CN.ts

@@ -0,0 +1,167 @@
+export default {
+  generator: {
+    views: {
+      code: {
+        table: {
+          connectionName: '连接名称',
+          configName: '配置名称',
+          tableName: '表名',
+          type: '类型',
+          remarks: '表备注',
+        },
+        title: {
+          dbMessage: '数据库信息',
+          tableSetting: '表格配置',
+          formSetting: '表单配置',
+          searchSetting: '查询配置',
+          design: '设计',
+          showCheckBox: '显示复选框',
+          isPage: '是否分页',
+          invented: '虚拟滚动',
+          columnSort: '列顺序可调',
+          leftButton: '左侧按钮',
+          rightButton: '右侧按钮',
+          rowButtonType: {
+            title: '行按钮类型',
+            none: '无',
+            single: '统一',
+            more: '多个',
+            text: '文本',
+          },
+          rowButtonList: '行操作按钮',
+          formColNum: '表单列数',
+          searchColNum: '搜索列数',
+          relateTable: '关联附表',
+          tableType: {
+            single: '单表',
+            main: '主表',
+            addendum: '附表',
+          },
+          colNum: {
+            one: '一列',
+            two: '两列',
+            three: '三列',
+            four: '四列',
+          },
+          controlList: {
+            input: '文本框',
+            textarea: '文本域',
+            number: '数字',
+            password: '密码',
+            select: '下拉框',
+            transfer: '穿梭框',
+            selectTable: '下拉表格',
+            radio: '单选框',
+            checkbox: '多选框',
+            switch_type: '开关',
+            date: '日期',
+            time: '时间',
+            datetime: '日期时间',
+            file: '文件',
+          },
+          ruleList: {
+            notEmpty: '非空',
+            PHONE: '手机号码',
+            EMAIL: '邮箱',
+            NUMBER: '数字',
+            REGEXP: '正则',
+          },
+          i18nPrefix: '国际化前缀',
+        },
+        button: {
+          createCode: '生成代码',
+          syncTableData: '同步表信息',
+        },
+        validate: {
+          connectionName: '请选择数据库连接',
+          tableName: '请输入表名',
+          configName: '请输入配置名',
+          syncTable: '请先同步表信息',
+          tableSetting: '请进行表格配置',
+          formSetting: '请进行表单配置',
+          searchSetting: '请进行搜索配置',
+          i18nPrefix: '请输入国际化前缀',
+        },
+        message: {
+          saveConfirmContent: '非空字段:【{0}】未设置form',
+          noSelectSystem: '请选择系统',
+        },
+      },
+      codeCreateForm: {
+        title: {
+          description: '功能描述',
+          tableName: '表名',
+          className: '实体类名',
+          packages: '包名',
+          extPackages: 'ext包名',
+          controllerBasePath: 'controller路径',
+          choseAddendum: '选择附表',
+          customConfig: '自定义配置',
+          templateList: '模板',
+        },
+        message: {
+          choseTemplate: '请选择模板',
+          customConfig: '请输入json',
+        },
+        validate: {
+          packages: '请输入包名',
+          className: '请输入实体类名',
+          controllerBasePath: '路径不能为空',
+        },
+      },
+      tableField: {
+        title: {
+          columnName: '字段名称',
+          typeName: '字段类型',
+          columnSize: '字段长度',
+          decimalDigits: '小数位数',
+          columnDef: '默认值',
+          nullable: '允许空值',
+          remarks: '表备注',
+          primaryKey: '主键',
+          indexed: '索引',
+        },
+      },
+      tableSetting: {
+        title: {
+          title: '标题',
+          sortable: '是否排序',
+          fixed: '列冻结',
+          width: '宽度',
+          align: '对齐方式',
+          resizable: '支持拖动',
+          visible: '是否渲染',
+          hidden: '是否隐藏',
+          editable: '是否可编辑',
+          format: '格式化',
+        },
+      },
+      formSetting: {
+        title: {
+          controlType: '控件类型',
+          readonly: '是否只读',
+          used: '是否传送后台',
+          useTableSearch: '查询数据库',
+          keyColumnName: 'key字段',
+          valueColumnName: 'value字段',
+          tableWhere: '查询条件/字典code',
+          rules: '验证规则',
+        },
+      },
+      searchSetting: {
+        title: {
+          searchSymbol: '搜索标识',
+        },
+      },
+      addendumTable: {
+        title: {
+          relatedColumn: '关联字段',
+        },
+        validate: {
+          relatedColumn: '请选择关联字段',
+          relatedColumnWithConfig: '请设置关联字段,配置:{0}',
+        },
+      },
+    },
+  },
+};

+ 19 - 0
src/modules/codeGenerator/views/database/DatabaseListHooks.ts

@@ -0,0 +1,19 @@
+import { testConnectedApi } from './DatabaseListView.api';
+import { message, Modal } from 'ant-design-vue';
+
+export const handleTestConnected = async (row, t: Function, setLoading) => {
+  try {
+    setLoading(true);
+    const result = await testConnectedApi(row.id);
+    if (result.result === true) {
+      message.success(t('generator.views.database.message.connectSuccess'));
+    } else {
+      Modal.error({
+        title: t('generator.views.database.message.connectFail'),
+        content: result.message,
+      });
+    }
+  } finally {
+    setLoading(false);
+  }
+};

+ 70 - 0
src/modules/codeGenerator/views/database/DatabaseListView.api.ts

@@ -0,0 +1,70 @@
+import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+enum api {
+  saveUpdate = 'db/connection/saveUpdate',
+  getById = 'db/connection/getById',
+  listBySystem = 'db/connection/listBySystem',
+  batchDeleteById = 'db/connection/batchDeleteById',
+  testConnected = 'db/connection/testConnection',
+  listTemplate = 'db/code/template/list',
+  createDict = '/public/db/createDic',
+}
+
+export const saveUpdateApi = (data: any) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_CODE,
+    url: api.saveUpdate,
+    data,
+  });
+};
+
+export const getByIdApi = (id: number) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_CODE,
+    url: api.getById,
+    data: id,
+  });
+};
+
+export const listApi = (data?: any) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_CODE,
+    url: api.listBySystem,
+    data,
+  });
+};
+
+export const deleteApi = async (rows: any[]) => {
+  if (rows.length === 0) {
+    return;
+  }
+  await defHttp.post({
+    service: ApiServiceEnum.SMART_CODE,
+    url: api.batchDeleteById,
+    data: rows.map((item: any) => item.id),
+  });
+};
+
+export const testConnectedApi = (id: number) =>
+  defHttp.post({
+    service: ApiServiceEnum.SMART_CODE,
+    url: api.testConnected,
+    data: id,
+  });
+
+export const listTemplate = (templateType?: string) =>
+  defHttp.post({
+    service: ApiServiceEnum.SMART_CODE,
+    url: api.listTemplate,
+    data: {
+      parameter: {
+        'templateType@=': templateType,
+      },
+    },
+  });
+
+export const getCreateDicUrl = ({ row, templateId, tempToken }) => {
+  return `${defHttp.getApiUrlByService(
+    ApiServiceEnum.SMART_CODE,
+  )}/public/db/createDic?connectionId=${row.id}&templateId=${templateId}&access-token=${tempToken}`;
+};

+ 203 - 0
src/modules/codeGenerator/views/database/DatabaseListView.data.ts

@@ -0,0 +1,203 @@
+import type { FormSchema } from '@/components/Form';
+import type { SmartColumn, SmartSearchFormSchema } from '@/components/SmartTable';
+
+const dbTypeList = ['MYSQL', 'SQL_SERVER', 'ORACLE'];
+
+export const tableColumns: SmartColumn[] = [
+  {
+    type: 'checkbox',
+    width: 60,
+    align: 'center',
+    fixed: 'left',
+  },
+  {
+    title: '{generator.views.database.table.connectionName}',
+    field: 'connectionName',
+    width: 160,
+    fixed: 'left',
+  },
+  {
+    title: '{generator.views.database.table.databaseName}',
+    field: 'databaseName',
+    width: 160,
+    fixed: 'left',
+  },
+  {
+    title: '{generator.views.database.table.type}',
+    field: 'type',
+    width: 120,
+  },
+  {
+    title: '{generator.views.database.table.url}',
+    field: 'url',
+    minWidth: 200,
+    showOverflow: 'tooltip',
+  },
+  {
+    title: '{generator.views.database.table.username}',
+    field: 'username',
+    width: 120,
+  },
+  {
+    title: '{generator.views.database.table.tableSchema}',
+    field: 'tableSchema',
+    width: 120,
+  },
+  {
+    title: '{common.table.createTime}',
+    field: 'createTime',
+    width: 165,
+    sortable: true,
+  },
+  {
+    title: '{common.table.createUser}',
+    field: 'createBy',
+    width: 120,
+  },
+  {
+    title: '{common.table.updateTime}',
+    field: 'updateTime',
+    width: 165,
+    sortable: true,
+  },
+  {
+    title: '{common.table.updateUser}',
+    field: 'updateBy',
+    width: 120,
+  },
+  {
+    title: '{common.table.operation}',
+    field: 'operation',
+    width: 120,
+    fixed: 'right',
+    slots: {
+      default: 'table-operation',
+    },
+  },
+];
+
+export const addEditForm: (t: Function) => Array<FormSchema> = (t: Function) => {
+  return [
+    {
+      field: 'id',
+      component: 'Input',
+      show: false,
+    },
+    {
+      field: 'systemId',
+      component: 'Input',
+      show: false,
+    },
+    {
+      label: t('generator.views.database.table.connectionName'),
+      field: 'connectionName',
+      component: 'Input',
+      componentProps: {
+        placeholder: t('generator.views.database.validate.connectionName'),
+      },
+      required: true,
+    },
+    {
+      label: t('generator.views.database.table.databaseName'),
+      field: 'databaseName',
+      component: 'Input',
+      componentProps: {
+        placeholder: t('generator.views.database.validate.databaseName'),
+      },
+      required: true,
+    },
+    {
+      label: t('generator.views.database.table.type'),
+      field: 'type',
+      component: 'Select',
+      componentProps: {
+        placeholder: t('generator.views.database.validate.type'),
+        options: dbTypeList.map((item) => {
+          return {
+            label: item,
+            value: item,
+          };
+        }),
+      },
+      rules: [
+        {
+          message: t('generator.views.database.validate.type'),
+          required: true,
+          trigger: 'change',
+        },
+      ],
+    },
+    {
+      label: t('generator.views.database.table.url'),
+      field: 'url',
+      component: 'Input',
+      componentProps: {
+        placeholder: t('generator.views.database.validate.url'),
+      },
+      required: true,
+    },
+    {
+      label: t('generator.views.database.table.username'),
+      field: 'username',
+      component: 'Input',
+      componentProps: {
+        placeholder: t('generator.views.database.validate.username'),
+      },
+      required: true,
+    },
+    {
+      label: t('generator.views.database.table.password'),
+      field: 'password',
+      component: 'InputPassword',
+      componentProps: {
+        placeholder: t('generator.views.database.validate.password'),
+      },
+      required: true,
+    },
+    {
+      label: t('generator.views.database.table.tableSchema'),
+      field: 'tableSchema',
+      component: 'Input',
+      componentProps: {},
+    },
+  ] as FormSchema[];
+};
+
+/**
+ * 搜索表单配置
+ * @param t
+ */
+export const searchForm: (t: Function) => SmartSearchFormSchema[] = (t: Function) => {
+  return [
+    {
+      field: 'connectionName',
+      component: 'Input',
+      componentProps: {
+        placeholder: t('generator.views.database.table.connectionName'),
+      },
+      colProps: { span: 6 },
+      searchSymbol: 'likeRight',
+      label: '',
+    },
+    {
+      field: 'databaseName',
+      component: 'Input',
+      componentProps: {
+        placeholder: t('generator.views.database.table.databaseName'),
+      },
+      colProps: { span: 6 },
+      searchSymbol: '=',
+      label: '',
+    },
+    {
+      field: 'project',
+      component: 'Input',
+      componentProps: {
+        placeholder: t('generator.views.database.table.project'),
+      },
+      colProps: { span: 6 },
+      searchSymbol: 'likeLeft',
+      label: '',
+    },
+  ];
+};

+ 172 - 0
src/modules/codeGenerator/views/database/DatabaseListView.vue

@@ -0,0 +1,172 @@
+<!--
+数据列表页面
+@author zhongming4762
+-->
+<template>
+  <div class="full-height page-container">
+    <LayoutSeparate first-size="240px" :show-line="false" class="full-height layout-container">
+      <template #first>
+        <div class="full-height system-container">
+          <SystemSimpleList
+            @current-change="handleSelectSystemChange"
+            :row-config="{ isHover: true, isCurrent: true }"
+            height="auto"
+          />
+        </div>
+      </template>
+      <template #second>
+        <SmartTable :size="getTableSize" @register="registerTable">
+          <template #table-operation="{ row }">
+            <SmartVxeTableAction
+              :actions="getTableAction(row)"
+              :drop-down-actions="getDropDownAction(row)"
+            />
+          </template>
+        </SmartTable>
+      </template>
+    </LayoutSeparate>
+    <TemplateSelectedModal template-type="template_db_dict" @register="registerModal" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { useI18n } from '@/hooks/web/useI18n';
+
+  import {
+    ActionItem,
+    SmartVxeTableAction,
+    SmartTable,
+    useSmartTable,
+  } from '@/components/SmartTable';
+
+  import TemplateSelectedModal from './components/TemplateSelectedModal.vue';
+  import { useModal } from '@/components/Modal';
+  import { LayoutSeparate } from '@/components/LayoutSeparate';
+  import SystemSimpleList from '@/modules/system/components/system/SystemSimpleList.vue';
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+
+  import { addEditForm, searchForm, tableColumns } from './DatabaseListView.data';
+  import { listApi, deleteApi, getByIdApi, saveUpdateApi } from './DatabaseListView.api';
+  import { handleTestConnected } from './DatabaseListHooks';
+
+  const { t } = useI18n();
+  const { getTableSize } = useSizeSetting();
+
+  const [registerModal, { openModal }] = useModal();
+  let currentSystem: Recordable = {};
+
+  const handleSelectSystemChange = (row) => {
+    currentSystem = row;
+    query();
+  };
+
+  const getTableAction = (row): ActionItem[] => {
+    return [
+      {
+        label: t('common.button.edit'),
+        onClick: () => editByRowModal(row),
+      },
+    ];
+  };
+
+  const getDropDownAction = (row): ActionItem[] => {
+    return [
+      {
+        label: t('generator.views.database.button.testConnected'),
+        onClick: () => handleTestConnected(row, t, setLoading),
+      },
+      {
+        label: t('generator.views.database.button.createDic'),
+        onClick: () => openModal(true, row),
+        auth: 'db:connection:createDic',
+      },
+    ];
+  };
+
+  const [registerTable, { editByRowModal, setLoading, query, showAddModal }] = useSmartTable({
+    searchFormConfig: {
+      searchWithSymbol: true,
+      schemas: searchForm(t),
+      layout: 'inline',
+      actionColOptions: {
+        span: undefined,
+      },
+    },
+    addEditConfig: {
+      modalConfig: {
+        width: 600,
+      },
+      formConfig: {
+        schemas: addEditForm(t),
+        baseColProps: {
+          span: 24,
+        },
+      },
+    },
+    columnConfig: {
+      resizable: true,
+    },
+    border: true,
+    height: 'auto',
+    columns: tableColumns,
+    useSearchForm: true,
+    pagerConfig: true,
+    sortConfig: {
+      remote: true,
+    },
+    proxyConfig: {
+      autoLoad: false,
+      ajax: {
+        query: (params) => {
+          const queryParameter = {
+            ...params.ajaxParameter,
+            systemId: currentSystem.id,
+          };
+          return listApi(queryParameter);
+        },
+        delete: async ({ body }) => {
+          await deleteApi(body.removeRecords);
+        },
+        getById: async (row) => {
+          return getByIdApi(row.id);
+        },
+        save: async ({ body }) => {
+          const { insertRecords, updateRecords } = body;
+          const data = [...insertRecords, ...updateRecords][0];
+          return saveUpdateApi(data);
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+    },
+    toolbarConfig: {
+      refresh: true,
+      buttons: [
+        {
+          // name: t('common.button.add'),
+          code: 'ModalAdd',
+          props: {
+            onClick: () =>
+              showAddModal({
+                systemId: currentSystem.id,
+              }),
+          },
+        },
+        {
+          code: 'ModalEdit',
+        },
+        {
+          code: 'delete',
+        },
+      ],
+    },
+  });
+</script>
+
+<style scoped lang="less">
+  .system-container {
+    margin-right: 5px;
+    background: white;
+  }
+</style>

+ 77 - 0
src/modules/codeGenerator/views/database/components/DatabaseListViewAddEditModal.vue

@@ -0,0 +1,77 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    :title="isAddRef ? $t('common.button.add') : $t('common.button.edit')"
+    :width="700"
+    @ok="handleSave"
+    @register="registerModal"
+  >
+    <BasicForm @register="registerAddEditForm" :size="getFormSize" />
+  </BasicModal>
+</template>
+
+<script lang="ts" setup>
+  import { ref } from 'vue';
+  import { useI18n } from '@/hooks/web/useI18n';
+
+  import { BasicModal, useModalInner } from '@/components/Modal';
+  import BasicForm from '@/components/Form/src/BasicForm.vue';
+  import { useForm } from '@/components/Form';
+  import { addEditForm } from '@/modules/codeGenerator/views/database/DatabaseListView.data';
+
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+
+  import { getByIdApi, saveUpdateApi } from '../DatabaseListView.api';
+
+  const emit = defineEmits(['success', 'register']);
+
+  const { t } = useI18n();
+  const { getFormSize } = useSizeSetting();
+  const isAddRef = ref(true);
+
+  const [registerAddEditForm, { setFieldsValue, resetFields, validate }] = useForm({
+    schemas: addEditForm(t),
+    baseColProps: {
+      span: 24,
+    },
+    labelCol: {
+      span: 5,
+    },
+    wrapperCol: {
+      span: 18,
+    },
+    // @ts-ignore
+    showActionButtonGroup: false,
+  });
+
+  const [registerModal, { changeLoading, closeModal }] = useModalInner(async ({ isAdd, id }) => {
+    await resetFields();
+    isAddRef.value = isAdd;
+    if (!isAdd) {
+      changeLoading(true);
+      try {
+        const result = await getByIdApi(id);
+        await setFieldsValue({
+          ...result,
+        });
+      } finally {
+        changeLoading(false);
+      }
+    }
+  });
+
+  /**
+   * 执行保存操作
+   */
+  const handleSave = async () => {
+    const model = await validate();
+    changeLoading(true);
+    try {
+      await saveUpdateApi(model);
+      closeModal();
+      emit('success');
+    } finally {
+      changeLoading(false);
+    }
+  };
+</script>

+ 84 - 0
src/modules/codeGenerator/views/database/components/TemplateSelected.vue

@@ -0,0 +1,84 @@
+<template>
+  <a-transfer
+    class="db-template-selected"
+    :data-source="transDataSource"
+    :target-keys="targetKeysModel"
+    show-search
+    :render="(item: any) => item.title"
+    @change="handleTransChange"
+  />
+</template>
+
+<script lang="ts">
+  import { defineComponent, ref, toRefs, onMounted } from 'vue';
+  import type { PropType } from 'vue';
+
+  import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+  /**
+   * 模板选择组件
+   */
+  export default defineComponent({
+    name: 'TemplateSelected',
+    props: {
+      templateType: {
+        type: String as PropType<string>,
+      },
+    },
+    emits: ['templateChange'],
+    setup(props, content) {
+      const { templateType } = toRefs(props);
+      // 所有模板数据
+      const transDataSource = ref([]);
+      const dataLoading = ref(false);
+      const targetKeysModel = ref<Array<string>>([]);
+      /**
+       * 加载模板数据
+       */
+      const loadData = async () => {
+        dataLoading.value = true;
+        targetKeysModel.value = [];
+        try {
+          const result = await defHttp.post({
+            service: ApiServiceEnum.SMART_CODE,
+            url: 'db/code/template/list',
+            data: {
+              parameter: {
+                'templateType@=': templateType.value,
+              },
+            },
+          });
+          transDataSource.value = result.map((item: any) => {
+            return {
+              key: item.templateId + '',
+              title: item.name,
+            };
+          });
+        } finally {
+          dataLoading.value = false;
+        }
+      };
+      onMounted(loadData);
+      const handleTransChange = (targetKeys: Array<string>) => {
+        content.emit('templateChange', targetKeys);
+        targetKeysModel.value = targetKeys;
+      };
+      return {
+        transDataSource,
+        targetKeysModel,
+        handleTransChange,
+        loadData,
+      };
+    },
+  });
+</script>
+
+<style lang="less" scoped>
+  .db-template-selected {
+    ::v-deep(.ant-transfer-list) {
+      flex: none;
+      width: 46%;
+      height: 450px;
+    }
+  }
+</style>

+ 97 - 0
src/modules/codeGenerator/views/database/components/TemplateSelectedModal.vue

@@ -0,0 +1,97 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="registerModal"
+    @ok="handleCreate"
+    :width="600"
+    :title="$t('generator.views.database.common.chooseTemplate')"
+  >
+    <a-transfer
+      class="db-template-selected"
+      :data-source="transDataSource"
+      :target-keys="targetKeysModel"
+      show-search
+      :render="(item: any) => item.title"
+      @change="handleTransChange"
+    />
+  </BasicModal>
+</template>
+
+<script lang="ts" setup>
+  import { PropType, ref, unref } from 'vue';
+
+  import BasicModal from '@/components/Modal/src/BasicModal.vue';
+  import { listTemplate, getCreateDicUrl } from '../DatabaseListView.api';
+  import { useModalInner } from '@/components/Modal';
+  import { message } from 'ant-design-vue';
+  import { applyTempToken } from '@/utils/auth';
+  import { useMessage } from '@/hooks/web/useMessage';
+  import { Result } from '/#/axios';
+  import { useI18n } from '@/hooks/web/useI18n';
+
+  const props = defineProps({
+    templateType: String as PropType<string>,
+  });
+
+  const { t } = useI18n();
+
+  const currentRow = ref<any>(null);
+
+  const [registerModal, { closeModal }] = useModalInner((data) => {
+    targetKeysModel.value = [];
+    currentRow.value = data;
+    loadData();
+  });
+
+  const transDataSource = ref([]);
+  const targetKeysModel = ref<Array<string>>([]);
+  const dataLoading = ref(false);
+
+  const loadData = async () => {
+    dataLoading.value = true;
+    targetKeysModel.value = [];
+    try {
+      const result = await listTemplate(props.templateType);
+      transDataSource.value = result.map((item: any) => {
+        return {
+          key: item.templateId + '',
+          title: item.name,
+        };
+      });
+    } finally {
+      dataLoading.value = false;
+    }
+  };
+  const handleCreate = async () => {
+    const selectTemplateIdList = unref(targetKeysModel);
+    if (selectTemplateIdList.length === 0) {
+      message.error(t('generator.views.database.validate.template'));
+      return false;
+    }
+    try {
+      const tempToken = await applyTempToken('db:connection:createDic', false);
+      selectTemplateIdList.forEach((templateId) => {
+        const url = getCreateDicUrl({ row: unref(currentRow), templateId, tempToken });
+        window.open(url);
+      });
+      closeModal();
+    } catch (e) {
+      const { errorMessage } = useMessage();
+      errorMessage(e as Result);
+    }
+  };
+
+  const handleTransChange = (targetKeys: Array<string>) => {
+    targetKeysModel.value = targetKeys;
+  };
+</script>
+
+<style scoped lang="less">
+  .db-template-selected {
+    :deep(.ant-transfer-list) {
+      flex: none;
+      width: 46%;
+      height: 450px;
+    }
+  }
+</style>

+ 40 - 0
src/modules/codeGenerator/views/database/lang/en_US.ts

@@ -0,0 +1,40 @@
+export default {
+  generator: {
+    views: {
+      database: {
+        common: {
+          chooseTemplate: 'Select template',
+        },
+        table: {
+          connectionName: 'Connection name',
+          databaseName: 'DB name',
+          type: 'Type',
+          project: 'Project',
+          url: 'URL',
+          username: 'Username',
+          tableSchema: 'Table schema',
+          password: 'Password',
+        },
+        button: {
+          testConnected: 'Test connection',
+          createDic: 'Generate database dic',
+        },
+        validate: {
+          type: 'Please select database type',
+          connectionName: 'Please enter connection name',
+          databaseName: 'Please enter database name',
+          project: 'Please enter database',
+          url: 'Please enter URL',
+          username: 'Please enter username',
+          password: 'Please enter password',
+          template: 'Please select template',
+        },
+        message: {
+          deleteOwn: 'You can only delete database connections that you have created',
+          connectSuccess: 'Database connection succeeded',
+          connectFail: 'Database connection failed',
+        },
+      },
+    },
+  },
+};

+ 40 - 0
src/modules/codeGenerator/views/database/lang/zh_CN.ts

@@ -0,0 +1,40 @@
+export default {
+  generator: {
+    views: {
+      database: {
+        common: {
+          chooseTemplate: '选择模板',
+        },
+        table: {
+          connectionName: '连接名称',
+          databaseName: '数据库名称',
+          type: '类型',
+          project: '项目',
+          url: 'URL',
+          username: '用户名',
+          tableSchema: 'TableSchema',
+          password: '密码',
+        },
+        button: {
+          testConnected: '测试连接',
+          createDic: '生成数据库字典',
+        },
+        validate: {
+          type: '请选择数据库类型',
+          connectionName: '请输入连接名称',
+          databaseName: '请输入数据库名称',
+          project: '请输入项目',
+          url: '请输入URL',
+          username: '请输入用户名',
+          password: '请输入密码',
+          template: '请选择模板',
+        },
+        message: {
+          deleteOwn: '只能删除自己创建的数据库连接',
+          connectSuccess: '数据库连接成功',
+          connectFail: '数据库连接失败',
+        },
+      },
+    },
+  },
+};

+ 85 - 0
src/modules/codeGenerator/views/document/TemplateDataDocumentView.vue

@@ -0,0 +1,85 @@
+<template>
+  <div style="padding: 10px" class="full-height">
+    <vxe-grid
+      v-bind="tableProps"
+      :tree-config="treeConfig"
+      :columns="columns"
+      highlight-hover-row
+      height="auto"
+      stripe
+      border
+    />
+  </div>
+</template>
+
+<script lang="ts">
+  import { defineComponent, onMounted } from 'vue';
+
+  import { useVxeTable } from '@/hooks/web/useCrud';
+  import { tableBooleanColumn } from '@/components/SmartTable';
+  import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+  const doLoadData = () => {
+    return defHttp.post({
+      service: ApiServiceEnum.SMART_CODE,
+      url: 'db/code/main/getTemplateDataDocument',
+    });
+  };
+
+  export default defineComponent({
+    name: 'TemplateDataDocumentView',
+    setup() {
+      const { tableProps, loadData } = useVxeTable(doLoadData, {
+        paging: false,
+      });
+
+      onMounted(loadData);
+
+      return {
+        tableProps,
+      };
+    },
+    data() {
+      return {
+        treeConfig: {
+          children: 'fieldList',
+        },
+        columns: [
+          {
+            title: '属性',
+            field: 'name',
+            width: 240,
+            fixed: 'left',
+            treeNode: true,
+          },
+          {
+            title: '说明',
+            field: 'remark',
+            minWidth: 240,
+          },
+          {
+            title: '参数|返回值',
+            field: 'type',
+            width: 120,
+          },
+          {
+            title: '可选值',
+            field: 'optional',
+            width: 180,
+          },
+          {
+            title: '默认值',
+            field: 'defaultValue',
+            width: 200,
+          },
+          {
+            ...tableBooleanColumn(this.$t, '是否可null', 'nullable').createColumn(),
+            width: 120,
+          },
+        ],
+      };
+    },
+  });
+</script>
+
+<style scoped></style>

+ 39 - 0
src/modules/codeGenerator/views/template/CodeTemplateList.api.ts

@@ -0,0 +1,39 @@
+import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+enum Api {
+  list = 'db/code/template/list',
+  saveUpdate = 'db/code/template/saveUpdate',
+  delete = 'db/code/template/batchDeleteById',
+  getById = 'db/code/template/getById',
+}
+
+export const listApi = (params) =>
+  defHttp.post({
+    service: ApiServiceEnum.SMART_CODE,
+    url: Api.list,
+    data: params,
+  });
+
+export const saveUpdateApi = (model) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_CODE,
+    url: Api.saveUpdate,
+    data: model,
+  });
+};
+
+export const deleteApi = (ids: number[]) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_CODE,
+    url: Api.delete,
+    data: ids,
+  });
+};
+
+export const getByIdApi = (id: number) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_CODE,
+    url: Api.getById,
+    data: id,
+  });
+};

+ 160 - 0
src/modules/codeGenerator/views/template/CodeTemplateList.config.ts

@@ -0,0 +1,160 @@
+import type { SmartColumn, SmartSearchFormSchema } from '@/components/SmartTable';
+import type { FormSchema } from '@/components/Form';
+
+import { TemplateType as templateTypeConstants } from '../../constants/DatabaseConstants';
+import { extensionLanguageMap } from '../../constants/Constants';
+
+export const getTableColumns = (t: Function): SmartColumn[] => {
+  return [
+    {
+      type: 'checkbox',
+      width: 60,
+      fixed: 'left',
+    },
+    {
+      field: 'name',
+      title: '{generator.views.template.table.name}',
+      width: 200,
+      fixed: 'left',
+      align: 'left',
+      headerAlign: 'center',
+    },
+    {
+      field: 'templateType',
+      title: '{generator.views.template.table.templateType}',
+      width: 140,
+      formatter: ({ row }: any) => {
+        const templateType = templateTypeConstants[row.templateType];
+        if (templateType) {
+          return t(templateType.label);
+        }
+        return '';
+      },
+    },
+    {
+      field: 'language',
+      title: '{generator.views.template.table.language}',
+      width: 200,
+    },
+    {
+      field: 'remark',
+      title: '{generator.views.template.table.remark}',
+      minWidth: 200,
+      align: 'left',
+      headerAlign: 'center',
+    },
+    {
+      title: '{common.table.createTime}',
+      field: 'createTime',
+      width: 165,
+      sortable: true,
+    },
+    {
+      title: '{common.table.createUser}',
+      field: 'createBy',
+      width: 120,
+    },
+    {
+      title: '{common.table.updateTime}',
+      field: 'updateTime',
+      width: 165,
+      sortable: true,
+    },
+    {
+      title: '{common.table.updateUser}',
+      field: 'updateBy',
+      width: 120,
+    },
+    {
+      title: '{common.table.operation}',
+      field: 'operation',
+      width: 120,
+      fixed: 'right',
+      slots: {
+        default: 'table-operation',
+      },
+    },
+  ];
+};
+
+export const getSearchSchemas = (t: Function): SmartSearchFormSchema[] => {
+  return [
+    {
+      label: t('generator.views.template.table.name'),
+      field: 'name',
+      component: 'Input',
+      searchSymbol: 'like',
+    },
+  ];
+};
+
+export const getAddEditFormSchemas = (t: Function): FormSchema[] => {
+  return [
+    {
+      label: '',
+      field: 'templateId',
+      component: 'Input',
+      show: false,
+    },
+    {
+      label: '',
+      field: 'groupId',
+      component: 'Input',
+      show: false,
+    },
+    {
+      label: t('generator.views.template.table.templateType'),
+      field: 'templateType',
+      component: 'Select',
+      required: true,
+      componentProps: {
+        options: Object.keys(templateTypeConstants).map((item) => {
+          const value = templateTypeConstants[item];
+          return {
+            value: value.value,
+            label: t(value.label),
+          };
+        }),
+      },
+    },
+    {
+      label: t('generator.views.template.table.name'),
+      field: 'name',
+      component: 'Input',
+      required: true,
+    },
+    {
+      label: t('generator.views.template.table.remark'),
+      field: 'remark',
+      component: 'Input',
+    },
+    {
+      label: t('generator.views.template.table.filenameSuffix'),
+      field: 'filenameSuffix',
+      component: 'Input',
+    },
+    {
+      label: t('generator.views.template.table.language'),
+      field: 'language',
+      component: 'Select',
+      componentProps: {
+        options: Object.keys(extensionLanguageMap).map((item) => {
+          return {
+            label: extensionLanguageMap[item],
+            value: item,
+          };
+        }),
+      },
+    },
+    {
+      label: '',
+      field: 'template',
+      slot: 'addEditForm-language',
+      colProps: {
+        span: 24,
+      },
+      labelWidth: 0,
+      disabledLabelWidth: true,
+    },
+  ];
+};

+ 180 - 0
src/modules/codeGenerator/views/template/CodeTemplateList.vue

@@ -0,0 +1,180 @@
+<template>
+  <div class="full-height code-container" id="codeTemplateContainer" style="padding: 10px">
+    <LayoutSeparate :show-line="false" first-size="240px" class="full-height">
+      <template #first>
+        <div class="full-height" style=" margin-right: 5px;background: white">
+          <TemplateGroup @change="handleGroupChange" />
+        </div>
+      </template>
+      <template #second>
+        <SmartTable @register="registerTable" :addEditConfig="getAddEditConfig">
+          <template #table-operation="{ row }">
+            <SmartVxeTableAction :actions="getActions(row)" />
+          </template>
+          <template #addEditForm-language="{ model }">
+            <div class="code-edit-container">
+              <CodeEditor
+                :read-only="isReadonly"
+                v-model:value="model.template"
+                :mode="model.language"
+              />
+            </div>
+          </template>
+        </SmartTable>
+      </template>
+    </LayoutSeparate>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import type { ActionItem, SmartTableAddEditConfig } from '@/components/SmartTable';
+
+  import { computed, ref, unref } from 'vue';
+  import { useI18n } from '@/hooks/web/useI18n';
+  import { merge } from 'lodash-es';
+  import { message } from 'ant-design-vue';
+
+  import { SmartTable, SmartVxeTableAction, useSmartTable } from '@/components/SmartTable';
+  import { LayoutSeparate } from '@/components/LayoutSeparate';
+  import TemplateGroup from './components/TemplateGroup.vue';
+  import { CodeEditor } from '@/components/CodeEditor';
+
+  import {
+    getTableColumns,
+    getSearchSchemas,
+    getAddEditFormSchemas,
+  } from './CodeTemplateList.config';
+  import { listApi, getByIdApi, deleteApi, saveUpdateApi } from './CodeTemplateList.api';
+
+  const { t } = useI18n();
+
+  const currentGroupIdRef = ref<number | undefined>();
+
+  const handleGroupChange = (id: number | undefined) => {
+    currentGroupIdRef.value = id;
+    reload();
+  };
+
+  const getActions = (row): ActionItem[] => {
+    return [
+      {
+        label: t('common.button.edit'),
+        onClick: () => {
+          isReadonly.value = false;
+          editByRowModal(row);
+        },
+      },
+      {
+        label: t('common.button.look'),
+        onClick: () => {
+          isReadonly.value = true;
+          editByRowModal(row);
+        },
+      },
+    ];
+  };
+
+  const isReadonly = ref(false);
+
+  const getAddEditConfig = computed<SmartTableAddEditConfig>(() => {
+    return {
+      modalConfig: {
+        defaultFullscreen: true,
+        getContainer: () => {
+          return document.getElementById('codeTemplateContainer') as HTMLElement;
+        },
+      },
+      formConfig: {
+        schemas: getAddEditFormSchemas(t),
+        baseColProps: {
+          span: 8,
+        },
+        colon: true,
+        disabled: unref(isReadonly),
+        baseRowStyle: {
+          height: '100%',
+        },
+      },
+    };
+  });
+
+  const [registerTable, { editByRowModal, showAddModal, reload }] = useSmartTable({
+    height: 'auto',
+    highlightHoverRow: true,
+    stripe: true,
+    columns: getTableColumns(t),
+    useSearchForm: true,
+    searchFormConfig: {
+      schemas: getSearchSchemas(t),
+      searchWithSymbol: true,
+      colon: true,
+      layout: 'inline',
+      actionColOptions: {
+        span: undefined,
+      },
+    },
+    toolbarConfig: {
+      refresh: true,
+      resizable: true,
+      buttons: [
+        {
+          code: 'ModalAdd',
+          props: {
+            onClick: () => {
+              isReadonly.value = false;
+              const currentGroupId = unref(currentGroupIdRef);
+              if (!currentGroupId) {
+                message.warn(t('generator.views.template.notice.choseGroup'));
+                return false;
+              }
+              showAddModal({
+                groupId: currentGroupId,
+              });
+            },
+          },
+        },
+        {
+          code: 'delete',
+        },
+      ],
+    },
+    proxyConfig: {
+      ajax: {
+        query: (params) => {
+          const parameter = merge(params.ajaxParameter, {
+            parameter: {
+              'groupId@=': unref(currentGroupIdRef),
+            },
+          });
+          return listApi(parameter);
+        },
+        save: ({ body: { insertRecords, updateRecords } }) =>
+          saveUpdateApi([...insertRecords, ...updateRecords][0]),
+        delete: ({ body: { removeRecords } }) =>
+          deleteApi(removeRecords.map((item) => item.templateId)),
+        getById: (params) => getByIdApi(params.templateId),
+      },
+    },
+  });
+</script>
+
+<style scoped lang="less">
+  .code-container {
+    :deep(.ant-form) {
+      height: 100%;
+    }
+
+    :deep(.ant-col-24) {
+      height: calc(100% - 106px);
+
+      .ant-form-item {
+        height: 100%;
+        overflow: auto;
+      }
+    }
+  }
+
+  .code-edit-container {
+    height: 100%;
+  }
+</style>

+ 248 - 0
src/modules/codeGenerator/views/template/components/TemplateGroup.vue

@@ -0,0 +1,248 @@
+<template>
+  <div class="full-height">
+    <div class="table-container">
+      <vxe-grid
+        ref="gridRef"
+        highlight-current-row
+        stripe
+        :columns="columns"
+        height="auto"
+        size="small"
+        v-bind="tableProps"
+        @cell-click="handleChange"
+      >
+        <template #table-groupName="{ row }">
+          <div style="cursor: pointer" @contextmenu="handleContext">
+            {{ row.groupName }}
+          </div>
+          <!--            <ContextMenu event="contextmenu">-->
+          <!--              {{ row.groupName }}-->
+          <!--              <template #menu>-->
+          <!--                <a-menu @click="({ key, domEvent }) => handleMenuClick(key, row.groupId, domEvent)">-->
+          <!--                  <a-menu-item key="edit">-->
+          <!--                    <EditOutlined />-->
+          <!--                    &nbsp;&nbsp;{{ $t('common.button.edit') }}-->
+          <!--                  </a-menu-item>-->
+          <!--                  <a-menu-item key="delete">-->
+          <!--                    <DeleteOutlined />-->
+          <!--                    &nbsp;&nbsp;{{ $t('common.button.delete') }}-->
+          <!--                  </a-menu-item>-->
+          <!--                </a-menu>-->
+          <!--              </template>-->
+          <!--            </ContextMenu>-->
+          <!--          </div>-->
+        </template>
+      </vxe-grid>
+    </div>
+    <div class="button-container">
+      <a-button class="button" block type="primary" @click="() => handleAddEdit(true, null)">
+        {{ $t('common.button.add') }}
+      </a-button>
+    </div>
+
+    <a-modal v-bind="modalProps">
+      <a-spin :spinning="spinning">
+        <a-form
+          v-bind="formProps"
+          :rules="rules"
+          :label-col="{ span: 6 }"
+          :wrapper-col="{ span: 17 }"
+        >
+          <a-form-item :label="$t('generator.views.template.title.templateGroup')" name="groupName">
+            <a-input
+              v-model:value="formProps.model.groupName"
+              :placeholder="$t('generator.views.template.validate.templateGroup')"
+            />
+          </a-form-item>
+          <a-form-item :label="$t('generator.views.template.title.seq')" name="seq">
+            <a-input-number
+              v-model:value="formProps.model.seq"
+              style="width: 100%"
+              :placeholder="$t('generator.views.template.validate.seq')"
+            />
+          </a-form-item>
+        </a-form>
+      </a-spin>
+    </a-modal>
+  </div>
+</template>
+
+<script lang="ts">
+  import { defineComponent, onMounted, ref } from 'vue';
+  import { useI18n } from 'vue-i18n';
+
+  // import { ContextMenu } from '@/components/ContextMenu';
+  import { useVxeTable, useAddEdit, useVxeDelete } from '@/hooks/web/useCrud';
+  import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+  import { useContextMenu } from '@/hooks/web/useContextMenu';
+
+  const handleLoadData = async () => {
+    const result = [
+      {
+        groupName: 'ALL',
+      },
+    ];
+    return result.concat(
+      (await defHttp.post({
+        url: 'db/code/template/listGroup',
+        service: ApiServiceEnum.SMART_CODE,
+      })) || [],
+    );
+  };
+
+  /**
+   * 通过ID查询
+   */
+  const handleGet = (id: any) => {
+    return defHttp.post({
+      service: ApiServiceEnum.SMART_CODE,
+      url: 'db/code/template/getGroupById',
+      data: id,
+    });
+  };
+
+  const handleSaveUpdate = async (model: any) => {
+    return defHttp.post({
+      service: ApiServiceEnum.SMART_CODE,
+      url: 'db/code/template/saveUpdateGroup',
+      data: model,
+    });
+  };
+
+  const handleDelete = async (idList: Array<any>) => {
+    return defHttp.post({
+      service: ApiServiceEnum.SMART_CODE,
+      url: 'db/code/template/deleteGroupById',
+      data: idList,
+    });
+  };
+
+  export default defineComponent({
+    name: 'TemplateGroup',
+    emits: ['change'],
+    setup() {
+      const { t } = useI18n();
+      const gridRef = ref();
+      const { tableProps, loadData } = useVxeTable(handleLoadData, {
+        paging: false,
+      });
+      // 编辑
+      const { modalProps, handleAddEdit, spinning, formProps, formRef } = useAddEdit(
+        gridRef,
+        handleGet,
+        loadData,
+        handleSaveUpdate,
+        t,
+        {
+          defaultModel: {
+            seq: 1,
+          },
+        },
+      );
+
+      const [createContextMenu] = useContextMenu();
+
+      const handleContext = (e: MouseEvent) => {
+        return createContextMenu({
+          event: e,
+          items: [
+            {
+              label: t('common.button.edit'),
+              icon: 'ant-design:edit-outlined',
+            },
+            {
+              label: t('common.button.delete'),
+              icon: 'ant-design:delete-outlined',
+            },
+          ],
+        });
+      };
+
+      const { handleDeleteById } = useVxeDelete(gridRef, t, handleDelete, {
+        idField: 'groupId',
+        listHandler: loadData,
+      });
+
+      const handleMenuClick = (ident: string, groupId: number, event: Event) => {
+        event.preventDefault();
+        switch (ident) {
+          case 'edit': {
+            handleAddEdit(false, groupId);
+            break;
+          }
+          case 'delete': {
+            handleDeleteById(groupId);
+            break;
+          }
+        }
+      };
+
+      onMounted(loadData);
+      return {
+        gridRef,
+        tableProps,
+        loadData,
+        handleMenuClick,
+        modalProps,
+        handleAddEdit,
+        spinning,
+        formProps,
+        formRef,
+        handleContext,
+      };
+    },
+    data() {
+      return {
+        columns: [
+          {
+            title: '{generator.views.template.title.templateGroup}',
+            field: 'groupName',
+            slots: {
+              default: 'table-groupName',
+            },
+          },
+        ],
+        rules: {
+          groupName: [
+            {
+              required: true,
+              message: this.$t('generator.views.template.validate.templateGroup'),
+              trigger: 'blur',
+            },
+          ],
+          seq: [
+            {
+              required: true,
+              message: this.$t('generator.views.template.validate.seq'),
+              trigger: 'blur',
+              type: 'number',
+            },
+          ],
+        },
+      };
+    },
+    methods: {
+      handleChange({ row }: any) {
+        this.$emit('change', row.groupId);
+      },
+    },
+  });
+</script>
+
+<style lang="less" scoped>
+  @buttonContainerHeight: 50px;
+
+  .table-container {
+    height: calc(100% - @buttonContainerHeight);
+  }
+
+  .button-container {
+    height: @buttonContainerHeight;
+    line-height: @buttonContainerHeight;
+    text-align: center;
+
+    .button {
+      width: 90%;
+    }
+  }
+</style>

+ 171 - 0
src/modules/codeGenerator/views/template/components/TemplateSetUserGroup.vue

@@ -0,0 +1,171 @@
+<template>
+  <a-layout class="full-height">
+    <a-layout-header style="height: 56px; background: white; text-align: center">
+      <h3>{{ $t('generator.views.template.title.userGroup') }}</h3>
+    </a-layout-header>
+    <a-divider style="margin: 0" />
+    <a-layout-content style="background: white">
+      <div class="full-height">
+        <a-spin class="full-height" :spinning="dataLoading">
+          <a-table
+            class="full-height"
+            size="small"
+            row-key="groupId"
+            :row-selection="rowSelection"
+            :columns="columns"
+            :show-header="false"
+            :pagination="false"
+            :data-source="allUserGroup"
+          />
+        </a-spin>
+      </div>
+    </a-layout-content>
+    <a-divider style="margin: 0" />
+    <a-layout-footer style="height: 50px; padding: 10px 0; background: white; text-align: center">
+      <div style="padding: 0 5px">
+        <a-button
+          :disabled="!saveButtonVisible"
+          :loading="saveLoading"
+          block
+          type="primary"
+          @click="handleSave"
+        >
+          {{ $t('common.button.save') }}
+        </a-button>
+      </div>
+    </a-layout-footer>
+  </a-layout>
+</template>
+
+<script lang="ts">
+  import { defineComponent, toRefs, onMounted, ref, reactive, watch, computed } from 'vue';
+  import type { PropType } from 'vue';
+
+  import { isSuperAdmin, getCurrentUserId } from '@/utils/auth';
+
+  import { message } from 'ant-design-vue';
+  import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+  /**CodeListView
+   * 模板设置关联的用户组
+   */
+  export default defineComponent({
+    name: 'TemplateSetUserGroup',
+    props: {
+      template: {
+        type: Object as PropType<any>,
+        default: null,
+      },
+    },
+    setup(props) {
+      const { template } = toRefs(props);
+      const dataLoading = ref(false);
+      // 保存状态
+      const saveLoading = ref(false);
+      const allUserGroup = ref<Array<any>>([]);
+      const rowSelection = reactive({
+        columnWidth: 60,
+        selectedRowKeys: [] as Array<number>,
+        onChange: (selectedRowKeys: Array<number>) => {
+          rowSelection.selectedRowKeys = selectedRowKeys;
+        },
+      });
+      /**
+       * 保存安装显示状态
+       */
+      const saveButtonVisible = computed(() => {
+        if (isSuperAdmin()) {
+          return true;
+        }
+        const currentUserId = getCurrentUserId;
+        const templateValue: any = template.value;
+        return templateValue ? templateValue.createUserId === currentUserId : false;
+      });
+      /**
+       * 加载所有用户组数据
+       */
+      const loadAllUserGroup = async () => {
+        try {
+          dataLoading.value = true;
+          allUserGroup.value = await defHttp.post({
+            service: ApiServiceEnum.SMART_SYSTEM,
+            url: 'sys/userGroup/list',
+            data: {
+              sortName: 'seq',
+              parameter: {
+                'useYn@=': true,
+              },
+            },
+          });
+        } finally {
+          dataLoading.value = false;
+        }
+      };
+      /**
+       * 加载模板对应的用户组信息
+       */
+      const loadTemplateUserGroup = async () => {
+        const templateValue: any = template.value;
+        if (templateValue === null) {
+          rowSelection.selectedRowKeys = [];
+        }
+        try {
+          dataLoading.value = true;
+          const result: Array<any> = await defHttp.post({
+            service: ApiServiceEnum.SMART_CODE,
+            url: 'db/code/template/listUserGroupByTemplate',
+            data: templateValue.templateId,
+          });
+          rowSelection.selectedRowKeys = result.map((item) => item.groupId);
+        } finally {
+          dataLoading.value = false;
+        }
+      };
+      /**
+       * 保存操作
+       */
+      const handleSave = async () => {
+        const templateValue: any = template.value;
+        if (!templateValue) {
+          message.error('请先指定模板');
+          return false;
+        }
+        try {
+          saveLoading.value = true;
+          await defHttp.post({
+            service: ApiServiceEnum.SMART_CODE,
+            url: 'db/code/template/saveTemplateUserGroup',
+            data: {
+              templateId: templateValue.templateId,
+              groupIdList: rowSelection.selectedRowKeys,
+            },
+          });
+          message.success('保存成功');
+        } finally {
+          saveLoading.value = false;
+        }
+      };
+      watch(template, loadTemplateUserGroup);
+      onMounted(loadAllUserGroup);
+      return {
+        dataLoading,
+        allUserGroup,
+        rowSelection,
+        handleSave,
+        saveLoading,
+        saveButtonVisible,
+      };
+    },
+    data() {
+      return {
+        columns: [
+          {
+            dataIndex: 'groupName',
+          },
+        ],
+      };
+    },
+  });
+</script>
+
+<style scoped></style>

+ 37 - 0
src/modules/codeGenerator/views/template/lang/en_US.ts

@@ -0,0 +1,37 @@
+export default {
+  generator: {
+    views: {
+      template: {
+        label: {
+          templateType: {
+            templateCode: 'Code template',
+            templateDbDict: 'DB dict template',
+          },
+        },
+        title: {
+          userGroup: 'User group',
+          templateGroup: 'Template group',
+          seq: 'Seq',
+        },
+        table: {
+          name: 'Name',
+          templateType: 'Template Type',
+          language: 'Language',
+          remark: 'Remark',
+          filenameSuffix: 'Filename Suffix',
+        },
+        notice: {
+          onlyDeleteMy: 'Only self created templates can be deleted',
+          choseGroup: 'Please select a template group first',
+        },
+        validate: {
+          templateType: 'Please enter template type',
+          name: 'Please enter name',
+          remark: 'Please enter remark',
+          seq: 'Please enter seq',
+          templateGroup: 'Please enter template group',
+        },
+      },
+    },
+  },
+};

+ 37 - 0
src/modules/codeGenerator/views/template/lang/zh_CN.ts

@@ -0,0 +1,37 @@
+export default {
+  generator: {
+    views: {
+      template: {
+        label: {
+          templateType: {
+            templateCode: '代码模板',
+            templateDbDict: '数据库字典模板',
+          },
+        },
+        title: {
+          userGroup: '用户组',
+          templateGroup: '模板分组',
+          seq: '序号',
+        },
+        table: {
+          name: '名称',
+          templateType: '模板类型',
+          language: '语言',
+          remark: '备注',
+          filenameSuffix: '文件名后缀',
+        },
+        notice: {
+          onlyDeleteMy: '只能删除自己创建的模板',
+          choseGroup: '请先选择模板分组',
+        },
+        validate: {
+          templateType: '请输入模板类型',
+          name: '请输入模板名称',
+          remark: '请输入备注',
+          seq: '请输入序号',
+          templateGroup: '请输入模板分组',
+        },
+      },
+    },
+  },
+};

+ 42 - 0
src/modules/system/views/accessSecret/SysAuthAccessSecretListView.api.ts

@@ -0,0 +1,42 @@
+import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+enum Api {
+  list = '/sys/auth/accessSecret/list',
+  getById = '/sys/auth/accessSecret/getById',
+  saveUpdate = '/sys/auth/accessSecret/saveUpdate',
+  delete = '/sys/auth/accessSecret/batchDeleteById',
+}
+
+export const listApi = (params) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_SYSTEM,
+    url: Api.list,
+    data: {
+      ...params,
+    },
+  });
+};
+
+export const saveUpdateApi = (modelList: any[]) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_SYSTEM,
+    url: Api.saveUpdate,
+    data: modelList[0],
+  });
+};
+
+export const deleteApi = (removeRecords: Recordable[]) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_SYSTEM,
+    url: Api.delete,
+    data: removeRecords.map((item) => item.id),
+  });
+};
+
+export const getByIdApi = (id: number) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_SYSTEM,
+    url: Api.getById,
+    data: id,
+  });
+};

+ 186 - 0
src/modules/system/views/accessSecret/SysAuthAccessSecretListView.config.ts

@@ -0,0 +1,186 @@
+import type { SmartColumn, SmartSearchFormSchema } from '@/components/SmartTable';
+import type { FormSchema } from '@/components/Form';
+
+import { tableUseYnClass } from '@/components/SmartTable';
+
+/**
+ * 表格列表
+ */
+export const getTableColumns = (): SmartColumn[] => {
+  return [
+    {
+      type: 'checkbox',
+      width: 60,
+      align: 'center',
+      fixed: 'left',
+    },
+    {
+      field: 'seq',
+      sortable: true,
+      title: '{common.table.seq}',
+      width: 100,
+    },
+    {
+      field: 'accessKey',
+      title: '{system.views.auth.acccessSecret.title.accessKey}',
+      width: 120,
+    },
+    {
+      field: 'secretKey',
+      title: '{system.views.auth.acccessSecret.title.secretKey}',
+      width: 120,
+    },
+    {
+      field: 'expireDate',
+      title: '{system.views.auth.acccessSecret.title.expireDate}',
+      width: 165,
+    },
+    {
+      field: 'accessIp',
+      title: '{system.views.auth.acccessSecret.title.accessIp}',
+      width: 120,
+    },
+    {
+      field: 'remark',
+      title: '{common.table.remark}',
+      width: 200,
+    },
+    {
+      ...tableUseYnClass(),
+    },
+    {
+      field: 'createBy',
+      title: '{common.table.createUser}',
+      width: 120,
+    },
+    {
+      field: 'createTime',
+      title: '{common.table.createTime}',
+      width: 165,
+    },
+    {
+      field: 'updateBy',
+      title: '{common.table.updateUser}',
+      width: 120,
+    },
+    {
+      field: 'updateTime',
+      title: '{common.table.updateTime}',
+      width: 165,
+    },
+    {
+      title: '{common.table.operation}',
+      field: 'operation',
+      width: 120,
+      fixed: 'right',
+      slots: {
+        default: 'table-operation',
+      },
+    },
+  ];
+};
+
+/**
+ * 添加修改表单
+ */
+export const getFormSchemas = (t: Function): FormSchema[] => {
+  return [
+    {
+      field: 'id',
+      show: false,
+      label: '',
+      component: 'Input',
+      componentProps: {},
+    },
+    {
+      field: 'accessKey',
+      label: t('system.views.auth.acccessSecret.title.accessKey'),
+      component: 'Input',
+      componentProps: {},
+      dynamicDisabled: true,
+    },
+    {
+      field: 'secretKey',
+      label: t('system.views.auth.acccessSecret.title.secretKey'),
+      component: 'Input',
+      componentProps: {},
+      dynamicDisabled: true,
+    },
+    {
+      field: 'expireDate',
+      label: t('system.views.auth.acccessSecret.title.expireDate'),
+      component: 'DatePicker',
+      componentProps: {
+        showTime: true,
+        style: { width: '100%' },
+      },
+    },
+    {
+      field: 'accessIp',
+      label: t('system.views.auth.acccessSecret.title.accessIp'),
+      component: 'InputTextArea',
+      componentProps: {
+        placeholder: t('system.views.auth.acccessSecret.validate.accessIp'),
+      },
+    },
+    {
+      field: 'remark',
+      label: t('common.table.remark'),
+      component: 'InputTextArea',
+      componentProps: {},
+    },
+    {
+      field: 'useYn',
+      label: t('common.table.useYn'),
+      component: 'Switch',
+      componentProps: {},
+      defaultValue: true,
+    },
+    {
+      field: 'seq',
+      label: t('common.table.seq'),
+      component: 'InputNumber',
+      componentProps: {},
+      required: true,
+    },
+  ];
+};
+
+export const getSearchFormSchemas = (t: Function): SmartSearchFormSchema[] => {
+  return [
+    {
+      field: 'accessKey',
+      label: t('system.views.auth.acccessSecret.title.accessKey'),
+      component: 'Input',
+      searchSymbol: 'like',
+    },
+    {
+      field: 'secretKey',
+      label: t('system.views.auth.acccessSecret.title.secretKey'),
+      component: 'Input',
+      searchSymbol: 'like',
+    },
+    {
+      field: 'useYn',
+      label: t('common.table.useYn'),
+      component: 'Select',
+      searchSymbol: '=',
+      defaultValue: 1,
+      componentProps: {
+        style: {
+          width: '120px',
+        },
+        options: [
+          {
+            label: t('common.form.use'),
+            value: 1,
+          },
+          {
+            label: t('common.form.noUse'),
+            value: 0,
+          },
+        ],
+      },
+    },
+  ];
+};

+ 98 - 0
src/modules/system/views/accessSecret/SysAuthAccessSecretListView.vue

@@ -0,0 +1,98 @@
+<template>
+  <div class="full-height page-container">
+    <SmartTable @register="registerTable" :size="getTableSize">
+      <template #table-operation="{ row }">
+        <SmartVxeTableAction :actions="getActions(row)" />
+      </template>
+    </SmartTable>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { useI18n } from '@/hooks/web/useI18n';
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+
+  import {
+    ActionItem,
+    SmartTable,
+    SmartVxeTableAction,
+    useSmartTable,
+  } from '@/components/SmartTable';
+
+  import {
+    getTableColumns,
+    getFormSchemas,
+    getSearchFormSchemas,
+  } from './SysAuthAccessSecretListView.config';
+  import { listApi, deleteApi, getByIdApi, saveUpdateApi } from './SysAuthAccessSecretListView.api';
+
+  const { t } = useI18n();
+  const { getTableSize } = useSizeSetting();
+
+  const getActions = (row: Recordable): ActionItem[] => {
+    return [
+      {
+        label: t('common.button.edit'),
+        onClick: () => editByRowModal(row),
+      },
+    ];
+  };
+
+  const [registerTable, { editByRowModal }] = useSmartTable({
+    id: 'smart-system-tool-accessSecret',
+    customConfig: { storage: true },
+    columns: getTableColumns(),
+    height: 'auto',
+    border: true,
+    sortConfig: {
+      remote: true,
+    },
+    rowConfig: {
+      isHover: true,
+    },
+    showOverflow: 'tooltip',
+    pagerConfig: true,
+    useSearchForm: true,
+    searchFormConfig: {
+      schemas: getSearchFormSchemas(t),
+      searchWithSymbol: true,
+      colon: true,
+      layout: 'inline',
+      actionColOptions: {
+        span: undefined,
+      },
+      compact: true,
+    },
+    addEditConfig: {
+      formConfig: {
+        schemas: getFormSchemas(t),
+        colon: true,
+        baseColProps: { span: 24 },
+        labelCol: { span: 6 },
+        wrapperCol: { span: 17 },
+      },
+    },
+    proxyConfig: {
+      ajax: {
+        query: (params) => listApi(params.ajaxParameter),
+        save: ({ body: { insertRecords, updateRecords } }) =>
+          saveUpdateApi([...insertRecords, ...updateRecords]),
+        delete: ({ body: { removeRecords } }) => deleteApi(removeRecords),
+        getById: (params) => getByIdApi(params.id),
+      },
+    },
+    toolbarConfig: {
+      zoom: true,
+      refresh: true,
+      column: { columnOrder: true },
+      buttons: [
+        {
+          code: 'ModalAdd',
+        },
+        {
+          code: 'delete',
+        },
+      ],
+    },
+  });
+</script>

+ 28 - 0
src/modules/system/views/accessSecret/lang/zh_CN.ts

@@ -0,0 +1,28 @@
+/**
+ *  国际化信息
+ */
+export default {
+  trans: true,
+  key: 'system.views.auth.acccessSecret',
+  data: {
+    title: {
+      accessKey: 'Access key',
+      secretKey: 'Secret key',
+      expireDate: '过期时间',
+      accessIp: '授权IP或域名',
+      createBy: 'createBy',
+      updateBy: 'updateBy',
+    },
+    validate: {
+      accessKey: '请输入Access key',
+      secretKey: '请输入Secret key',
+      expireDate: '请输入过期时间',
+      accessIp: '请输入授权IP或域名,以逗号分隔',
+    },
+    rules: {},
+    search: {
+      accessKey: '请输入Access key',
+      secretKey: '请输入Secret key',
+    },
+  },
+};

+ 22 - 5
src/utils/http/axios/Axios.ts

@@ -1,5 +1,11 @@
 import type { AxiosInstance, AxiosResponse, AxiosError, InternalAxiosRequestConfig } from 'axios';
-import type { SmartAxiosRequestConfig, RequestOptions, Result, UploadFileParams } from '#/axios';
+import type {
+  SmartAxiosRequestConfig,
+  RequestOptions,
+  Result,
+  UploadFileParams,
+  UploadFileItemParams,
+} from '#/axios';
 import type { CreateAxiosOptions } from './axiosTransform';
 import axios from 'axios';
 import qs from 'qs';
@@ -9,6 +15,7 @@ import { cloneDeep } from 'lodash-es';
 import { ContentTypeEnum, RequestEnum } from '@/enums/httpEnum';
 import { useGlobSetting } from '@/hooks/setting';
 import { downloadByData } from '@/utils/file/download';
+import { isArray } from 'xe-utils';
 
 export * from './axiosTransform';
 
@@ -124,13 +131,23 @@ export class VAxios {
    */
   uploadFile<T = any>(config: SmartAxiosRequestConfig, params: UploadFileParams) {
     const formData = new window.FormData();
-    const customFilename = params.name || 'file';
+    // const customFilename = params.name || 'file';
 
-    if (params.filename) {
-      formData.append(customFilename, params.file, params.filename);
+    const file = params.file;
+    let uploadFileList: UploadFileItemParams[];
+    if (isArray(file)) {
+      uploadFileList = file;
     } else {
-      formData.append(customFilename, params.file);
+      uploadFileList = [file];
     }
+    uploadFileList.forEach((item) => {
+      const customFilename = item.name || 'file';
+      if (item.filename) {
+        formData.append(customFilename, item.file, item.filename);
+      } else {
+        formData.append(customFilename, item.file);
+      }
+    });
 
     if (params.data) {
       Object.keys(params.data).forEach((key) => {

+ 15 - 1
src/utils/http/axios/index.ts

@@ -104,8 +104,22 @@ const transform: AxiosTransform = {
       config.url = `${urlPrefix}${config.url}`;
     }
 
+    // 处理URL
+    const { isStandalone } = useGlobSetting();
+    if (!isStandalone) {
+      let spi = '';
+      if (!config.url?.startsWith('/')) {
+        spi = '/';
+      }
+      config.url = `${config.service}${spi}${config.url}`;
+    }
+
     if (apiUrl && isString(apiUrl)) {
-      config.url = `${apiUrl}${config.url}`;
+      let spi = '';
+      if (!config.url?.startsWith('/')) {
+        spi = '/';
+      }
+      config.url = `${apiUrl}${spi}${config.url}`;
     }
     const params = config.params || {};
     const data = config.data || false;