feat: 系统管理-数据源管理

This commit is contained in:
easy 2023-02-17 18:16:49 +08:00
parent 9e2c0069b6
commit 42d49a3083
8 changed files with 570 additions and 7 deletions

View File

@ -0,0 +1,11 @@
import server from '@/utils/request';
// 获取数据源列表
export const getDataSourceList_api = (data: object) => server.post(`/datasource/config/_query/`, data);
// 获取数据库类型字典
export const getDataTypeDict_api = () => server.get(`/datasource/config/types`);
// 修改数据源状态
export const changeStatus_api = (id:string, status:'_disable'|'_enable') => server.put(`/datasource/config/${id}/${status}`);

View File

@ -0,0 +1,224 @@
<template>
<a-modal
class="edit-dialog-container"
:title="dialog.title"
width="1050px"
@ok="dialog.handleOk"
:confirmLoading="dialog.loading.value"
cancelText="取消"
okText="确定"
v-model:visible="dialog.visible.value"
>
<a-form ref="formRef" :model="form.data" layout="vertical">
<a-row :gutter="24">
<a-col :span="12">
<a-form-item
name="name"
label="名称"
:rules="[
{ required: true, message: '请输入名称' },
{ max: 64, message: '最多可输入64个字符' },
]"
>
<a-input
v-model:value="form.data.name"
placeholder="请输入名称"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
name="typeId"
label="类型"
:rules="[{ required: true, message: '请选择类型' }]"
>
<a-select
v-model:value="form.data.typeId"
style="width: 120px"
:options="form.typeOptions"
placeholder="请选择类型"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24" v-if="form.data.typeId === 'rdb'">
<a-col :span="24">
<a-form-item
:name="['shareConfig', 'url']"
label="URL"
:rules="[{ required: true, message: '请输入URL' }]"
>
<a-input
v-model:value="form.data.shareConfig.url"
placeholder="请输入r2bdc或者jdbc连接地址示例r2dbc:mysql://127.0.0.1:3306/test"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24" v-if="form.data.typeId === 'rabbitmq'">
<a-col :span="24">
<a-form-item
:name="['shareConfig', 'adminUrl']"
label="管理地址"
:rules="[{ required: true, message: '请输入管理地址' }]"
>
<a-input
v-model:value="form.data.shareConfig.adminUrl"
placeholder="请输入管理地址示例http://localhost:15672"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24" v-if="form.data.typeId === 'rabbitmq'">
<a-col :span="24">
<a-form-item
:name="['shareConfig', 'addresses']"
label="链接地址"
:rules="[{ required: true, message: '请输入链接地址' }]"
>
<a-input
v-model:value="form.data.shareConfig.addresses"
placeholder="请输入链接地址示例localhost:5672"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item
:name="['shareConfig', 'username']"
label="用户名"
:rules="[
{ required: true, message: '请输入用户名' },
{
max: 64,
message: '最多可输入64个字符',
},
]"
>
<a-input
v-model:value="form.data.shareConfig.username"
placeholder="请输入用户名"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item
:name="['shareConfig', 'password']"
label="密码"
:rules="[
{ required: true, message: '请输入密码' },
{
max: 64,
message: '最多可输入64个字符',
},
]"
>
<a-input-password
v-model:value="form.data.shareConfig.password"
placeholder="请输入密码"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24" v-if="form.data.typeId === 'rabbitmq'">
<a-col :span="24">
<a-form-item
:name="['shareConfig', 'virtualHost']"
label="虚拟域"
:rules="[
{ required: true, message: '请输入虚拟域' },
{
max: 64,
message: '最多可输入64个字符',
},
]"
>
<a-input
v-model:value="form.data.shareConfig.virtualHost"
placeholder="请输入虚拟域"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24" v-if="form.data.typeId === 'rdb'">
<a-col :span="24">
<a-form-item
:name="['shareConfig', 'schema']"
label="schema"
:rules="[
{ required: true, message: '请输入schema' },
{
max: 64,
message: '最多可输入64个字符',
},
]"
>
<a-input
v-model:value="form.data.shareConfig.schema"
placeholder="请输入schema"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="24">
<a-form-item name="description" label="说明">
<a-textarea
v-model:value="form.data.description"
placeholder="请输入说明"
:rows="3"
showCount
:maxlength="200"
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-modal>
</template>
<script setup lang="ts">
import { getDataTypeDict_api } from '@/api/system/dataSource';
import type { dictItemType, optionItemType, sourceItemType } from '../typing';
//
const dialog = {
title: '',
loading: ref<boolean>(false),
visible: ref<boolean>(false),
handleOk: () => {},
//
changeVisible: (row: sourceItemType) => {
if (row.id) dialog.title = '编辑数据源';
else dialog.title = '新增数据源';
form.data = { ...row };
dialog.visible.value = true;
},
};
//
defineExpose({
openDialog: dialog.changeVisible,
});
const form = reactive({
data: {
shareConfig: {},
} as sourceItemType,
typeOptions: [] as optionItemType[],
getTypeOption: () => {
getDataTypeDict_api().then((resp: any) => {
const result = resp.result as dictItemType[];
form.typeOptions = result.map((item) => ({
label: item.name,
value: item.id,
}));
});
},
});
form.getTypeOption();
</script>
<style scoped></style>

