发布代码生成、更新20+表单组件,优化数据字典,gf版本更新到2.3.1

This commit is contained in:
孟帅
2023-01-18 16:23:39 +08:00
parent 50207ded90
commit 87c27a17a3
386 changed files with 27926 additions and 44297 deletions

View File

@@ -0,0 +1,509 @@
<template>
<div>
<n-card
:bordered="true"
title="基本设置"
class="proCard mt-2"
size="small"
:segmented="{ content: true }"
>
<n-form ref="formRef" :model="formValue">
<n-row :gutter="24">
<n-col :span="6" style="min-width: 200px">
<n-form-item label="生成类型" path="title">
<n-select
placeholder="请选择"
:options="selectList.genType"
v-model:value="formValue.genType"
/>
</n-form-item>
</n-col>
<n-col :span="6" style="min-width: 200px">
<n-form-item label="实体命名" path="varName">
<n-input placeholder="请输入" v-model:value="formValue.varName" />
</n-form-item>
</n-col>
<n-col :span="6" style="min-width: 200px">
<n-form-item
label="数据库"
path="dbName"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-select
placeholder="请选择"
:options="selectList.db"
v-model:value="formValue.dbName"
@update:value="handleDbUpdateValue"
/>
</n-form-item>
</n-col>
<n-col :span="6" style="min-width: 200px">
<n-form-item
label="数据库表"
path="tableName"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-select
filterable
tag
:loading="tablesLoading"
placeholder="请选择"
:options="tablesOption"
v-model:value="formValue.tableName"
@update:value="handleTableUpdateValue"
:disabled="formValue.dbName === ''"
/>
</n-form-item>
</n-col>
<n-col :span="18">
<n-form-item
label="表格头部按钮组"
path="tableName"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-checkbox-group v-model:value="formValue.options.headOps">
<n-space item-style="display: flex;">
<n-checkbox value="add" label="新增表单按钮" />
<n-checkbox value="batchDel" label="批量删除按钮" />
<n-checkbox value="export" label="导出按钮" />
</n-space>
</n-checkbox-group>
</n-form-item>
</n-col>
<n-col :span="24">
<n-form-item
label="表格列操作"
path="columnOps"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-checkbox-group v-model:value="formValue.options.columnOps">
<n-space item-style="display: flex;">
<n-checkbox value="edit" label="编辑" />
<n-checkbox value="status" label="状态修改" />
<n-popover trigger="hover">
<template #trigger>
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
<span>主表中存在`status`字段时才会生效</span>
</n-popover>
<n-checkbox value="del" label="删除" />
<n-checkbox value="view" label="详情页" />
<n-checkbox value="check" label="开启勾选列" />
<n-checkbox value="switch" label="操作开关" />
<n-popover trigger="hover">
<template #trigger>
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
<span>主表中存在`switch`字段时才会生效</span>
</n-popover>
</n-space>
</n-checkbox-group>
</n-form-item>
</n-col>
<n-col :span="24">
<n-form-item
label="自动化操作"
path="autoOps"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-checkbox-group v-model:value="formValue.options.autoOps">
<n-space item-style="display: flex;">
<n-checkbox value="genMenuPermissions" label="生成菜单权限" />
<n-checkbox value="runDao" label="生成前运行 [gf gen dao]" />
<n-popover trigger="hover">
<template #trigger>
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
<span>请确保运行环境已安装gf命令</span>
</n-popover>
<n-checkbox value="runService" label="生成后运行 [gf gen service]" />
<n-popover trigger="hover">
<template #trigger>
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
<span>请确保运行环境已安装gf命令</span>
</n-popover>
<n-checkbox value="forcedCover" label="强制覆盖" />
<n-popover trigger="hover">
<template #trigger>
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
<span>只会强制覆盖需要生成的文件但不包含SQL文件</span>
</n-popover>
</n-space>
</n-checkbox-group>
</n-form-item>
</n-col>
<n-col
:span="6"
style="min-width: 200px"
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
>
<n-form-item label="上级菜单" path="pid">
<n-tree-select
:options="optionMenuTree"
:value="formValue.options.menu.pid"
@update:value="handleUpdateMenuPid"
/>
</n-form-item>
</n-col>
<n-col
:span="6"
style="min-width: 200px"
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
>
<n-form-item label="菜单名称" path="tableComment">
<n-input placeholder="请输入" v-model:value="formValue.tableComment" />
</n-form-item>
</n-col>
<n-col
:span="6"
style="min-width: 200px"
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
>
<n-form-item label="菜单图标" path="menuIcon">
<IconSelector style="width: 100%" v-model:value="formValue.options.menu.icon" />
</n-form-item>
</n-col>
<n-col
:span="6"
style="min-width: 200px"
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
>
<n-form-item label="菜单排序" path="menuIcon">
<n-input-number
style="width: 100%"
placeholder="请输入"
v-model:value="formValue.options.menu.sort"
clearable
/>
</n-form-item>
</n-col>
</n-row>
</n-form>
</n-card>
<n-card
:bordered="true"
title="关联表设置"
class="proCard mt-2"
size="small"
:segmented="{ content: true }"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<template #header-extra>
<n-space>
<n-button type="warning" @click="addJoin" :disabled="formValue.options?.join?.length >= 3"
>新增关联表</n-button
>
</n-space>
</template>
<n-form ref="formRef" :model="formValue">
<n-alert :show-icon="false">关联表数量建议在三个以下</n-alert>
<n-row :gutter="6" v-for="(join, index) in formValue.options.join" :key="index">
<n-col :span="6" style="min-width: 200px">
<n-form-item label="关联表" path="join.linkTable">
<n-select
filterable
tag
:loading="tablesLoading"
placeholder="请选择"
:options="linkTablesOption"
v-model:value="join.linkTable"
@update:value="handleLinkTableUpdateValue(join)"
:disabled="formValue.dbName === ''"
/>
</n-form-item>
</n-col>
<n-col :span="3" style="min-width: 100px">
<n-form-item
label="别名"
path="join.alias"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-input
placeholder="请输入"
v-model:value="join.alias"
@update:value="updateJoinAlias"
/>
<template #feedback> {{ joinAliasFeedback }}</template>
</n-form-item>
</n-col>
<n-col :span="3" style="min-width: 100px">
<n-form-item label="关联方式" path="join.linkMode">
<n-select
placeholder="请选择"
:options="selectList.linkMode"
v-model:value="join.linkMode"
/>
</n-form-item>
</n-col>
<n-col :span="5" style="min-width: 180px">
<n-form-item label="关联字段" path="join.field">
<n-select
filterable
tag
:loading="linkColumnsLoading"
placeholder="请选择"
:options="linkColumnsOption[join.uuid]"
v-model:value="join.field"
/>
</n-form-item>
</n-col>
<n-col :span="5" style="min-width: 180px">
<n-form-item label="主表关联字段" path="join.masterField">
<n-select
filterable
tag
:loading="columnsLoading"
placeholder="请选择"
:options="columnsOption"
v-model:value="join.masterField"
/>
</n-form-item>
</n-col>
<n-col :span="2" style="min-width: 50px">
<n-space>
<n-form-item label="操作" path="title">
<n-button @click="delJoin(join, index)" size="small" strong secondary type="error"
>移除</n-button
>
</n-form-item>
</n-space>
</n-col>
</n-row>
</n-form>
</n-card>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, computed, watch } from 'vue';
import { FormInst } from 'naive-ui';
import { newState, selectListObj } from './model';
import { TableSelect, ColumnSelect } from '@/api/develop/code';
import { getRandomString } from '@/utils/charset';
import IconSelector from '@/components/IconSelector/index.vue';
import { QuestionCircleOutlined } from '@vicons/antd';
import { getMenuList } from '@/api/system/menu';
import { cloneDeep } from 'lodash-es';
import { isLetterBegin } from '@/utils/is';
const formRef = ref<FormInst | null>(null);
const tablesLoading = ref(false);
const columnsLoading = ref(false);
const linkColumnsLoading = ref(false);
const tablesOption = ref<any>([]); // 数据库表选项
const columnsOption = ref<any>([]); // 主表字段选项
const linkTablesOption = ref<any>([]); // 关联表选项
const linkColumnsOption = ref<any>([]); // 关联表字段选项
const optionMenuTree = ref([
{
id: 0,
key: 0,
label: '根目录',
pid: 0,
title: '根目录',
type: 1,
},
]);
const emit = defineEmits(['update:value']);
interface Props {
value?: any;
selectList: any;
}
const props = withDefaults(defineProps<Props>(), {
value: newState(null),
selectList: selectListObj,
});
watch(props, async (newVal, oldVal) => {
if (newVal.value.dbName != oldVal.value.dbName) {
await instLoad();
}
});
const formValue = computed({
get() {
return props.value;
},
set(value) {
emit('update:value', value);
},
});
onMounted(() => {
setTimeout(async function () {
await instLoad();
// 切换tab时会导致选项被清空这里重新进行加载
await loadLinkColumnsOption();
await loadMenuTreeOption();
}, 500);
});
const loadMenuTreeOption = async () => {
const options = await getMenuList();
optionMenuTree.value = [
{
id: 0,
key: 0,
label: '根目录',
pid: 0,
title: '根目录',
type: 1,
},
];
optionMenuTree.value = optionMenuTree.value.concat(options.list);
};
const loadSelect = async () => {
columnsOption.value = await loadColumnSelect(formValue.value.tableName);
};
async function instLoad() {
columnsLoading.value = true;
tablesLoading.value = true;
await loadSelect();
await loadTableSelect(formValue.value.dbName);
tablesLoading.value = false;
columnsLoading.value = false;
}
async function loadLinkColumnsOption() {
if (formValue.value.options.join === undefined) {
return;
}
for (let i = 0; i < formValue.value.options.join.length; i++) {
linkColumnsLoading.value = true;
linkColumnsOption.value[formValue.value.options.join[i].uuid] = await loadColumnSelect(
formValue.value.options.join[i].linkTable
);
linkColumnsLoading.value = false;
}
}
// 处理选项更新
async function handleDbUpdateValue(value, _option) {
tablesLoading.value = true;
await loadTableSelect(value);
tablesLoading.value = false;
}
async function loadTableSelect(value) {
const options = await TableSelect({ name: value });
tablesOption.value = cloneDeep(options);
linkTablesOption.value = cloneDeep(options);
}
async function loadColumnSelect(value) {
return await ColumnSelect({ name: formValue.value.dbName, table: value });
}
function handleTableUpdateValue(value, option) {
formValue.value.varName = option?.defVarName as string;
formValue.value.daoName = option?.daoName as string;
formValue.value.tableComment = option?.defTableComment as string;
}
function addJoin() {
if (formValue.value.options.join === undefined) {
formValue.value.options.join = [];
}
let uuid = getRandomString(16, true);
formValue.value.options.join.push({
uuid: uuid,
linkTable: '',
alias: '',
linkMode: 1,
field: '',
masterField: '',
daoName: '',
columns: [],
});
linkColumnsOption.value[uuid] = [];
}
function delJoin(join, index) {
formValue.value.options.join.splice(index, 1);
delete linkColumnsOption.value[join.uuid];
let i = linkTablesOption.value.findIndex((res) => res.value === join.linkTable);
if (i > -1) {
linkTablesOption.value[i].disabled = false;
}
}
async function handleLinkTableUpdateValue(join) {
let i = linkTablesOption.value.findIndex((res) => res.value === join.linkTable);
if (i > -1) {
join.alias = linkTablesOption.value[i].defAlias;
join.daoName = linkTablesOption.value[i].daoName;
linkTablesOption.value[i].disabled = true;
}
linkColumnsLoading.value = true;
linkColumnsOption.value[join.uuid] = await loadColumnSelect(join.linkTable);
// 清空更新前的字段
join.field = '';
linkColumnsLoading.value = false;
}
const joinAliasFeedback = ref('');
function updateJoinAlias(value: string) {
if (value.length < 3) {
joinAliasFeedback.value = '别名不能小于3位';
return;
}
if (!isLetterBegin(value)) {
joinAliasFeedback.value = '别名必须以字母开头';
return;
}
joinAliasFeedback.value = '';
}
function handleUpdateMenuPid(value: string | number | Array<string | number> | null) {
formValue.value.options.menu.pid = value;
}
</script>
<style lang="less" scoped>
::v-deep(.default_text_value) {
color: var(--n-tab-text-color-active);
}
::v-deep(.tips-help-icon) {
margin-left: -16px;
margin-top: 5px;
display: block;
}
</style>

