im 管理后台

This commit is contained in:
richjohnrambo
2026-08-11 23:13:51 +08:00
parent 0397fe5f22
commit 331a55751d
1407 changed files with 162069 additions and 2 deletions
+326
View File
@@ -0,0 +1,326 @@
<template>
<div>
<div class="n-layout-page-header">
<n-card :bordered="false" title="插件管理">
在这里可以管理和创建插件模板,开发独立、临时性、工具类型的功能时推荐使用插件化开发。
</n-card>
</div>
<n-card :bordered="false" class="proCard">
<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>
</template>
</BasicTable>
<n-modal
v-model:show="showModal"
:mask-closable="false"
:show-icon="false"
preset="dialog"
title="创建新插件"
:style="{
width: dialogWidth,
}"
>
<n-alert :show-icon="false" type="info">
注意:插件创建成功后会在服务端对应的项目目录中生成插件模块文件,并自动注册到当前项目中。
</n-alert>
<n-form
:model="formParams"
ref="formRef"
label-placement="left"
:label-width="80"
class="py-4"
>
<n-form-item label="插件标签" path="label">
<n-input placeholder="请输入" v-model:value="formParams.label" />
<template #feedback>显示在插件列表中的模块名称. 不要超过20个字符</template>
</n-form-item>
<n-form-item label="插件包名" path="name">
<n-input placeholder="请输入" v-model:value="formParams.name" />
<template #feedback>
系统按照此名称创建和查找插件定义, 只能英文和下划线组成,建议全部小写,例如:hgexample
</template>
</n-form-item>
<n-form-item label="功能分组" path="group">
<n-select
placeholder="请选择"
:options="dict.getOptionUnRef('addonsGroupOptions')"
v-model:value="formParams.group"
:on-update:value="onUpdateValueGroup"
/>
</n-form-item>
<n-form-item label="扩展功能" path="extend">
<n-checkbox-group v-model:value="formParams.extend">
<n-space item-style="display: flex;">
<n-checkbox
:value="option.value"
:label="option.label"
v-for="option in dict.getOptionUnRef('addonsExtend')"
:key="option"
/>
</n-space>
</n-checkbox-group>
</n-form-item>
<n-form-item label="版本" path="version">
<n-input placeholder="请输入" v-model:value="formParams.version" />
<template #feedback>此版本号用于插件的版本更新</template>
</n-form-item>
<n-form-item label="简介" path="brief">
<n-input placeholder="请输入" v-model:value="formParams.brief" />
</n-form-item>
<n-form-item label="使用说明" path="description">
<n-input
type="textarea"
placeholder="使用说明"
v-model:value="formParams.description"
/>
<template #feedback>详细介绍插件的功能和使用方法</template>
</n-form-item>
<n-form-item label="作者" path="author">
<n-input placeholder="请输入" v-model:value="formParams.author" />
</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>
</div>
</template>
<script lang="ts" setup>
import { computed, h, onMounted, 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 } from '@vicons/antd';
import { newState, schemas, columns, loadOptions } from './model';
import { adaModalWidth } from '@/utils/hotgo';
import { useDictStore } from '@/store/modules/dict';
const dict = useDictStore();
const dialog = useDialog();
const message = useMessage();
const notification = useNotification();
const showModal = ref(false);
const formBtnLoading = ref(false);
const formRef: any = ref(null);
const actionRef = ref();
const formParams = ref<any>();
const checkedIds = ref([]);
const searchFormRef = ref<any>();
const dialogWidth = computed(() => {
return adaModalWidth();
});
const actionColumn = reactive({
width: 220,
title: '操作',
key: 'action',
fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '安装模块',
onClick: handleInstall.bind(null, record),
ifShow: () => {
return record.installStatus !== 1;
},
},
{
type: 'warning',
label: '更新配置',
onClick: handleUpgrade.bind(null, record),
ifShow: () => {
return record.installStatus === 1;
},
},
{
type: 'error',
label: '卸载模块',
onClick: handleUnInstall.bind(null, record),
ifShow: () => {
return record.installStatus === 1;
},
},
],
});
},
});
const [register, {}] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 80,
schemas,
});
function onCheckedRow(rowKeys) {
checkedIds.value = rowKeys;
}
const loadDataTable = async (res) => {
return await List({ ...res, ...searchFormRef.value?.formModel });
};
function reloadTable() {
actionRef.value.reload();
}
function handleInstall(record: Recordable) {
dialog.info({
title: '提示',
content: '你确定要安装 【' + record.label + '】 模块?安装成功后需要重启服务才能生效!',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Install(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
});
}
function handleUpgrade(record: Recordable) {
dialog.warning({
title: '提示',
content: '你确定要更新 【' + record.label + '】 模块?更新成功后需要重启服务才能生效!',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Upgrade(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
});
}
function handleUnInstall(record: Recordable) {
dialog.error({
title: '提示',
content: '你确定要卸载 【' + record.label + '】 模块?卸载成功后需要重启服务才能生效!',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
UnInstall(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
});
}
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) {
dialog.info({
title: '提示',
content:
'你确定要提交生成吗?热编译生成后如果列表自动刷新没有出现新插件请等几秒刷新即可。否则请手动重启服务后刷新页面!',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Build(formParams.value).then((_res) => {
showModal.value = false;
buildSuccessNotify();
});
},
});
} else {
message.error('请填写完整信息');
}
formBtnLoading.value = false;
});
}
function onUpdateValueGroup(value) {
formParams.value.group = value;
}
function buildSuccessNotify() {
let count = 10;
const n = notification.success({
title: '插件构建成功',
content: `如果你使用的热编译,页面将在 ${count} 秒后自动刷新插件列表即可生效。否则请手动重启服务后刷新页面!`,
duration: 10000,
closable: false,
onAfterEnter: () => {
const minusCount = () => {
count--;
n.content = `如果你使用的热编译,页面将在 ${count} 秒后自动刷新插件列表即可生效。否则请手动重启服务后刷新页面!`;
if (count > 0) {
window.setTimeout(minusCount, 1000);
}
};
window.setTimeout(minusCount, 1000);
},
onAfterLeave: () => {
location.reload();
},
});
}
onMounted(async () => {
loadOptions();
});
</script>
<style lang="less" scoped></style>
+179
View File
@@ -0,0 +1,179 @@
import { cloneDeep } from 'lodash-es';
import { h, ref } from 'vue';
import { fallbackSrc } from '@/utils/hotgo';
import { isUrl } from '@/utils/is';
import { NIcon, NIconWrapper, NImage, NTag } from 'naive-ui';
import { getIconComponent } from '@/utils/icons';
import { FormSchema } from '@/components/Form';
import { useDictStore } from '@/store/modules/dict';
const dict = useDictStore();
export const genInfoObj = {
label: '',
name: '',
group: 1,
extend: ['resourcePublic', 'resourceTemplate'],
version: 'v1.0.0',
brief: '',
description: '',
author: '',
};
export function newState(state) {
if (state !== null) {
return cloneDeep(state);
}
return cloneDeep(genInfoObj);
}
export const columns = [
{
title: '图标',
key: 'logo',
width: 80,
render(row) {
if (isUrl(row.logo)) {
return h(NImage, {
width: 48,
height: 48,
src: row.logo,
fallbackSrc: fallbackSrc(),
style: {
width: '48px',
height: '48px',
'max-width': '100%',
'max-height': '100%',
},
});
} else {
return h(
NIconWrapper,
{
size: 48,
borderRadius: 8,
},
{
default: () =>
h(
NIcon,
{
size: 36,
style: {
marginTop: '-8px',
},
},
{
default: () => h(getIconComponent(row.logo)),
}
),
}
);
}
},
},
{
title: '模块名称',
key: 'name',
width: 120,
render(row) {
return h('div', {
innerHTML:
'<div >' + row.label + '<br><span style="opacity: 0.8;">' + row.name + '</span></div>',
});
},
},
{
title: '作者',
key: 'author',
width: 100,
},
{
title: '分组',
key: 'groupName',
render(row) {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: 'info',
bordered: false,
},
{
default: () => row.groupName,
}
);
},
width: 120,
},
{
title: '简介',
key: 'brief',
render(row) {
return row.brief;
},
width: 180,
},
{
title: '详细描述',
key: 'description',
width: 300,
render(row) {
return h('p', { id: 'app' }, [
h('div', {
innerHTML: '<div style="white-space: pre-wrap">' + row.description + '</div>',
}),
]);
},
},
{
title: '版本',
key: 'version',
width: 100,
},
];
export const schemas = ref<FormSchema[]>([
{
field: 'name',
component: 'NInput',
label: '模块名称',
componentProps: {
placeholder: '请输入模块名称或标签',
onInput: (e: any) => {
console.log(e);
},
},
rules: [{ trigger: ['blur'] }],
},
{
field: 'group',
component: 'NSelect',
label: '分组',
componentProps: {
placeholder: '请选择分组',
options: dict.getOption('addonsGroupOptions'),
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'status',
component: 'NSelect',
label: '安装状态',
componentProps: {
placeholder: '请选择状态',
options: dict.getOption('addonsInstallStatus'),
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
]);
export function loadOptions() {
dict.loadOptions(['addonsGroupOptions', 'addonsInstallStatus', 'addonsExtend']);
}
@@ -0,0 +1,614 @@
<template>
<div>
<n-spin :show="bodyShow" description="请稍候...">
<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>
<!-- 树表-->
<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="表格头部按钮组"
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="notFilterAuth" 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-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>如果你选择的表已经生成过dao相关代码,取消勾选可减少生成时间</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>如果是插件模块,勾选后也会自动在对应插件下运行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>
<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 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"
@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="primary"
@click="addJoin"
:disabled="formValue.options?.join?.length >= 20"
>新增关联表</n-button
>
</n-space>
</template>
<n-form ref="formRef" :model="formValue">
<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">
<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>
</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, useDialog, useMessage } 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';
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);
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 menuModalRef = ref();
const setFuncDictRef = ref();
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(() => {
timer.value = setInterval(async () => {
if (props.value.id > 0) {
clearTimeout(timer.value);
await instLoad();
// 切换tab时会导致选项被清空,这里重新进行加载
await loadLinkColumnsOption();
await loadMenuTreeOption();
bodyShow.value = false;
}
}, 30);
});
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;
}
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>
::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>
@@ -0,0 +1,651 @@
<template>
<n-card :bordered="false" class="proCard">
<n-spin :show="show" description="加载中...">
<BasicTable
size="small"
striped
resizable
canResize
virtual-scroll
:single-line="false"
:showTopRight="false"
:pagination="false"
:columns="columns"
:dataSource="dataSource"
:row-key="(row) => row.id"
:resizeHeightOffset="-20000"
:scroll-x="columnCollapse ? 1400 : 2400"
:scroll-y="720"
:scrollbar-props="{ trigger: 'none' }"
>
<template #tableTitle>
<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-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 {
formatColumns,
formGridColsOptions,
formGridSpanOptions,
genInfoObj,
selectListObj,
} from '@/views/develop/code/components/model';
import { ColumnList } from '@/api/develop/code';
import { NInputNumber, NSpace, NButton, NCheckbox, NInput, NSelect, NCascader } from 'naive-ui';
import { HelpCircleOutline, Reload, Sync, MoveOutline } from '@vicons/ionicons5';
import { cloneDeep } from 'lodash-es';
import { renderIcon, renderTooltip } from '@/utils';
import Move from './Move.vue';
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 show = ref(false);
const dataSource = ref([]);
const moveRef = ref();
const columnCollapse = ref(true);
const columnsCollapseData = computed(() => {
return columnCollapse.value
? [
{
title: '字段列名',
key: 'name',
width: 100,
},
{
title: '字段描述',
key: 'dc',
width: 100,
render(row) {
return h(NInput, {
value: row.dc,
onUpdateValue: function (e) {
row.dc = e;
},
});
},
},
]
: [
{
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,
title(_column) {
return renderTooltip(
h(
NButton,
{
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',
},
{ default: () => '新增/编辑表单', icon: renderIcon(HelpCircleOutline) }
),
'勾选编辑以后会在新增、编辑表单中显示该字段;当同时勾选列表查询时,会优先使用配置的表单组件'
);
},
key: 'edit',
align: 'center',
children: [
{
align: 'center',
title: '编辑',
key: 'isEdit',
width: 30,
render(row) {
const disabled = isEditDisabled(row);
const checkbox = h(NCheckbox, {
defaultChecked: row.isEdit,
disabled: disabled,
onUpdateChecked: function (e) {
row.isEdit = e;
},
});
if (!disabled) {
return checkbox;
}
return renderTooltip(checkbox, '该字段属性由系统维护,无需单独配置!');
},
},
{
title: '必填',
key: 'required',
width: 30,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.required,
disabled: row.name === 'id',
onUpdateChecked: function (e) {
row.required = e;
},
});
},
},
{
title: '唯一',
key: 'unique',
width: 30,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.unique,
disabled: row.name === 'id',
onUpdateChecked: function (e) {
row.unique = e;
},
});
},
},
{
title: '表单组件',
key: 'formMode',
width: 100,
render(row) {
return h(NSelect, {
consistentMenuWidth: false,
value: row.formMode,
options: getFormModeOptions(row.tsType),
onUpdateValue: function (e) {
row.formMode = e;
},
});
},
},
{
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: 100,
render(row) {
return h(NSelect, {
consistentMenuWidth: false,
value: row.formRole,
disabled: row.name === 'id',
options: props.selectList?.formRole ?? [],
onUpdateValue: function (e) {
row.formRole = e;
},
});
},
},
{
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) {
return h(NSelect, {
consistentMenuWidth: false,
disabled: row.name === 'id',
value: row.formGridSpan,
options: getFormGridSpanOptions(formValue.value.options.presetStep.formGridCols),
onUpdateValue: function (e) {
row.formGridSpan = e;
},
});
},
},
],
},
{
width: 800,
title: '列表',
key: 'list',
align: 'center',
children: [
{
title: '列表',
key: 'isList',
width: 30,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isList,
onUpdateChecked: function (e) {
row.isList = e;
},
});
},
},
{
title: '导出',
key: 'isExport',
width: 30,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isExport,
onUpdateChecked: function (e) {
row.isExport = e;
},
});
},
},
{
title: '查询',
key: 'isQuery',
width: 30,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isQuery,
onUpdateChecked: function (e) {
row.isQuery = e;
},
});
},
},
{
title: '查询条件',
key: 'queryWhere',
width: 90,
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: 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;
},
});
},
},
],
},
];
});
// 同步字段
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) {
return [];
}
switch (type) {
case 'number':
for (let i = 0; i < options.length; i++) {
const allows = [
'Input',
'InputNumber',
'Radio',
'Select',
'Switch',
'Rate',
'TreeSelect',
'Cascader',
];
if (!allows.includes(options[i].value)) {
options[i].disabled = true;
}
}
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>
.tree-tips {
margin-left: 12px;
color: #18a058;
font-weight: 600;
}
</style>
@@ -0,0 +1,430 @@
<template>
<n-spin :show="show" description="加载中...">
<n-card :bordered="false" class="proCard">
<BasicTable
size="small"
striped
resizable
canResize
:single-line="false"
:showTopRight="false"
:pagination="false"
:columns="columns"
:dataSource="dataSource"
:row-key="(row) => row.id"
:resizeHeightOffset="-20000"
: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 { computed, h, onMounted, ref } from 'vue';
import { BasicTable } from '@/components/Table';
import { formatColumns, genInfoObj, selectListObj } from '@/views/develop/code/components/model';
import { ColumnList } from '@/api/develop/code';
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']);
interface Props {
value?: any;
selectList: any;
uuid: string;
}
const props = withDefaults(defineProps<Props>(), {
value: genInfoObj,
selectList: selectListObj,
uuid: '',
});
const formValue = computed({
get() {
return props.value;
},
set(value) {
emit('update:value', value);
},
});
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;
}
for (let i = 0; i < formValue.value.options.join.length; i++) {
if (formValue.value.options.join[i].uuid === props.uuid) {
return i;
}
}
return -1;
}
// 同步字段属性
function syncColumns(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) => {
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) {
dataSource.value = formValue.value.options.join[index].columns;
show.value = false;
return;
}
reloadColumns(index);
}, 100);
});
</script>
<style lang="less" scoped></style>
@@ -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>
@@ -0,0 +1,161 @@
<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-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
:class="'code-' + getFileExtension(view.path)"
:code="view.content"
:hljs="hljs"
:language="getFileExtension(view.path)"
show-line-numbers
/>
</n-scrollbar>
</n-tab-pane>
</n-tabs>
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import hljs from 'highlight.js/lib/core';
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,
CheckmarkDoneCircle,
CloseCircleOutline,
HelpCircleOutline,
RemoveCircleOutline,
} from '@vicons/ionicons5';
import { useMessage } from 'naive-ui';
hljs.registerLanguage('go', go);
hljs.registerLanguage('ts', typescript);
hljs.registerLanguage('sql', sql);
hljs.registerLanguage('vue', xml);
interface Props {
previewModel: any;
showModal: boolean;
}
const props = withDefaults(defineProps<Props>(), {
previewModel: cloneDeep({ views: {} }),
showModal: false,
});
const message = useMessage();
const content = ref('');
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;
});
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;
}
::v-deep(.code-scrollbar) {
height: calc(100vh - 300px);
background: #282b2e;
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>
@@ -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>
@@ -0,0 +1,142 @@
import { cloneDeep } from 'lodash-es';
import { isJsonString } from '@/utils/is';
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,
genTemplate: null,
varName: '',
options: {
headOps: ['add', 'batchDel', 'export'],
columnOps: ['edit', 'del', 'view', 'status', 'check'],
autoOps: ['genMenuPermissions', 'runDao', 'runService'],
join: [],
menu: {
pid: 0,
icon: 'MenuOutlined',
sort: 0,
},
tree: {
titleColumn: null,
styleType: 1,
},
funcDict: {
valueColumn: null,
labelColumn: null,
},
presetStep: {
formGridCols: 1,
},
},
dbName: '',
tableName: '',
tableComment: '',
daoName: '',
masterColumns: [],
addonName: null,
status: 2,
createdAt: '',
updatedAt: '',
};
export const selectListObj = {
db: [],
genType: [],
status: [],
tables: [],
formMode: [],
formRole: [],
dictMode: [],
whereMode: [],
buildMeth: [],
tableAlign: [],
treeStyleType: [],
};
export function newState(state) {
if (state !== null) {
return cloneDeep(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;
}
+308
View File
@@ -0,0 +1,308 @@
<template>
<div>
<n-spin :show="show" description="正在生成配置信息...">
<n-card class="proCard">
<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"
>
<EditSlaveCell
v-model:value="genInfo"
:uuid="slaveMap[panel]"
:selectList="selectList"
/>
</n-tab-pane>
<template #suffix>
<n-space>
<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
>
<n-button type="info" dashed @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, useNotification } 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, formatColumns } 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 formBtnPreviewLoading = ref(false);
const previewModel = ref<any>();
const dialog = useDialog();
const notification = useNotification();
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 });
// 导入主表数据
tmp.masterColumns = formatColumns(tmp.masterColumns);
// 导入生成选项
if (isJsonString(tmp.options)) {
tmp.options = JSON.parse(tmp.options);
}
// 预设流程
if (!tmp.options.presetStep) {
tmp.options.presetStep = {
formGridCols: 1,
};
}
// 树表
if (!tmp.options.tree) {
tmp.options.tree = {
titleColumn: null,
styleType: 1,
};
}
genInfo.value = tmp;
}
watch(
genInfo,
(newVal, _oldVal) => {
if (newVal.genType >= 10 && newVal.genType < 20) {
handleAdd('主表字段');
} else {
handleClose('主表字段');
}
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) {
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 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({});
};
function preview() {
formBtnPreviewLoading.value = true;
Preview(genInfo.value)
.then((res) => {
previewModel.value = res;
showModal.value = true;
})
.finally(() => {
formBtnPreviewLoading.value = false;
});
}
function submitBuild() {
dialog.info({
title: '提示',
content: '你确定要提交生成吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
formBtnLoading.value = true;
Build(genInfo.value)
.then((_res) => {
buildSuccessNotify();
})
.finally(() => {
formBtnLoading.value = false;
});
},
});
}
function submitSave() {
dialog.info({
title: '提示',
content: '你确定要保存生成配置吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Edit(genInfo.value).then((_res) => {
message.success('操作成功');
});
},
});
}
function buildSuccessNotify() {
let count = 10;
const n = notification.success({
title: '生成提交成功',
content: `如果你使用的热编译,页面将在 ${count} 秒后自动刷新即可生效。否则请手动重启服务后刷新页面!`,
duration: 10000,
closable: false,
onAfterEnter: () => {
const minusCount = () => {
count--;
n.content = `如果你使用的热编译,页面将在 ${count} 秒后自动刷新即可生效。否则请手动重启服务后刷新页面!`;
if (count > 0) {
window.setTimeout(minusCount, 1000);
}
};
window.setTimeout(minusCount, 1000);
},
onAfterLeave: () => {
location.reload();
},
});
}
function handleBack() {
dialog.info({
title: '提示',
content: '你确定要返回生成列表?系统不会主动保存更改',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
router.push({ name: 'develop_code' });
},
});
}
</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>
+499
View File
@@ -0,0 +1,499 @@
<template>
<div>
<n-card :bordered="false" class="proCard" title="代码生成">
<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"
:resizeHeightOffset="-10000"
>
<template #tableTitle>
<n-button type="primary" @click="addTable">
<template #icon>
<n-icon>
<PlusOutlined />
</n-icon>
</template>
立即生成
</n-button>
<n-button
type="error"
@click="batchDelete"
:disabled="batchDeleteDisabled"
class="min-left-space"
>
<template #icon>
<n-icon>
<DeleteOutlined />
</n-icon>
</template>
批量删除
</n-button>
</template>
</BasicTable>
<n-modal
v-model:show="showModal"
:mask-closable="false"
: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"
:on-update:value="onUpdateValueGenType"
/>
</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="genTemplate">
<n-select
placeholder="请选择"
:options="genTemplateOptions"
v-model:value="formParams.genTemplate"
:onFocus="onFocusGenTemplate"
@update:value="handleGenTemplateUpdateValue"
/>
</n-form-item>
<n-form-item label="选择插件" path="addonName" v-show="selectAddon">
<n-select
placeholder="请选择"
:options="selectList.addons"
v-model:value="formParams.addonName"
/>
</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>
</div>
</template>
<script lang="ts" setup>
import { h, onBeforeMount, reactive, ref } from 'vue';
import { NTag, 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';
const selectList = ref({
db: [],
genType: [],
status: [],
tables: [],
formMode: [],
formRole: [],
dictMode: [],
whereMode: [],
addons: [],
});
const columns = [
{
title: 'ID',
key: 'id',
width: 60,
},
{
title: '模板',
key: 'genTemplate',
render(row) {
return h('p', { id: 'app' }, [
h('div', {
innerHTML: '<div><p>' + row.genTemplateGroup + '</p></div>',
}),
h('div', {
innerHTML:
'<div><p>' + getOptionLabel(selectList.value.genType, row.genType) + '</p></div>',
}),
]);
},
width: 120,
},
{
title: '数据库',
key: 'dbName',
width: 280,
render(row) {
return h('p', { id: 'app' }, [
h('div', {
innerHTML: '<div><p>' + row.dbName + '</p></div>',
}),
h('div', {
innerHTML: '<div><p>' + row.tableName + '</p></div>',
}),
]);
},
},
{
title: '实体命名',
key: 'varName',
render(row) {
return row.varName;
},
width: 140,
},
{
title: '生成名称',
key: 'tableComment',
width: 140,
},
{
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,
},
];
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>();
const rules = {
varName: {
required: true,
trigger: ['blur', 'input'],
message: '实体命名不能为空,首字母大写',
},
};
const actionColumn = reactive({
width: 180,
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) => {
message.success('操作成功');
reloadTable();
});
},
});
}
function batchDelete() {
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value }).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
});
}
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) {
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;
}
function getOptionLabel(options, value) {
if (!options || options?.length === 0) {
return `unknown`;
}
for (const item of options) {
if (item.value == value) {
return item.label;
}
}
return `unknown`;
}
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) {
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;
}
const genTemplateOptions = ref([]);
function onFocusGenTemplate() {
for (let i = 0; i < selectList.value.genType?.length; i++) {
if (selectList.value.genType[i].value === formParams.value.genType) {
genTemplateOptions.value = selectList.value.genType[i].templates;
formParams.value.genTemplate = null;
}
}
}
function onUpdateValueGenType(value) {
formParams.value.genType = value;
onFocusGenTemplate();
}
const selectAddon = ref(false);
function handleGenTemplateUpdateValue(value, option) {
formParams.value.genTemplate = value;
selectAddon.value = option.isAddon === true;
}
onBeforeMount(async () => {
await loadSelect();
});
</script>
<style lang="less" scoped></style>