View File

@ -0,0 +1,304 @@
<template>
<div class="data-source-container">
<Search :columns="query.columns" @search="query.search" />
<JTable
ref="tableRef"
:columns="table.columns"
:request="getDataSourceList_api"
model="TABLE"
:params="query.params.value"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
>
<template #headerTitle>
<PermissionButton
type="primary"
:uhasPermission="`${permission}:add`"
@click="table.openDialog({})"
>
<AIcon type="PlusOutlined" />新增
</PermissionButton>
</template>
<template #state="slotProps">
<BadgeStatus
:status="slotProps.state?.value"
:text="slotProps.state?.text"
:statusNames="{
enabled: 'success',
disabled: 'error',
}"
>
</BadgeStatus>
</template>
<template #typeId="slotProps">
{{
(table.typeOptions.value.length &&
table.getTypeLabel(slotProps.typeId)) ||
''
}}
</template>
<template #action="slotProps">
<a-space :size="16">
<PermissionButton
:uhasPermission="`${permission}:update`"
type="link"
:tooltip="{
title: '编辑',
}"
@click="table.openDialog(slotProps)"
>
<AIcon type="EditOutlined" />
</PermissionButton>
<PermissionButton
:uhasPermission="`${permission}:manage`"
type="link"
:tooltip="{
title:
slotProps?.typeId === 'rabbitmq'
? '暂不支持管理功能'
: table.getRowStatus(slotProps)
? '管理'
: '请先启用数据源',
}"
@click="
() =>
router.push(
`/system/DataSource/Management?id=${slotProps.id}`,
)
"
>
<AIcon type="icon-ziyuankuguanli" />
</PermissionButton>
<PermissionButton
:uhasPermission="`${permission}:action`"
type="link"
:popConfirm="{
title: `确定要${
table.getRowStatus(slotProps) ? '禁用' : '启用'
}`,
onConfirm: () => table.clickChangeStatus(slotProps),
}"
:tooltip="{
title: table.getRowStatus(slotProps)
? '禁用'
: '启用',
}"
>
<AIcon
:type="
table.getRowStatus(slotProps)
? 'StopOutlined'
: 'PlayCircleOutlined'
"
/>
<!-- <AIcon type="PlayCircleOutlined" /> -->
</PermissionButton>
<PermissionButton
:uhasPermission="`${permission}:delete`"
type="link"
:tooltip="{
title: table.getRowStatus(slotProps)
? '请先禁用,再删除'
: '删除',
}"
:popConfirm="{
title: `确认删除`,
onConfirm: () => table.clickDel(slotProps),
}"
:disabled="table.getRowStatus(slotProps)"
>
<AIcon type="DeleteOutlined" />
</PermissionButton>
</a-space>
</template>
</JTable>
<div class="dialogs">
<EditDialog ref="editDialogRef" @confirm="table.refresh" />
</div>
</div>
</template>
<script setup lang="ts" name="DataSource">
import PermissionButton from '@/components/PermissionButton/index.vue';
import BadgeStatus from '@/components/BadgeStatus/index.vue';
import EditDialog from './components/EditDialog.vue';
import type { dictItemType, optionItemType, sourceItemType } from './typing';
import {
getDataSourceList_api,
getDataTypeDict_api,
changeStatus_api,
} from '@/api/system/dataSource';
import { message } from 'ant-design-vue';
const permission = 'system/Relationship';
const router = useRouter();
const query = {
columns: [
{
title: '名称',
dataIndex: 'name',
key: 'name',
search: {
type: 'string',
},
},
{
title: '类型',
dataIndex: 'typeId',
key: 'typeId',
search: {
type: 'select',
options: () =>
new Promise((resolve) => {
if (table.typeOptions.value.length > 0)
return resolve(table.typeOptions.value);
getDataTypeDict_api().then((resp: any) => {
const result = resp.result as dictItemType[];
resolve(
result.map((item) => ({
label: item.name,
value: item.id,
})),
);
});
}),
},
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
search: {
type: 'string',
},
},
{
title: '状态',
dataIndex: 'state',
key: 'state',
ellipsis: true,
fixed: 'left',
search: {
type: 'select',
options: [
{
label: '正常',
value: 'enabled',
},
{
label: '已禁用',
value: 'disabled',
},
],
},
},
],
params: ref({}),
search: (params: object) => {
query.params.value = params;
},
};
const editDialogRef = ref(); //
const tableRef = ref<Record<string, any>>({}); //
const table = {
columns: [
{
title: '名称',
dataIndex: 'name',
key: 'name',
width: '250px',
},
{
title: '类型',
dataIndex: 'typeId',
key: 'typeId',
scopedSlots: true,
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
},
{
title: '状态',
dataIndex: 'state',
key: 'state',
scopedSlots: true,
width: '120px',
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
scopedSlots: true,
width: '200px',
},
],
typeOptions: ref<optionItemType[]>([]),
getTypeOption: () => {
getDataTypeDict_api().then((resp: any) => {
const result = resp.result as dictItemType[];
table.typeOptions.value = result.map((item) => ({
label: item.name,
value: item.id,
}));
});
},
getTypeLabel: (val: string): string => {
const options = table.typeOptions.value;
if (options.length < 1 || val === '') return '';
return options.find((item) => item.value === val)?.label || '';
},
getRowStatus: (row: sourceItemType) => {
return row.state?.value === 'enabled';
},
//
openDialog: (row: sourceItemType | {}) => {
editDialogRef.value.openDialog({shareConfig:{},...row});
},
//
clickDel: (row: sourceItemType) => {
// delRelation_api(row.id).then((resp: any) => {
// if (resp.status === 200) {
// tableRef.value?.reload();
// message.success('!');
// }
// });
},
clickChangeStatus: (row: sourceItemType) => {
const status = row.state.value === 'enabled' ? '_disable' : '_enable';
changeStatus_api(row.id as string, status).then(() => {
message.success('操作成功');
table.refresh();
});
},
//
refresh: () => {
tableRef.value.reload();
},
};
table.getTypeOption();
</script>
<style lang="less" scoped>
.data-source-container {
padding: 24px;
:deep(.ant-table-cell) {
.ant-btn-link {
padding: 0;
}
}
}
</style>