View File

@@ -0,0 +1,357 @@
<template>
<n-spin :show="show" description="加载中...">
<n-card :bordered="false" class="proCard">
<BasicTable
:single-line="false"
size="small"
:striped="true"
:resizable="true"
:columns="columns"
:dataSource="dataSource"
:openChecked="false"
:showTopRight="false"
:row-key="(row) => row.id"
ref="actionRef"
:canResize="true"
:resizeHeightOffset="-20000"
:pagination="false"
:scroll-x="1090"
:scrollbar-props="{ trigger: 'none' }"
/>
</n-card>
</n-spin>
</template>
<script lang="ts" setup>
import { computed, h, onMounted, ref } from 'vue';
import { BasicTable } from '@/components/Table';
import { genInfoObj, selectListObj } from '@/views/develop/code/components/model';
import { ColumnList } from '@/api/develop/code';
import { NButton, NCheckbox, NInput, NSelect, NTooltip, NTreeSelect } from 'naive-ui';
import { HelpCircleOutline } from '@vicons/ionicons5';
import { renderIcon } from '@/utils';
import { cloneDeep } from 'lodash-es';
const renderTooltip = (trigger, content) => {
return h(NTooltip, null, {
trigger: () => trigger,
default: () => content,
});
};
const emit = defineEmits(['update:value']);
interface Props {
value?: any;
selectList: any;
}
const props = withDefaults(defineProps<Props>(), {
value: genInfoObj,
selectList: selectListObj,
});
const formValue = computed({
get() {
return props.value;
},
set(value) {
emit('update:value', value);
},
});
const actionRef = ref();
const columns = ref<any>([]);
const show = ref(false);
const dataSource = ref(formValue.value.masterColumns);
onMounted(async () => {
show.value = true;
if (formValue.value.masterColumns.length === 0) {
formValue.value.masterColumns = await ColumnList({
name: formValue.value.dbName,
table: formValue.value.tableName,
});
dataSource.value = formValue.value.masterColumns;
}
columns.value = [
{
title: '位置',
key: 'id',
width: 50,
},
{
title(_column) {
return renderTooltip(
h(
NButton,
{
ghost: true,
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',
},
{ default: () => '字段', icon: renderIcon(HelpCircleOutline) }
),
'Go类型和属性定义取决于你在/hack/config.yaml中的配置参数'
);
},
key: 'field',
align: 'center',
width: 800,
children: [
{
title: '字段列名',
key: 'name',
width: 150,
},
{
title: '物理类型',
key: 'sqlType',
width: 150,
},
{
title: 'Go属性',
key: 'goName',
width: 130,
},
{
title: 'Go类型',
key: 'goType',
width: 100,
},
{
title: 'Ts属性',
key: 'tsName',
width: 130,
},
{
title: 'Ts类型',
key: 'tsType',
width: 100,
},
{
title: '字段描述',
key: 'dc',
width: 150,
render(row) {
return h(NInput, {
value: row.dc,
onUpdateValue: function (e) {
row.dc = e;
},
});
},
},
],
},
{
width: 800,
title(_column) {
return renderTooltip(
h(
NButton,
{
ghost: true,
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',
},
{ default: () => '新增/编辑表单', icon: renderIcon(HelpCircleOutline) }
),
'勾选编辑以后会在新增、编辑表单中显示该字段;当同时勾选列表查询时,会优先使用配置的表单组件'
);
},
key: 'edit',
align: 'center',
children: [
{
align: 'center',
title: '编辑',
key: 'isEdit',
width: 50,
render(row) {
return h(NCheckbox, {
defaultChecked: row.isEdit,
disabled: row.name === 'id',
onUpdateChecked: function (e) {
row.isEdit = e;
},
});
},
},
{
title: '必填',
key: 'required',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.required,
disabled: row.name === 'id',
onUpdateChecked: function (e) {
row.required = e;
},
});
},
},
{
title: '唯一',
key: 'unique',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.unique,
disabled: row.name === 'id',
onUpdateChecked: function (e) {
row.unique = e;
},
});
},
},
{
title: '表单组件',
key: 'formMode',
width: 200,
render(row) {
return h(NSelect, {
value: row.formMode,
options: getFormModeOptions(row.tsType),
// render: function (row) {
// return props.selectList?.formMode ?? [];
// },
// onFocus: function (e) {
// console.log('表单组件 onFocus row:', e);
// },
onUpdateValue: function (e) {
row.formMode = e;
},
});
},
},
{
title: '表单验证',
key: 'formRole',
width: 200,
render(row) {
return h(NSelect, {
value: row.formRole,
disabled: row.name === 'id',
options: props.selectList?.formRole ?? [],
onUpdateValue: function (e) {
row.formRole = e;
},
});
},
},
{
title: '字典类型',
key: 'dictType',
width: 300,
render(row) {
return h(NTreeSelect, {
value: row.dictType,
disabled: row.name === 'id',
options: props.selectList?.dictMode ?? [],
onUpdateValue: function (e) {
row.dictType = e;
},
});
},
},
],
},
{
width: 800,
title: '列表',
key: 'list',
align: 'center',
children: [
{
title: '列表',
key: 'isList',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isList,
onUpdateChecked: function (e) {
row.isList = e;
},
});
},
},
{
title: '导出',
key: 'isExport',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isExport,
onUpdateChecked: function (e) {
row.isExport = e;
},
});
},
},
{
title: '查询',
key: 'isQuery',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isQuery,
onUpdateChecked: function (e) {
row.isQuery = e;
},
});
},
},
{
title: '查询条件',
key: 'queryWhere',
width: 300,
render(row) {
return h(NSelect, {
value: row.queryWhere,
disabled: row.name === 'id',
options: props.selectList?.whereMode ?? [],
onUpdateValue: function (e) {
row.queryWhere = e;
},
});
},
},
],
},
];
show.value = false;
});
function getFormModeOptions(type: string) {
const options = cloneDeep(props.selectList?.formMode ?? []);
if (options.length === 0) {
return [];
}
switch (type) {
case 'number':
for (let i = 0; i < options.length; i++) {
const allows = ['InputNumber', 'Radio', 'Select', 'Switch', 'Rate'];
if (!allows.includes(options[i].value)) {
options[i].disabled = true;
}
}
break;
default:
}
return options;
}
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,243 @@
<template>
<n-spin :show="show" description="加载中...">
<n-card :bordered="false" class="proCard">
<BasicTable
:single-line="false"
size="small"
:striped="true"
:resizable="true"
:columns="columns"
:dataSource="dataSource"
:openChecked="false"
:showTopRight="false"
:row-key="(row) => row.id"
ref="actionRef"
:canResize="true"
:resizeHeightOffset="-20000"
:pagination="false"
:scroll-x="1090"
:scrollbar-props="{ trigger: 'none' }"
/>
</n-card>
</n-spin>
</template>
<script lang="ts" setup>
import { Component, computed, h, onMounted, ref } from 'vue';
import { BasicTable } from '@/components/Table';
import { genInfoObj, selectListObj } from '@/views/develop/code/components/model';
import { ColumnList } from '@/api/develop/code';
import { NButton, NCheckbox, NIcon, NInput, NSelect, NTooltip } from 'naive-ui';
import { HelpCircleOutline } from '@vicons/ionicons5';
const renderTooltip = (trigger, content) => {
return h(NTooltip, null, {
trigger: () => trigger,
default: () => content,
});
};
function renderIcon(icon: Component) {
return () => h(NIcon, null, { default: () => h(icon) });
}
const emit = defineEmits(['update:value']);
interface Props {
value?: any;
selectList: any;
uuid: string;
}
const props = withDefaults(defineProps<Props>(), {
value: genInfoObj,
selectList: selectListObj,
uuid: '',
});
const columns = ref<any>([]);
const formValue = computed({
get() {
return props.value;
},
set(value) {
emit('update:value', value);
},
});
function getIndex() {
if (formValue.value.options.join.length === 0) {
return -1;
}
for (let i = 0; i < formValue.value.options.join.length; i++) {
if (formValue.value.options.join[i].uuid === props.uuid) {
return i;
}
}
return -1;
}
const show = ref(false);
const dataSource = ref([]);
onMounted(async () => {
show.value = true;
setTimeout(async () => {
const index = getIndex();
if (formValue.value.options.join[index].columns.length === 0) {
formValue.value.options.join[index].columns = await ColumnList({
name: formValue.value.dbName,
table: formValue.value.options.join[index].linkTable,
isLink: 1,
alias: formValue.value.options.join[index].alias,
});
}
dataSource.value = formValue.value.options.join[index].columns;
columns.value = [
{
title: '位置',
key: 'id',
width: 50,
},
{
title(_column) {
return renderTooltip(
h(
NButton,
{
ghost: true,
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',
},
{ default: () => '字段', icon: renderIcon(HelpCircleOutline) }
),
'Go类型和属性定义取决于你在/hack/config.yaml中的配置参数'
);
},
key: 'field',
align: 'center',
width: 800,
children: [
{
title: '字段列名',
key: 'name',
width: 150,
},
{
title: '物理类型',
key: 'sqlType',
width: 150,
},
{
title: 'Go属性',
key: 'goName',
width: 260,
},
{
title: 'Go类型',
key: 'goType',
width: 100,
},
{
title: 'Ts属性',
key: 'tsName',
width: 260,
},
{
title: 'Ts类型',
key: 'tsType',
width: 100,
},
{
title: '字段描述',
key: 'dc',
width: 150,
render(row) {
return h(NInput, {
value: row.dc,
onUpdateValue: function (e) {
row.dc = e;
// await saveProductCustom(row.id, 'frontShow', e);
},
});
},
},
],
},
{
width: 800,
title: '列表',
key: 'list',
align: 'center',
children: [
{
title: '列表',
key: 'isList',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isList,
onUpdateChecked: function (e) {
row.isList = e;
},
});
},
},
{
title: '导出',
key: 'isExport',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isExport,
onUpdateChecked: function (e) {
row.isExport = e;
},
});
},
},
{
title: '查询',
key: 'isQuery',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isQuery,
onUpdateChecked: function (e) {
row.isQuery = e;
},
});
},
},
{
title: '查询条件',
key: 'queryWhere',
width: 300,
render(row) {
return h(NSelect, {
value: row.queryWhere,
disabled: row.name === 'id',
options: props.selectList?.whereMode ?? [],
onUpdateValue: function (e) {
row.queryWhere = e;
},
});
},
},
],
},
];
show.value = false;
}, 50);
});
const actionRef = ref();
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,84 @@
<template>
<div>
<n-tabs type="line" animated>
<n-tab-pane v-for="(view, index) in views" :key="index" :name="view.name" :tab="view.name">
<n-tag :type="view.tag.type" class="tag-margin">
{{ view.tag.label }}
<template #icon>
<n-icon :component="view.tag.icon" />
</template>
{{ view.path }}
</n-tag>
<n-scrollbar class="code-scrollbar" trigger="none">
<n-code :code="view.content" />
</n-scrollbar>
</n-tab-pane>
</n-tabs>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { cloneDeep } from 'lodash-es';
import {
CheckmarkCircle,
CheckmarkDoneCircle,
CloseCircleOutline,
HelpCircleOutline,
RemoveCircleOutline,
} from '@vicons/ionicons5';
interface Props {
previewModel: any;
showModal: boolean;
}
const props = withDefaults(defineProps<Props>(), {
previewModel: cloneDeep({ views: {} }),
showModal: false,
});
const views = computed(() => {
let tmpViews: any = [];
let i = 0;
for (const [k, v] of Object.entries(props.previewModel.views)) {
let item = v as any;
item.name = k;
switch (item.meth) {
case 1:
item.tag = { type: 'success', label: '创建文件', icon: CheckmarkCircle };
break;
case 2:
item.tag = { type: 'warning', label: '覆盖文件', icon: CheckmarkDoneCircle };
break;
case 3:
item.tag = { type: 'info', label: '已存在跳过', icon: CloseCircleOutline };
break;
case 4:
item.tag = { type: 'error', label: '不生成', icon: RemoveCircleOutline };
break;
default:
item.tag = { type: 'error', label: '未知状态', icon: HelpCircleOutline };
}
tmpViews[i] = item;
i++;
}
return tmpViews;
});
</script>
<style lang="less" scoped>
::v-deep(.alert-margin) {
margin-bottom: 20px;
}
::v-deep(.tag-margin) {
margin-bottom: 10px;
}
::v-deep(.code-scrollbar) {
height: calc(100vh - 300px);
background: #282b2e;
color: #e0e2e4;
padding: 10px;
}
</style>

