im 管理后台
This commit is contained in:
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user