24
src/views/system/DataSource/typing.d.ts vendored Normal file
View File

@ -0,0 +1,24 @@
export type dictItemType = {
id: string,
name: string
}
export type optionItemType = {
label: string,
value: string
}
export type sourceItemType = {
id?: string,
name: string,
state: { text: string, value: "enabled" | 'disabled' },
typeId: string,
shareConfig:{
url:string,
adminUrl:string,
addresses:string,
username:string,
password:string,
virtualHost:string,
schema:string
}
description: string
}

View File

@ -20,7 +20,7 @@
</template>
<script setup lang="ts">
import { dictType, optionsType } from '../typing.d.ts';
import type { dictType, optionsType } from '../typing';
import { updatePermission_api } from '@/api/system/department';
import { message } from 'ant-design-vue';

View File

@ -167,7 +167,7 @@ import {
} from '@/api/system/department';
import { intersection } from 'lodash-es';
import { dictType } from '../typing.d.ts';
import type { dictType } from '../typing';
import { message } from 'ant-design-vue';
const permission = 'system/Department';

View File

@ -1,9 +1,9 @@
type dictType = {
export type dictType = {
id: string;
name: string;
}[];
type optionsType = {
export type optionsType = {
label: string,
value: string;
disabled?:boolean

View File

@ -7,7 +7,7 @@
:columns="table.columns"
:request="getRelationshipList_api"
model="TABLE"
:params="query.params"
:params="query.params.value"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
>
<template #headerTitle>
@ -122,9 +122,9 @@ const query = {
},
},
],
params: {},
params: ref({}),
search: (params: object) => {
query.params = params;
query.params.value = params;
},
};