View File

@@ -0,0 +1,62 @@
import { cloneDeep } from 'lodash-es';
export const genFileObj = {
meth: 1,
content: '',
path: '',
required: true,
};
export interface joinAttr {
uuid: string;
linkTable: string;
alias: string;
linkMode: number;
field: string;
masterField: string;
columns: any;
}
export const genInfoObj = {
id: 0,
genType: 10,
varName: '',
options: {
headOps: ['add', 'batchDel', 'export'],
columnOps: ['edit', 'del', 'view', 'status', 'switch', 'check'],
autoOps: ['genMenuPermissions', 'runDao', 'runService'],
join: [],
menu: {
pid: 0,
icon: 'MenuOutlined',
sort: 0,
},
},
dbName: '',
tableName: '',
tableComment: '',
daoName: '',
masterColumns: [],
status: 2,
createdAt: '',
updatedAt: '',
};
export const selectListObj = {
db: [],
genType: [],
status: [],
tables: [],
formMode: [],
formRole: [],
dictMode: [],
whereMode: [],
buildMeth: [],
};
export function newState(state) {
if (state !== null) {
return cloneDeep(state);
}
return cloneDeep(genInfoObj);
}

View File

@@ -0,0 +1,264 @@
<template>
<div>
<n-spin :show="show" description="正在生成配置信息...">
<n-card>
<n-tabs
type="card"
class="card-tabs"
:default-value="value"
animated
tab-style="min-width: 80px;"
style="margin: 0 -4px"
pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;"
:on-update:value="updateTabs"
ref="tabsRef"
@close="handleClose"
@add="handleAdd"
>
<n-tab-pane v-for="panel in panels" :key="panel" :name="panel">
<template v-if="panel === '基本信息'">
<BaseInfo v-model:value="genInfo" :selectList="selectList" />
</template>
<template v-if="panel === '主表字段'">
<EditMasterCell v-model:value="genInfo" :selectList="selectList" />
</template>
</n-tab-pane>
<n-tab-pane
v-for="panel in slavePanels"
:key="panel"
:name="panel"
v-show="slavePanels.length > 0 && slavePanels !== []"
>
<EditSlaveCell
v-model:value="genInfo"
:uuid="slaveMap[panel]"
:selectList="selectList"
/>
</n-tab-pane>
<template #suffix>
<n-space>
<n-button type="primary" @click="preview">预览代码</n-button>
<n-button type="success" :loading="formBtnLoading" @click="submitBuild"
>提交生成</n-button
>
<n-button type="info" dashed :loading="formBtnLoading" @click="submitSave"
>仅保存配置</n-button
>
</n-space>
</template>
</n-tabs>
<n-modal
v-model:show="showModal"
:block-scroll="false"
:mask-closable="false"
:show-icon="false"
preset="card"
title="预览代码"
style="width: 95%"
>
<PreviewTab :previewModel="previewModel" />
<template #action>
<n-space justify="end">
<n-button @click="() => (showModal = false)">关闭</n-button>
<n-button type="info" :loading="formBtnLoading" @click="submitBuild"
>提交生成</n-button
>
</n-space>
</template>
</n-modal>
</n-card>
</n-spin>
</div>
</template>
<style scoped>
.card-tabs .n-tabs-nav--bar-type {
padding-left: 4px;
}
</style>
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { useDialog, useMessage } from 'naive-ui';
import BaseInfo from './components/BaseInfo.vue';
import EditMasterCell from './components/EditMasterCell.vue';
import EditSlaveCell from './components/EditSlaveCell.vue';
import { Selects, View, Preview, Build, Edit } from '@/api/develop/code';
import { selectListObj, newState } from '@/views/develop/code/components/model';
import PreviewTab from '@/views/develop/code/components/PreviewTab.vue';
import { isJsonString } from '@/utils/is';
interface Props {
genId?: number;
}
const props = withDefaults(defineProps<Props>(), { genId: 0 });
const router = useRouter();
const genId = Number(router.currentRoute.value.params.id ?? props.genId);
const show = ref(false);
const message = useMessage();
const selectList = ref<any>(selectListObj);
const genInfo = ref(newState(null));
const tabsRef = ref();
const value = ref('基本信息');
const panels = ref(['基本信息', '主表字段']);
const slaveMap = ref<any>([]);
const slavePanels = ref<any>([]);
const showModal = ref(false);
const formBtnLoading = ref(false);
const previewModel = ref<any>();
const dialog = useDialog();
onMounted(async () => {
if (genId < 1 && props.genId < 1) {
message.error('生成ID不正确请检查');
return;
}
await getGenInfo();
await loadSelect();
});
async function getGenInfo() {
let tmp = await View({ id: genId });
if (isJsonString(tmp.options)) {
tmp.options = JSON.parse(tmp.options);
}
if (tmp.masterColumns === undefined || tmp.masterColumns.length === 0) {
tmp.masterColumns = [];
}
if (isJsonString(tmp.masterColumns)) {
tmp.masterColumns = JSON.parse(tmp.masterColumns);
}
genInfo.value = tmp;
}
watch(
genInfo,
(newVal, _oldVal) => {
if (newVal.genType >= 10 && newVal.genType < 20) {
handleAdd('主表字段');
} else {
handleClose('主表字段');
}
if (newVal.options.join !== undefined) {
slavePanels.value = [];
for (let i = 0; i <= newVal.options.join.length; i++) {
if (newVal.options.join[i]?.alias !== undefined && newVal.options.join[i]?.alias !== '') {
handleSlaveAdd(
'关联表[ ' + newVal.options.join[i]?.alias + ' ]',
newVal.options.join[i]
);
}
}
}
},
{
deep: true, // 是否深度监听
}
);
function updateTabs(value: string | number) {
console.log('value:' + value);
}
function handleAdd(name: string) {
const nameIndex = panels.value.findIndex((panelName) => panelName === name);
if (!~nameIndex) {
panels.value.push(name);
}
}
function handleSlaveAdd(name: string, join) {
const nameIndex = slavePanels.value.findIndex((panelName) => panelName === name);
if (!~nameIndex) {
slavePanels.value.push(name);
slaveMap.value[name] = join.uuid;
}
}
function _handleSlaveClose(name: string) {
const nameIndex = panels.value.findIndex((panelName) => panelName === name);
if (!~nameIndex) return;
panels.value.splice(nameIndex, 1);
if (name === value.value) {
value.value = panels.value[Math.min(nameIndex, panels.value.length - 1)];
}
}
function handleClose(name: string) {
const nameIndex = panels.value.findIndex((panelName) => panelName === name);
if (!~nameIndex) return;
panels.value.splice(nameIndex, 1);
if (name === value.value) {
value.value = panels.value[Math.min(nameIndex, panels.value.length - 1)];
}
}
const loadSelect = async () => {
selectList.value = await Selects({});
};
async function preview() {
previewModel.value = await Preview(genInfo.value);
showModal.value = true;
}
function submitBuild() {
dialog.warning({
title: '警告',
content: '你确定要提交生成吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Build(genInfo.value).then((_res) => {
setTimeout(function () {
location.reload();
}, 1500);
message.success('生成提交成功,即将刷新页面..');
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function submitSave() {
dialog.warning({
title: '警告',
content: '你确定要保存生成配置吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Edit(genInfo.value).then((_res) => {
message.success('操作成功');
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
</script>
<style lang="less" scoped>
::v-deep(.alert-margin) {
margin-bottom: 20px;
}
::v-deep(.tag-margin) {
margin-bottom: 10px;
}
::v-deep(.code-scrollbar) {
height: calc(100vh - 300px);
background: #282b2e;
color: #e0e2e4;
padding: 10px;
}
</style>

View File

@@ -1,39 +1,452 @@
<template>
<div class="flex flex-col justify-center page-container">
<div class="text-center">
<h1 class="text-base">代码生成敬请期待</h1>
<n-button type="info" @click="goHome">回到首页</n-button>
</div>
</div>
<n-card :bordered="false" class="proCard">
<n-card :bordered="false" title="代码生成"> 你可以在这里查看到平台所有的短信发送记录 </n-card>
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" ref="searchFormRef">
<template #statusSlot="{ model, field }">
<n-input v-model:value="model[field]" />
</template>
</BasicForm>
<BasicTable
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
ref="actionRef"
:actionColumn="actionColumn"
@update:checked-row-keys="onCheckedRow"
:scroll-x="1090"
>
<template #tableTitle>
<n-button type="primary" @click="addTable">
<template #icon>
<n-icon>
<PlusOutlined />
</n-icon>
</template>
生成
</n-button>
&nbsp;
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
<template #icon>
<n-icon>
<DeleteOutlined />
</n-icon>
</template>
批量删除
</n-button>
</template>
</BasicTable>
<n-modal
v-model:show="showModal"
:show-icon="false"
preset="dialog"
title="生成"
:style="{
width: dialogWidth,
}"
>
<!-- <n-alert :show-icon="false" type="info">-->
<!-- 注意:!-->
<!-- </n-alert>-->
<n-form
:model="formParams"
:rules="rules"
ref="formRef"
label-placement="left"
:label-width="80"
class="py-4"
>
<n-form-item label="生成类型" path="genType">
<n-select
placeholder="请选择"
:options="selectList.genType"
v-model:value="formParams.genType"
/>
</n-form-item>
<n-form-item
label="数据库"
path="dbName"
v-if="formParams.genType >= 10 && formParams.genType < 20"
>
<n-select
placeholder="请选择"
:options="selectList.db"
v-model:value="formParams.dbName"
@update:value="handleDbUpdateValue"
/>
</n-form-item>
<n-form-item
label="数据库表"
path="tableName"
v-if="formParams.genType >= 10 && formParams.genType < 20"
>
<n-select
filterable
tag
:loading="tablesLoading"
placeholder="请选择"
:options="selectList.tables"
v-model:value="formParams.tableName"
@update:value="handleTableUpdateValue"
:disabled="formParams.dbName === ''"
/>
</n-form-item>
<n-form-item
label="菜单名称"
path="tableComment"
v-show="formParams.genType >= 10 && formParams.genType < 20"
>
<n-input placeholder="请输入" v-model:value="formParams.tableComment" />
</n-form-item>
<n-form-item label="实体命名" path="varName">
<n-input placeholder="请输入" v-model:value="formParams.varName" />
</n-form-item>
</n-form>
<template #action>
<n-space>
<n-button @click="() => (showModal = false)">取消</n-button>
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">生成配置</n-button>
</n-space>
</template>
</n-modal>
</n-card>
</template>
<script lang="ts" setup>
import { h, onBeforeMount, reactive, ref } from 'vue';
import { NTag, TreeSelectOption, useDialog, useMessage } from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
import { List, Delete, Edit, Selects, TableSelect } from '@/api/develop/code';
import { useRouter } from 'vue-router';
import { PlusOutlined, DeleteOutlined } from '@vicons/antd';
import { newState } from '@/views/develop/code/components/model';
import { getOptionLabel } from '@/utils/hotgo';
const selectList = ref({
db: [],
genType: [],
status: [],
tables: [],
formMode: [],
formRole: [],
dictMode: [],
whereMode: [],
});
const columns = [
{
title: '生成ID',
key: 'id',
width: 100,
},
{
title: '生成类型',
key: 'genType',
render(row) {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: 'info',
bordered: false,
},
{
default: () => getOptionLabel(selectList.value.genType, row.genType),
}
);
},
width: 200,
},
{
title: '实体命名',
key: 'varName',
render(row) {
return row.varName;
},
width: 180,
},
{
title: '数据库',
key: 'dbName',
width: 200,
},
{
title: '数据表',
key: 'tableName',
width: 200,
},
{
title: '菜单名称',
key: 'tableComment',
width: 200,
},
{
title: '生成状态',
key: 'status',
render(row) {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: row.status == 1 ? 'success' : 'warning',
bordered: false,
},
{
default: () => getOptionLabel(selectList.value.status, row.status),
}
);
},
width: 150,
},
{
title: '创建时间',
key: 'createdAt',
width: 180,
},
{
title: '更新时间',
key: 'updatedAt',
width: 180,
},
];
const dialog = useDialog();
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
const searchFormRef = ref<any>();
const schemas = ref<FormSchema[]>([
{
field: 'genType',
component: 'NSelect',
label: '生成类型',
componentProps: {
placeholder: '请选择生成类型',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'varName',
component: 'NInput',
label: '实体命名',
componentProps: {
placeholder: '请输入实体命名',
onInput: (e: any) => {
console.log(e);
},
},
rules: [{ trigger: ['blur'] }],
},
{
field: 'status',
component: 'NSelect',
label: '生成状态',
componentProps: {
placeholder: '请选择状态码',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
]);
const router = useRouter();
const showModal = ref(false);
const formBtnLoading = ref(false);
const formRef: any = ref(null);
const message = useMessage();
const actionRef = ref();
const formParams = ref<any>();
function goHome() {
router.push('/');
const rules = {
varName: {
required: true,
trigger: ['blur', 'input'],
message: '实体命名不能为空,首字母大写',
},
};
const actionColumn = reactive({
width: 220,
title: '操作',
key: 'action',
// fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '生成配置',
onClick: handleEdit.bind(null, record),
},
{
label: '删除',
onClick: handleDelete.bind(null, record),
},
],
});
},
});
const [register, {}] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 80,
schemas,
});
function onCheckedRow(rowKeys) {
batchDeleteDisabled.value = rowKeys.length <= 0;
checkedIds.value = rowKeys;
}
function handleDelete(record: Recordable) {
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record).then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function batchDelete() {
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value }).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
const loadDataTable = async (res) => {
mapWidth();
return await List({ ...res, ...searchFormRef.value?.formModel });
};
function reloadTable() {
actionRef.value.reload();
}
function handleEdit(record: Recordable) {
router.push({ name: 'develop_code_deploy', params: { id: record.id } });
}
function handleSubmit(_values: Recordable) {
reloadTable();
}
function handleReset(_values: Recordable) {
reloadTable();
}
function addTable() {
showModal.value = true;
formParams.value = newState(null);
}
function confirmForm(e) {
e.preventDefault();
formBtnLoading.value = true;
formRef.value.validate((errors) => {
if (!errors) {
console.log('formParams:' + JSON.stringify(formParams.value));
Edit(formParams.value).then((res) => {
message.success('生成成功,正在前往配置');
setTimeout(() => {
showModal.value = false;
router.push({ name: 'develop_code_deploy', params: { id: res.id } });
});
});
} else {
message.error('请填写完整信息');
}
formBtnLoading.value = false;
});
}
const dialogWidth = ref('50%');
function mapWidth() {
let val = document.body.clientWidth;
const def = 840; // 默认宽度
if (val < def) {
dialogWidth.value = '100%';
} else {
dialogWidth.value = def + 'px';
}
return dialogWidth.value;
}
onBeforeMount(async () => {
await loadSelect();
});
const loadSelect = async () => {
selectList.value = await Selects({});
for (const item of schemas.value) {
switch (item.field) {
case 'status':
item.componentProps.options = selectList.value.status;
break;
case 'genType':
item.componentProps.options = selectList.value.genType;
break;
}
}
};
const tablesLoading = ref(false);
// 处理选项更新
async function handleDbUpdateValue(
value: string | number | Array<string | number> | null,
_option: TreeSelectOption | null | Array<TreeSelectOption | null>
) {
tablesLoading.value = true;
await loadTableSelect(value);
tablesLoading.value = false;
}
async function loadTableSelect(value) {
selectList.value.tables = await TableSelect({ name: value });
}
function handleTableUpdateValue(value, option) {
formParams.value.varName = option?.defVarName as string;
formParams.value.daoName = option?.daoName as string;
formParams.value.tableComment = option?.defTableComment as string;
}
</script>
<style lang="less" scoped>
.page-container {
width: 100%;
border-radius: 4px;
padding: 50px 0;
height: 60vh;
.text-center {
h1 {
color: #666;
padding: 20px 0;
}
}
img {
width: 350px;
margin: 0 auto;
}
}
</style>
<style lang="less" scoped></style>