This commit is contained in:
孟帅
2024-04-22 23:08:40 +08:00
parent 82483bd7b9
commit e144b12580
445 changed files with 17457 additions and 6708 deletions

View File

@@ -41,6 +41,7 @@
<n-modal
v-model:show="showModal"
:mask-closable="false"
:show-icon="false"
preset="dialog"
title="创建新插件"
@@ -127,12 +128,12 @@
</template>
<script lang="ts" setup>
import { h, reactive, ref } from 'vue';
import { computed, h, reactive, ref } from 'vue';
import { NIcon, useMessage, useDialog, useNotification } from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, useForm } from '@/components/Form/index';
import { List, Build, UnInstall, Install, Upgrade } from '@/api/develop/addons';
import { PlusOutlined, QuestionCircleOutlined } from '@vicons/antd';
import { PlusOutlined } from '@vicons/antd';
import { newState, schemas, columns, options } from './model';
import { adaModalWidth } from '@/utils/hotgo';
@@ -144,15 +145,17 @@
const formRef: any = ref(null);
const actionRef = ref();
const formParams = ref<any>();
const dialogWidth = ref('50%');
const checkedIds = ref([]);
const searchFormRef = ref<any>();
const dialogWidth = computed(() => {
return adaModalWidth();
});
const actionColumn = reactive({
width: 220,
title: '操作',
key: 'action',
// fixed: 'right',
fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',
@@ -196,7 +199,6 @@
}
const loadDataTable = async (res) => {
adaModalWidth(dialogWidth);
return await List({ ...res, ...searchFormRef.value?.formModel });
};

View File

@@ -60,6 +60,54 @@
</n-form-item>
</n-col>
<!-- 树表-->
<template v-if="formValue.genType == 11">
<n-col :span="6" style="min-width: 200px">
<n-form-item path="title">
<template #label>
<div class="flex flex-row items-end"
>树名称字段
<n-tooltip trigger="hover">
<template #trigger>
<n-button strong text>
<template #icon>
<n-icon size="15" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
</n-button>
</template>
树节点的显示名称字段名 `title`
</n-tooltip>
</div>
</template>
<n-select
filterable
tag
:loading="columnsLoading"
placeholder="请选择"
:options="columnsOption"
v-model:value="formValue.options.tree.titleColumn"
/>
</n-form-item>
</n-col>
<n-col :span="6" style="min-width: 200px">
<n-form-item label="树表格样式" path="styleType">
<n-radio-group v-model:value="formValue.options.tree.styleType" name="styleType">
<n-radio
v-for="status in selectList.treeStyleType"
:value="status.value"
:label="status.label"
>{{ status.label }}</n-radio
>
</n-radio-group>
</n-form-item>
</n-col>
</template>
<!-- 树表-->
<n-col :span="18">
<n-form-item
label="表格头部按钮组"
@@ -97,15 +145,6 @@
<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>表单组件中存在`开关`类型才会生效</span>
</n-popover>
<n-checkbox value="notFilterAuth" label="不过滤权限" />
<n-popover trigger="hover">
<template #trigger>
@@ -122,7 +161,7 @@
<n-col :span="24">
<n-form-item
label="自动化操作"
label="高级设置"
path="autoOps"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
@@ -136,7 +175,7 @@
<QuestionCircleOutlined />
</n-icon>
</template>
<span>如果你选择的表已经生成过dao相关代码可以忽略</span>
<span>如果你选择的表已经生成过dao相关代码取消勾选可减少生成时间</span>
</n-popover>
<n-checkbox value="runService" label="生成后运行 [gf gen service]" />
<n-popover trigger="hover">
@@ -147,6 +186,19 @@
</template>
<span>如果是插件模块勾选后也会自动在对应插件下运行service相关代码生成</span>
</n-popover>
<n-checkbox
value="genFuncDict"
label="生成字典选项"
@click="handleCheckboxGenFuncDict"
/>
<n-popover trigger="hover">
<template #trigger>
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
<span>将表数据生成为数据选项并注册到内置的方法字典</span>
</n-popover>
<n-checkbox value="forcedCover" label="强制覆盖" />
<n-popover trigger="hover">
<template #trigger>
@@ -166,7 +218,13 @@
style="min-width: 200px"
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
>
<n-form-item label="上级菜单" path="pid">
<n-form-item path="pid">
<template #label>
<span>上级菜单</span>
<n-button class="ml-2" text type="primary" strong @click="handleAddMenu"
>菜单管理</n-button
>
</template>
<n-tree-select
:options="optionMenuTree"
:value="formValue.options.menu.pid"
@@ -224,16 +282,16 @@
<template #header-extra>
<n-space>
<n-button
type="warning"
type="primary"
@click="addJoin"
:disabled="formValue.options?.join?.length >= 3"
:disabled="formValue.options?.join?.length >= 20"
>新增关联表</n-button
>
</n-space>
</template>
<n-form ref="formRef" :model="formValue">
<n-alert :show-icon="false">关联表数量建议在三个以下</n-alert>
<n-alert type="warning" :show-icon="false" v-if="formValue.options?.join?.length > 3">关联表数量建议在三个以下</n-alert>
<div class="mt-4"></div>
<n-row :gutter="6" v-for="(join, index) in formValue.options.join" :key="index">
<n-col :span="6" style="min-width: 200px">
@@ -315,12 +373,19 @@
</n-form>
</n-card>
</n-spin>
<MenuModal ref="menuModalRef" @reloadTable="loadMenuTreeOption" />
<SetFuncDict
ref="setFuncDictRef"
@update="handleUpdateFuncDict"
:columnsOption="columnsOption"
/>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, computed, watch } from 'vue';
import { FormInst } from 'naive-ui';
import { FormInst, useDialog, useMessage } from 'naive-ui';
import { newState, selectListObj } from './model';
import { TableSelect, ColumnSelect } from '@/api/develop/code';
import { getRandomString } from '@/utils/charset';
@@ -329,7 +394,11 @@
import { getMenuList } from '@/api/system/menu';
import { cloneDeep } from 'lodash-es';
import { isLetterBegin } from '@/utils/is';
import MenuModal from '@/views/permission/menu/menuModal.vue';
import SetFuncDict from './SetFuncDict.vue';
const message = useMessage();
const dialog = useDialog();
const timer = ref();
const formRef = ref<FormInst | null>(null);
const bodyShow = ref(true);
@@ -340,6 +409,8 @@
const columnsOption = ref<any>([]); // 主表字段选项
const linkTablesOption = ref<any>([]); // 关联表选项
const linkColumnsOption = ref<any>([]); // 关联表字段选项
const menuModalRef = ref();
const setFuncDictRef = ref();
const optionMenuTree = ref([
{
@@ -515,6 +586,20 @@
function handleUpdateMenuPid(value: string | number | Array<string | number> | null) {
formValue.value.options.menu.pid = value;
}
function handleAddMenu() {
menuModalRef.value.openModal();
}
function handleCheckboxGenFuncDict() {
if (formValue.value.options.autoOps.includes('genFuncDict')) {
setFuncDictRef.value.openModal(formValue.value.options.funcDict);
}
}
function handleUpdateFuncDict(value) {
formValue.value.options.funcDict = value;
}
</script>
<style lang="less" scoped>

View File

@@ -1,58 +1,85 @@
<template>
<n-spin :show="show" description="加载中...">
<n-card :bordered="false" class="proCard">
<n-card :bordered="false" class="proCard">
<n-spin :show="show" description="加载中...">
<BasicTable
:single-line="false"
size="small"
:striped="true"
:resizable="true"
striped
resizable
canResize
virtual-scroll
:single-line="false"
:showTopRight="false"
:pagination="false"
:columns="columns"
:dataSource="dataSource"
:openChecked="false"
:showTopRight="false"
:row-key="(row) => row.id"
ref="actionRef"
:canResize="true"
:pagination="false"
:scroll-x="3000"
:resizeHeightOffset="-20000"
:scroll-x="columnCollapse ? 1400 : 2400"
:scroll-y="720"
:scrollbar-props="{ trigger: 'none' }"
>
<template #tableTitle>
<n-tooltip placement="top-start" trigger="hover">
<template #trigger>
<n-button type="primary" @click="reloadFields(true)" class="min-left-space">
<template #icon>
<n-icon>
<Reload />
</n-icon>
</template>
重置字段
</n-button>
</template>
主要用于重置字段设置或数据库表字段发生变化时重新载入
</n-tooltip>
<n-space>
<n-popconfirm @positive-click="reloadColumns">
<template #trigger>
<n-button type="primary" class="min-left-space">
<template #icon>
<n-icon>
<Reload />
</n-icon>
</template>
重置字段
</n-button>
</template>
重置后将从数据库重新加载表,不保留当前字段配置,确定要重置吗?
</n-popconfirm>
<n-popconfirm @positive-click="syncColumns">
<template #trigger>
<n-button type="primary" class="min-left-space">
<template #icon>
<n-icon>
<Sync />
</n-icon>
</template>
同步字段
</n-button>
</template>
同步是从数据库重新加载表,保留当前有效的字段配置,确定要同步吗?
</n-popconfirm>
<n-button type="default" class="min-left-space" @click="handleMove">
<template #icon>
<n-icon>
<MoveOutline />
</n-icon>
</template>
移动字段
</n-button>
</n-space>
</template>
</BasicTable>
</n-card>
</n-spin>
</n-spin>
</n-card>
<Move ref="moveRef" v-model:columns="dataSource" />
</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 {
formatColumns,
formGridColsOptions,
formGridSpanOptions,
genInfoObj,
selectListObj,
} from '@/views/develop/code/components/model';
import { ColumnList } from '@/api/develop/code';
import { NButton, NCheckbox, NInput, NSelect, NTooltip, NTreeSelect,NCascader } from 'naive-ui';
import { HelpCircleOutline, Reload } from '@vicons/ionicons5';
import { renderIcon } from '@/utils';
import { NInputNumber, NSpace, NButton, NCheckbox, NInput, NSelect, NCascader } from 'naive-ui';
import { HelpCircleOutline, Reload, Sync, MoveOutline } from '@vicons/ionicons5';
import { cloneDeep } from 'lodash-es';
const renderTooltip = (trigger, content) => {
return h(NTooltip, null, {
trigger: () => trigger,
default: () => content,
});
};
import { renderIcon, renderTooltip } from '@/utils';
import Move from './Move.vue';
const emit = defineEmits(['update:value']);
@@ -75,93 +102,22 @@
},
});
const actionRef = ref();
const columns = ref<any>([]);
const show = ref(false);
const dataSource = ref(formValue.value.masterColumns);
async function reloadFields(loading = false) {
dataSource.value = [];
if (loading) {
show.value = true;
}
formValue.value.masterColumns = await ColumnList({
name: formValue.value.dbName,
table: formValue.value.tableName,
});
dataSource.value = formValue.value.masterColumns;
if (loading) {
show.value = false;
}
}
onMounted(async () => {
show.value = true;
if (formValue.value.masterColumns.length === 0) {
await reloadFields();
}
columns.value = [
{
title: '位置',
key: 'id',
width: 50,
},
{
title(_column) {
return renderTooltip(
h(
NButton,
{
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',
},
{ default: () => '字段', icon: renderIcon(HelpCircleOutline) }
),
'Go类型和属性定义取决于你在/hack/config.yaml中的配置参数'
);
},
key: 'field',
align: 'center',
width: 800,
children: [
const dataSource = ref([]);
const moveRef = ref();
const columnCollapse = ref(true);
const columnsCollapseData = computed(() => {
return columnCollapse.value
? [
{
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,
width: 100,
render(row) {
return h(NInput, {
value: row.dc,
@@ -171,7 +127,98 @@
});
},
},
],
]
: [
{
title: '字段列名',
key: 'name',
width: 100,
},
{
title: '物理类型',
key: 'sqlType',
width: 80,
},
{
title: 'Go属性',
key: 'goName',
width: 100,
},
{
title: 'Go类型',
key: 'goType',
width: 80,
},
{
title: 'Ts属性',
key: 'tsName',
width: 100,
},
{
title: 'Ts类型',
key: 'tsType',
width: 80,
},
{
title: '字段描述',
key: 'dc',
width: 100,
render(row) {
return h(NInput, {
value: row.dc,
onUpdateValue: function (e) {
row.dc = e;
},
});
},
},
];
});
const columns = computed(() => {
return [
{
title: '',
key: 'id',
width: 30,
render(row, index) {
return index + 1;
},
},
{
title(_column) {
return h('div', null, [
renderTooltip(
h(
NButton,
{
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',
},
{ default: () => '字段', icon: renderIcon(HelpCircleOutline) }
),
'Go类型和属性定义取决于你在/hack/config.yaml中的配置参数'
),
h(
NButton,
{
strong: true,
size: 'small',
text: true,
type: 'primary',
style: { 'margin-left': '20px' },
onClick: () => (columnCollapse.value = !columnCollapse.value),
},
{ default: () => (columnCollapse.value ? '展开 >>' : '折叠 <<') }
),
]);
},
key: 'field',
align: 'center',
width: 800,
children: columnsCollapseData.value,
},
{
width: 800,
@@ -197,21 +244,26 @@
align: 'center',
title: '编辑',
key: 'isEdit',
width: 50,
width: 30,
render(row) {
return h(NCheckbox, {
const disabled = isEditDisabled(row);
const checkbox = h(NCheckbox, {
defaultChecked: row.isEdit,
disabled: row.name === 'id',
disabled: disabled,
onUpdateChecked: function (e) {
row.isEdit = e;
},
});
if (!disabled) {
return checkbox;
}
return renderTooltip(checkbox, '该字段属性由系统维护,无需单独配置!');
},
},
{
title: '必填',
key: 'required',
width: 50,
width: 30,
align: 'center',
render(row) {
return h(NCheckbox, {
@@ -226,7 +278,7 @@
{
title: '唯一',
key: 'unique',
width: 50,
width: 30,
align: 'center',
render(row) {
return h(NCheckbox, {
@@ -241,17 +293,12 @@
{
title: '表单组件',
key: 'formMode',
width: 200,
width: 100,
render(row) {
return h(NSelect, {
consistentMenuWidth: false,
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;
},
@@ -259,11 +306,35 @@
},
},
{
title: '表单验证',
title: '绑定字典',
key: 'dictType',
width: 100,
render(row) {
if (row.dictType == 0) {
row.dictType = null;
}
return h(NCascader, {
placeholder: ' ',
filterable: true,
clearable: true,
showPath: false,
checkStrategy: 'child',
disabled: row.name === 'id',
value: row.dictType,
options: props.selectList?.dictMode ?? [],
onUpdateValue: function (e) {
row.dictType = e;
},
});
},
},
{
title: '验证规则',
key: 'formRole',
width: 200,
width: 100,
render(row) {
return h(NSelect, {
consistentMenuWidth: false,
value: row.formRole,
disabled: row.name === 'id',
options: props.selectList?.formRole ?? [],
@@ -274,22 +345,43 @@
},
},
{
title: '字典类型',
key: 'dictType',
width: 300,
title(_column) {
return h(NSpace, { inline: true }, [
renderTooltip(
h(
NButton,
{
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',
},
{ default: () => '栅格', icon: renderIcon(HelpCircleOutline) }
),
'表单每行摆放组件的个数。响应式栅格小屏幕自动转为每行摆放一个组件。参考文档https://www.naiveui.com/zh-CN/os-theme/components/grid#responsive-item.vue'
),
h(NSelect, {
style: { width: '100px' },
size: 'small',
consistentMenuWidth: false,
value: formValue.value.options.presetStep.formGridCols,
options: formGridColsOptions,
onUpdateValue: function (e) {
formValue.value.options.presetStep.formGridCols = e;
},
}),
]);
},
key: 'formGridSpan',
width: 120,
render(row) {
if (row.dictType == 0){
row.dictType = null;
}
return h(NCascader, {
placeholder: '请选择字典类型',
filterable: true,
clearable: true,
return h(NSelect, {
consistentMenuWidth: false,
disabled: row.name === 'id',
value: row.dictType,
options: props.selectList?.dictMode ?? [],
value: row.formGridSpan,
options: getFormGridSpanOptions(formValue.value.options.presetStep.formGridCols),
onUpdateValue: function (e) {
row.dictType = e;
row.formGridSpan = e;
},
});
},
@@ -305,7 +397,7 @@
{
title: '列表',
key: 'isList',
width: 50,
width: 30,
align: 'center',
render(row) {
return h(NCheckbox, {
@@ -319,7 +411,7 @@
{
title: '导出',
key: 'isExport',
width: 50,
width: 30,
align: 'center',
render(row) {
return h(NCheckbox, {
@@ -333,7 +425,7 @@
{
title: '查询',
key: 'isQuery',
width: 50,
width: 30,
align: 'center',
render(row) {
return h(NCheckbox, {
@@ -347,9 +439,10 @@
{
title: '查询条件',
key: 'queryWhere',
width: 300,
width: 90,
render(row) {
return h(NSelect, {
consistentMenuWidth: false,
value: row.queryWhere,
disabled: row.name === 'id',
options: props.selectList?.whereMode ?? [],
@@ -359,13 +452,108 @@
});
},
},
{
title: '排列方式',
key: 'align',
width: 80,
render(row) {
return h(NSelect, {
consistentMenuWidth: false,
value: row.align,
options: props.selectList?.tableAlign ?? [],
onUpdateValue: function (e) {
row.align = e;
},
});
},
},
{
title(_column) {
return renderTooltip(
h(
NButton,
{
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',
},
{ default: () => '列宽', icon: renderIcon(HelpCircleOutline) }
),
'选填。设定固定值时表格生成自动计算scroll-x未设定默认每列按100计算'
);
},
key: 'width',
width: 50,
render(row) {
return h(NInputNumber, {
value: row.width,
placeholder: ' ',
min: -1,
max: 2000,
showButton: false,
onUpdateValue: function (e) {
row.width = e;
},
});
},
},
],
},
];
show.value = false;
});
// 同步字段
function syncColumns() {
show.value = true;
dataSource.value = [];
const params = {
name: formValue.value.dbName,
table: formValue.value.tableName,
};
ColumnList(params)
.then((res) => {
const columns = formatColumns(res);
for (let i = 0; i < columns.length; i++) {
// 相同字段名称和类型,保留原字段属性
const index = formValue.value.masterColumns.findIndex(
(item) => item.name == columns[i].name && item.dataType == columns[i].dataType
);
if (index !== -1) {
columns[i] = formValue.value.masterColumns[index];
}
}
formValue.value.masterColumns = columns;
dataSource.value = formValue.value.masterColumns;
})
.finally(() => {
show.value = false;
});
}
// 重载字段属性
function reloadColumns() {
show.value = true;
dataSource.value = [];
const params = {
name: formValue.value.dbName,
table: formValue.value.tableName,
};
ColumnList(params)
.then((res) => {
formValue.value.masterColumns = formatColumns(res);
dataSource.value = formValue.value.masterColumns;
})
.finally(() => {
show.value = false;
});
}
function getFormModeOptions(type: string) {
const options = cloneDeep(props.selectList?.formMode ?? []);
if (options.length === 0) {
@@ -374,7 +562,16 @@
switch (type) {
case 'number':
for (let i = 0; i < options.length; i++) {
const allows = ['InputNumber', 'Radio', 'Select', 'Switch', 'Rate'];
const allows = [
'Input',
'InputNumber',
'Radio',
'Select',
'Switch',
'Rate',
'TreeSelect',
'Cascader',
];
if (!allows.includes(options[i].value)) {
options[i].disabled = true;
}
@@ -382,8 +579,73 @@
break;
default:
}
options.sort((a, b) => (a.disabled === b.disabled ? 0 : a.disabled ? 1 : -1));
return options;
}
function getFormGridSpanOptions(cols: number) {
if (cols < 1) {
cols = 1;
}
if (cols > 4) {
cols = 4;
}
for (let i = 0; i < formValue.value.masterColumns.length; i++) {
if (!formValue.value.masterColumns[i].formGridSpan) {
formValue.value.masterColumns[i].formGridSpan = 1;
}
if (formValue.value.masterColumns[i].formGridSpan > cols) {
formValue.value.masterColumns[i].formGridSpan = cols;
}
}
return formGridSpanOptions.slice(0, Math.min(cols, formGridSpanOptions.length));
}
// 禁止编辑的字段,由系统维护
function isEditDisabled(row) {
const disabledNames = [
'id',
'created_by',
'updated_by',
'deleted_by',
'created_at',
'updated_at',
'deleted_at',
];
if (disabledNames.includes(row.name)) {
return true;
}
if (formValue.value.genType == 11) {
const disabledTreeNames = ['pid', 'level', 'tree'];
if (disabledTreeNames.includes(row.name)) {
return true;
}
}
return false;
}
function handleMove() {
moveRef.value.openModal();
}
onMounted(() => {
if (formValue.value.masterColumns.length === 0) {
reloadColumns();
} else {
show.value = true;
setTimeout(function () {
dataSource.value = formValue.value.masterColumns;
show.value = false;
}, 100);
}
});
</script>
<style lang="less" scoped></style>
<style lang="less" scoped>
.tree-tips {
margin-left: 12px;
color: #18a058;
font-weight: 600;
}
</style>

View File

@@ -2,43 +2,85 @@
<n-spin :show="show" description="加载中...">
<n-card :bordered="false" class="proCard">
<BasicTable
:single-line="false"
size="small"
:striped="true"
:resizable="true"
striped
resizable
canResize
:single-line="false"
:showTopRight="false"
:pagination="false"
:columns="columns"
:dataSource="dataSource"
:openChecked="false"
:showTopRight="false"
:row-key="(row) => row.id"
ref="actionRef"
:canResize="true"
:resizeHeightOffset="-20000"
:pagination="false"
:scroll-x="1090"
:scroll-x="columnCollapse ? 880 : 1880"
:scroll-y="720"
:scrollbar-props="{ trigger: 'none' }"
/>
>
<template #tableTitle>
<n-space>
<n-popconfirm @positive-click="reloadColumns(getIndex())">
<template #trigger>
<n-button type="primary" class="min-left-space">
<template #icon>
<n-icon>
<Reload />
</n-icon>
</template>
重置字段
</n-button>
</template>
重置后将从数据库重新加载该表的默认配置,确定要重置吗?
</n-popconfirm>
<n-popconfirm @positive-click="syncColumns(getIndex())">
<template #trigger>
<n-button type="primary" class="min-left-space">
<template #icon>
<n-icon>
<Sync />
</n-icon>
</template>
同步字段
</n-button>
</template>
同步是从数据库重新加载表,保留当前有效的字段配置,确定要同步吗?
</n-popconfirm>
<n-button type="default" class="min-left-space" @click="handleMove">
<template #icon>
<n-icon>
<MoveOutline />
</n-icon>
</template>
移动字段
</n-button>
</n-space>
</template>
</BasicTable>
</n-card>
<Move ref="moveRef" v-model:columns="dataSource" />
</n-spin>
</template>
<script lang="ts" setup>
import { Component, computed, h, onMounted, ref } from 'vue';
import { computed, h, onMounted, ref } from 'vue';
import { BasicTable } from '@/components/Table';
import { genInfoObj, selectListObj } from '@/views/develop/code/components/model';
import { formatColumns, 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) });
}
import {
NButton,
NCheckbox,
NIcon,
NInput,
NInputNumber,
NSelect,
NSpace,
NTooltip,
} from 'naive-ui';
import { HelpCircleOutline, MoveOutline, Reload, Sync, WarningOutline } from '@vicons/ionicons5';
import { renderIcon, renderTooltip } from '@/utils';
import Move from './Move.vue';
const emit = defineEmits(['update:value']);
@@ -54,8 +96,6 @@
uuid: '',
});
const columns = ref<any>([]);
const formValue = computed({
get() {
return props.value;
@@ -65,6 +105,243 @@
},
});
const show = ref(false);
const dataSource = ref([]);
const moveRef = ref();
const columnCollapse = ref(true);
const columnsCollapseData = computed(() => {
return columnCollapse.value
? [
{
title: '字段列名',
key: 'name',
width: 150,
},
{
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);
},
});
},
},
]
: [
{
title: '字段列名',
key: 'name',
width: 100,
},
{
title: '物理类型',
key: 'sqlType',
width: 80,
},
{
title: 'Go属性',
key: 'goName',
width: 150,
},
{
title: 'Go类型',
key: 'goType',
width: 100,
},
{
title: 'Ts属性',
key: 'tsName',
width: 150,
},
{
title: 'Ts类型',
key: 'tsType',
width: 100,
},
{
title: '字段描述',
key: 'dc',
width: 100,
render(row) {
return h(NInput, {
value: row.dc,
onUpdateValue: function (e) {
row.dc = e;
},
});
},
},
];
});
const columns = computed(() => {
return [
{
title: '',
key: 'id',
width: 50,
render(row, index) {
return index + 1;
},
},
{
title(_column) {
return h('div', null, [
renderTooltip(
h(
NButton,
{
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',
},
{ default: () => '字段', icon: renderIcon(HelpCircleOutline) }
),
'Go类型和属性定义取决于你在/hack/config.yaml中的配置参数'
),
h(
NButton,
{
strong: true,
size: 'small',
text: true,
type: 'primary',
style: { 'margin-left': '20px' },
onClick: () => (columnCollapse.value = !columnCollapse.value),
},
{ default: () => (columnCollapse.value ? '展开 >>' : '折叠 <<') }
),
]);
},
key: 'field',
align: 'center',
width: 800,
children: columnsCollapseData.value,
},
{
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: 100,
render(row) {
return h(NSelect, {
consistentMenuWidth: false,
value: row.queryWhere,
disabled: row.name === 'id',
options: props.selectList?.whereMode ?? [],
onUpdateValue: function (e) {
row.queryWhere = e;
},
});
},
},
{
title: '排列方式',
key: 'align',
width: 100,
render(row) {
return h(NSelect, {
consistentMenuWidth: false,
value: row.align,
options: props.selectList?.tableAlign ?? [],
onUpdateValue: function (e) {
row.align = e;
},
});
},
},
{
title(_column) {
return renderTooltip(
h(
NButton,
{
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',
},
{ default: () => '列宽', icon: renderIcon(HelpCircleOutline) }
),
'选填。设定固定值时表格生成自动计算scroll-x未设定默认每列按100计算'
);
},
key: 'width',
width: 50,
render(row) {
return h(NInputNumber, {
value: row.width,
placeholder: ' ',
min: 0,
max: 2000,
showButton: false,
onUpdateValue: function (e) {
row.width = e;
},
});
},
},
],
},
];
});
function handleMove() {
moveRef.value.openModal();
}
function getIndex() {
if (formValue.value.options.join.length === 0) {
return -1;
@@ -77,166 +354,77 @@
return -1;
}
const show = ref(false);
const dataSource = ref([]);
onMounted(async () => {
// 同步字段属性
function syncColumns(index: number) {
show.value = true;
setTimeout(async () => {
dataSource.value = [];
const join = formValue.value.options.join[index];
const params = {
name: formValue.value.dbName,
table: join.linkTable,
isLink: 1,
alias: join.alias,
};
ColumnList(params)
.then((res) => {
const columns = formatColumns(res);
for (let i = 0; i < columns.length; i++) {
// 相同字段名称和类型,保留原字段属性
const index2 = join.columns.findIndex(
(item) => item.name == columns[i].name && item.dataType == columns[i].dataType
);
if (index2 !== -1) {
columns[i] = join.columns[index2];
}
}
join.columns = columns;
dataSource.value = join.columns;
})
.finally(() => {
show.value = false;
});
}
// 重载字段属性
function reloadColumns(index: number) {
show.value = true;
dataSource.value = [];
const join = formValue.value.options.join[index];
const params = {
name: formValue.value.dbName,
table: join.linkTable,
isLink: 1,
alias: join.alias,
};
ColumnList(params)
.then((res) => {
join.columns = formatColumns(res);
dataSource.value = join.columns;
})
.finally(() => {
show.value = false;
});
}
onMounted(() => {
show.value = true;
setTimeout(() => {
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,
});
// 已存在直接加载
if (formValue.value.options.join[index].columns.length > 0) {
dataSource.value = formValue.value.options.join[index].columns;
show.value = false;
return;
}
dataSource.value = formValue.value.options.join[index].columns;
columns.value = [
{
title: '位置',
key: 'id',
width: 50,
},
{
title(_column) {
return renderTooltip(
h(
NButton,
{
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);
reloadColumns(index);
}, 100);
});
const actionRef = ref();
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,82 @@
<template>
<div>
<n-modal
v-model:show="showModal"
:mask-closable="false"
:show-icon="false"
preset="dialog"
transform-origin="center"
title="移动字段"
:style="{
width: dialogWidth,
}"
>
<n-scrollbar style="max-height: 87vh" class="pr-5">
<n-card
:bordered="false"
:content-style="{ padding: '0px' }"
:header-style="{ padding: 'px' }"
:segmented="true"
>
请通过拖拽来移动字段的位置
<div class="mt-8"></div>
<Draggable
class="draggable-ul"
animation="300"
:list="columns"
group="people"
itemKey="name"
>
<template #item="{ element }">
<div class="cursor-move draggable-li">
<n-tag type="default" size="small" style="font-weight: 800">{{
element.name
}}</n-tag
><span class="ml-2">{{ element.dc }}</span>
</div>
</template>
</Draggable>
</n-card>
</n-scrollbar>
</n-modal>
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import Draggable from 'vuedraggable';
import { adaModalWidth } from '@/utils/hotgo';
const showModal = ref(false);
const columns = defineModel<[]>('columns');
const dialogWidth = computed(() => {
return adaModalWidth(360);
});
function openModal() {
showModal.value = true;
}
defineExpose({
openModal,
});
</script>
<style lang="less" scoped>
.draggable-ul {
width: 100%;
overflow: hidden;
margin-top: -8px;
.draggable-li {
width: 100%;
padding: 8px 4px;
color: #333;
border-bottom: 1px solid #efeff5;
}
.draggable-li:hover {
background-color: rgba(229, 231, 235, var(--tw-border-opacity));
}
}
</style>

View File

@@ -1,16 +1,28 @@
<template>
<div>
<textarea id="copy-code" :value="content"></textarea>
<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-space justify="space-between">
<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-button type="primary" size="small" class="tag-margin" @click="handleCopy(view.content)"
>复制本页代码</n-button
>
</n-space>
<n-scrollbar class="code-scrollbar" trigger="none">
<n-code :code="view.content" :hljs="hljs" language="goLang" show-line-numbers />
<n-code
:class="'code-' + getFileExtension(view.path)"
:code="view.content"
:hljs="hljs"
:language="getFileExtension(view.path)"
show-line-numbers
/>
</n-scrollbar>
</n-tab-pane>
</n-tabs>
@@ -18,9 +30,12 @@
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { computed, ref } from 'vue';
import hljs from 'highlight.js/lib/core';
import goLang from 'highlight.js/lib/languages/go';
import go from 'highlight.js/lib/languages/go';
import typescript from 'highlight.js/lib/languages/typescript';
import xml from 'highlight.js/lib/languages/xml';
import sql from 'highlight.js/lib/languages/sql';
import { cloneDeep } from 'lodash-es';
import {
CheckmarkCircle,
@@ -29,8 +44,12 @@
HelpCircleOutline,
RemoveCircleOutline,
} from '@vicons/ionicons5';
import { useMessage } from 'naive-ui';
hljs.registerLanguage('goLang', goLang);
hljs.registerLanguage('go', go);
hljs.registerLanguage('ts', typescript);
hljs.registerLanguage('sql', sql);
hljs.registerLanguage('vue', xml);
interface Props {
previewModel: any;
@@ -41,7 +60,8 @@
previewModel: cloneDeep({ views: {} }),
showModal: false,
});
const message = useMessage();
const content = ref('');
const views = computed(() => {
let tmpViews: any = [];
let i = 0;
@@ -69,12 +89,31 @@
}
return tmpViews;
});
function getFileExtension(path: string): string {
const parts = path.split('.');
if (parts.length > 1) {
return parts[parts.length - 1];
}
return '';
}
function handleCopy(code: string) {
content.value = code;
setTimeout(function () {
const copyVal = document.getElementById('copy-code');
copyVal.select();
document.execCommand('copy');
message.success('已复制');
}, 20);
}
</script>
<style lang="less" scoped>
::v-deep(.alert-margin) {
margin-bottom: 20px;
}
::v-deep(.tag-margin) {
margin-bottom: 10px;
}
@@ -85,4 +124,38 @@
color: #e0e2e4;
padding: 10px;
}
::v-deep(.code-vue .hljs-tag) {
color: rgb(242, 197, 92);
}
::v-deep(.code-vue .hljs-name) {
color: rgb(242, 197, 92);
}
::v-deep(.code-vue .hljs-attr) {
color: rgb(49, 104, 213);
}
::v-deep(.code-go .hljs-params) {
color: rgb(49, 104, 213);
}
::v-deep(.code-ts .hljs-params) {
color: rgb(49, 104, 213);
}
::v-deep(.code-ts .hljs-property) {
color: rgb(49, 104, 213);
}
::v-deep(.code-ts .hljs-function) {
color: rgb(49, 104, 213);
}
#copy-code {
position: fixed;
top: -100px;
left: -100px;
}
</style>

View File

@@ -0,0 +1,133 @@
<template>
<div>
<n-modal
title="设置选项字段"
v-model:show="showModal"
:block-scroll="false"
:mask-closable="false"
:show-icon="false"
preset="dialog"
>
<n-form
:model="formValue"
:rules="rules"
ref="formRef"
label-placement="left"
:label-width="100"
class="py-4"
>
<n-form-item label="选项值" path="valueColumn">
<n-select
filterable
tag
placeholder="请选择"
:options="columnsOption"
v-model:value="formValue.valueColumn"
/>
</n-form-item>
<n-form-item label="选项名称" path="labelColumn">
<n-select
filterable
tag
placeholder="请选择"
:options="columnsOption"
v-model:value="formValue.labelColumn"
/>
</n-form-item>
</n-form>
<template #action>
<n-space>
<n-button @click="closeForm">取消</n-button>
<n-button type="info" @click="confirmForm">保存</n-button>
</n-space>
</template>
</n-modal>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { rules } from '@/views/addons/stock/itemBrand/model';
import { cloneDeep } from 'lodash-es';
import { useMessage } from 'naive-ui';
import { Edit } from '@/api/addons/stock/itemClass';
interface Props {
columnsOption: any;
}
const props = withDefaults(defineProps<Props>(), {
columnsOption: [],
});
const rules = {
valueColumn: {
required: true,
trigger: ['blur', 'input'],
type: 'string',
message: '选项值不能为空',
},
labelColumn: {
required: true,
trigger: ['blur', 'input'],
type: 'string',
message: '选项名称不能为空',
},
};
const message = useMessage();
const emit = defineEmits(['update']);
const showModal = ref(false);
const formRef = ref();
const formValue = ref({
valueColumn: null,
labelColumn: null,
});
function openModal(state) {
showModal.value = true;
if (!state) {
state = {
valueColumn: null,
labelColumn: null,
};
}
if (!state.valueColumn) {
const item = props.columnsOption.find((item) => item.value === 'id');
if (item) {
state.valueColumn = item.value;
}
}
if (!state.labelColumn) {
const item = props.columnsOption.find((item) => item.value === 'title' || item.value === 'name');
if (item) {
state.labelColumn = item.value;
}
}
formValue.value = cloneDeep(state);
emit('update', formValue.value);
}
function closeForm() {
showModal.value = false;
}
function confirmForm(e) {
e.preventDefault();
formRef.value.validate((errors) => {
if (!errors) {
emit('update', formValue.value);
closeForm();
} else {
message.error('请填写完整信息');
}
});
}
defineExpose({
openModal,
});
</script>

View File

@@ -1,4 +1,5 @@
import { cloneDeep } from 'lodash-es';
import { isJsonString } from '@/utils/is';
export const genFileObj = {
meth: 1,
@@ -24,7 +25,7 @@ export const genInfoObj = {
varName: '',
options: {
headOps: ['add', 'batchDel', 'export'],
columnOps: ['edit', 'del', 'view', 'status', 'switch', 'check'],
columnOps: ['edit', 'del', 'view', 'status', 'check'],
autoOps: ['genMenuPermissions', 'runDao', 'runService'],
join: [],
menu: {
@@ -32,6 +33,17 @@ export const genInfoObj = {
icon: 'MenuOutlined',
sort: 0,
},
tree: {
titleColumn: null,
styleType: 1,
},
funcDict: {
valueColumn: null,
labelColumn: null,
},
presetStep: {
formGridCols: 1,
},
},
dbName: '',
tableName: '',
@@ -54,6 +66,8 @@ export const selectListObj = {
dictMode: [],
whereMode: [],
buildMeth: [],
tableAlign: [],
treeStyleType: [],
};
export function newState(state) {
@@ -62,3 +76,67 @@ export function newState(state) {
}
return cloneDeep(genInfoObj);
}
export const formGridColsOptions = [
{
value: 1,
label: '一行一列',
},
{
value: 2,
label: '一行两列',
},
{
value: 3,
label: '一行三列',
},
{
value: 4,
label: '一行四列',
},
];
export const formGridSpanOptions = [
{
value: 1,
label: '占一列位置',
},
{
value: 2,
label: '占两列位置',
},
{
value: 3,
label: '占三列位置',
},
{
value: 4,
label: '占四列位置',
},
];
// 格式化列字段
export function formatColumns(columns: any) {
if (columns === undefined || columns.length === 0) {
columns = [];
}
if (isJsonString(columns)) {
columns = JSON.parse(columns);
}
if (columns.length > 0) {
for (let i = 0; i < columns.length; i++) {
if (!columns[i].formGridSpan) {
columns[i].formGridSpan = 1;
}
if (!columns[i].align) {
columns[i].align = 'left';
}
if (!columns[i].width || columns[i].width < 1) {
columns[i].width = null;
}
}
}
return columns;
}

View File

@@ -40,7 +40,10 @@
<template #suffix>
<n-space>
<n-button type="primary" @click="preview">预览代码</n-button>
<n-button type="default" @click="handleBack">返回列表</n-button>
<n-button type="primary" :loading="formBtnPreviewLoading" @click="preview"
>预览代码</n-button
>
<n-button type="success" :loading="formBtnLoading" @click="submitBuild"
>提交生成</n-button
>
@@ -87,7 +90,7 @@
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 { selectListObj, newState, formatColumns } from '@/views/develop/code/components/model';
import PreviewTab from '@/views/develop/code/components/PreviewTab.vue';
import { isJsonString } from '@/utils/is';
@@ -108,6 +111,7 @@
const slavePanels = ref<any>([]);
const showModal = ref(false);
const formBtnLoading = ref(false);
const formBtnPreviewLoading = ref(false);
const previewModel = ref<any>();
const dialog = useDialog();
const notification = useNotification();
@@ -123,15 +127,27 @@
async function getGenInfo() {
let tmp = await View({ id: genId });
// 导入主表数据
tmp.masterColumns = formatColumns(tmp.masterColumns);
// 导入生成选项
if (isJsonString(tmp.options)) {
tmp.options = JSON.parse(tmp.options);
}
if (tmp.masterColumns === undefined || tmp.masterColumns.length === 0) {
tmp.masterColumns = [];
// 预设流程
if (!tmp.options.presetStep) {
tmp.options.presetStep = {
formGridCols: 1,
};
}
if (isJsonString(tmp.masterColumns)) {
tmp.masterColumns = JSON.parse(tmp.masterColumns);
// 树表
if (!tmp.options.tree) {
tmp.options.tree = {
titleColumn: null,
styleType: 1,
};
}
genInfo.value = tmp;
@@ -146,12 +162,12 @@
handleClose('主表字段');
}
if (newVal.options.join !== undefined) {
if (newVal && newVal.options && 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 !== '') {
for (let i = 0; i < newVal.options.join.length; i++) {
if (newVal.options.join[i]?.alias) {
handleSlaveAdd(
'关联表[ ' + newVal.options.join[i]?.alias + ' ]',
'关联表[ ' + newVal.options.join[i].alias + ' ]',
newVal.options.join[i]
);
}
@@ -195,14 +211,21 @@
selectList.value = await Selects({});
};
async function preview() {
previewModel.value = await Preview(genInfo.value);
showModal.value = true;
function preview() {
formBtnPreviewLoading.value = true;
Preview(genInfo.value)
.then((res) => {
previewModel.value = res;
showModal.value = true;
})
.finally(() => {
formBtnPreviewLoading.value = false;
});
}
function submitBuild() {
dialog.warning({
title: '警告',
dialog.info({
title: '提示',
content: '你确定要提交生成吗?',
positiveText: '确定',
negativeText: '取消',
@@ -216,15 +239,12 @@
formBtnLoading.value = false;
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function submitSave() {
dialog.warning({
title: '警告',
dialog.info({
title: '提示',
content: '你确定要保存生成配置吗?',
positiveText: '确定',
negativeText: '取消',
@@ -233,18 +253,15 @@
message.success('操作成功');
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function buildSuccessNotify() {
let count = 6;
let count = 10;
const n = notification.success({
title: '生成提交成功',
content: `如果你使用的热编译,页面将在 ${count} 秒后自动刷新即可生效。否则请手动重启服务后刷新页面!`,
duration: 6000,
duration: 10000,
closable: false,
onAfterEnter: () => {
const minusCount = () => {
@@ -261,6 +278,18 @@
},
});
}
function handleBack() {
dialog.info({
title: '提示',
content: '你确定要返回生成列表?系统不会主动保存更改',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
router.push({ name: 'develop_code' });
},
});
}
</script>
<style lang="less" scoped>
::v-deep(.alert-margin) {

View File

@@ -20,6 +20,7 @@
:actionColumn="actionColumn"
@update:checked-row-keys="onCheckedRow"
:scroll-x="1090"
:resizeHeightOffset="-10000"
>
<template #tableTitle>
<n-button type="primary" @click="addTable">
@@ -30,8 +31,7 @@
</template>
立即生成
</n-button>
&nbsp;
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled" class="min-left-space">
<template #icon>
<n-icon>
<DeleteOutlined />
@@ -44,6 +44,7 @@
<n-modal
v-model:show="showModal"
:mask-closable="false"
:show-icon="false"
preset="dialog"
title="立即生成"
@@ -302,10 +303,10 @@
};
const actionColumn = reactive({
width: 220,
width: 180,
title: '操作',
key: 'action',
// fixed: 'right',
fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',