530 lines
17 KiB
Vue
530 lines
17 KiB
Vue
<template>
|
|
<page-container>
|
|
<div class="manager-container">
|
|
<div class="left">
|
|
<j-input-search
|
|
v-model:value="leftData.searchValue"
|
|
placeholder="请输入"
|
|
style="margin-bottom: 24px"
|
|
/>
|
|
<!-- 使用v-if用于解决异步加载数据后不展开的问题 -->
|
|
<j-tree
|
|
v-if="leftData.treeData.length > 0"
|
|
showLine
|
|
defaultExpandAll
|
|
:tree-data="leftData.treeData"
|
|
v-model:selectedKeys="leftData.selectedKeys"
|
|
@select="onSelect"
|
|
>
|
|
<template #title="{ dataRef }">
|
|
<div
|
|
v-if="dataRef.root"
|
|
style="
|
|
justify-content: space-between;
|
|
display: flex;
|
|
align-items: center;
|
|
width: 200px;
|
|
"
|
|
>
|
|
<span>
|
|
{{ dataRef.title }}
|
|
</span>
|
|
<AIcon
|
|
type="PlusOutlined"
|
|
style="color: #1d39c4"
|
|
@click="addTable"
|
|
/>
|
|
</div>
|
|
<span v-else>
|
|
{{ dataRef.title }}
|
|
</span>
|
|
</template>
|
|
</j-tree>
|
|
</div>
|
|
<div class="right">
|
|
<div class="btns">
|
|
<j-button type="primary" @click="clickSave">保存</j-button>
|
|
</div>
|
|
<j-form ref="formRef" :model="table">
|
|
<j-table
|
|
:columns="columns"
|
|
:dataSource="table.data"
|
|
:pagination="false"
|
|
:scroll="{ y: 500 }"
|
|
>
|
|
<template #bodyCell="{ column, record, index }">
|
|
<template v-if="column.key === 'name'">
|
|
<j-form-item
|
|
:name="['data', index, 'name']"
|
|
:rules="[
|
|
{
|
|
max: 64,
|
|
message: '最多可输入64个字符',
|
|
},
|
|
{
|
|
required: true,
|
|
message: '请输入名称',
|
|
},
|
|
]"
|
|
>
|
|
<j-input
|
|
:disabled="record.old_id"
|
|
v-model:value="record.name"
|
|
placeholder="请输入名称"
|
|
/>
|
|
</j-form-item>
|
|
</template>
|
|
<template v-else-if="column.key === 'type'">
|
|
<j-form-item
|
|
:name="['data', index, 'type']"
|
|
:rules="[
|
|
{
|
|
max: 64,
|
|
message: '最多可输入64个字符',
|
|
},
|
|
{
|
|
required: true,
|
|
message: '请输入类型',
|
|
},
|
|
]"
|
|
>
|
|
<j-input
|
|
v-model:value="record.type"
|
|
placeholder="请输入类型"
|
|
/>
|
|
</j-form-item>
|
|
</template>
|
|
<template v-else-if="column.key === 'length'">
|
|
<j-form-item :name="['data', index, 'length']">
|
|
<j-input-number
|
|
v-model:value="record.length"
|
|
:min="0"
|
|
:max="99999"
|
|
style="width: 100%"
|
|
/>
|
|
</j-form-item>
|
|
</template>
|
|
<template v-else-if="column.key === 'scale'">
|
|
<j-form-item :name="['data', index, 'scale']">
|
|
<j-input-number
|
|
v-model:value="record.scale"
|
|
:min="0"
|
|
:max="99999"
|
|
style="width: 100%"
|
|
/>
|
|
</j-form-item>
|
|
</template>
|
|
<template v-else-if="column.key === 'notnull'">
|
|
<j-form-item
|
|
:name="['data', index, 'notnull']"
|
|
:rules="[
|
|
{
|
|
required: true,
|
|
message: '请选择是否不能为空',
|
|
},
|
|
]"
|
|
>
|
|
<j-radio-group
|
|
v-model:value="record.notnull"
|
|
button-style="solid"
|
|
>
|
|
<j-radio-button :value="true"
|
|
>是</j-radio-button
|
|
>
|
|
<j-radio-button :value="false"
|
|
>否</j-radio-button
|
|
>
|
|
</j-radio-group>
|
|
</j-form-item>
|
|
</template>
|
|
<template v-else-if="column.key === 'comment'">
|
|
<j-form-item :name="['data', index, 'comment']">
|
|
<j-input
|
|
v-model:value="record.comment"
|
|
placeholder="请输入说明"
|
|
/>
|
|
</j-form-item>
|
|
</template>
|
|
<template v-else-if="column.key === 'action'">
|
|
<PermissionButton
|
|
hasPermission="system/DataSource:delete"
|
|
type="link"
|
|
:tooltip="{ title: '删除' }"
|
|
:danger="true"
|
|
:popConfirm="{
|
|
title: `确认删除`,
|
|
onConfirm: () =>
|
|
clickDel(record, index),
|
|
}"
|
|
:disabled="record.status"
|
|
>
|
|
<AIcon type="DeleteOutlined" />
|
|
</PermissionButton>
|
|
</template>
|
|
</template>
|
|
</j-table>
|
|
</j-form>
|
|
|
|
<j-button class="add-row" @click="addRow">
|
|
<AIcon type="PlusOutlined" /> 新增行
|
|
</j-button>
|
|
</div>
|
|
</div>
|
|
<j-modal
|
|
:visible="true"
|
|
v-if="dialog.visible"
|
|
title="新增"
|
|
@ok="handleOk"
|
|
@cancel="handleCancel"
|
|
>
|
|
<j-form :model="dialog.form" ref="addFormRef" :layout="'vertical'">
|
|
<j-form-item
|
|
label="名称"
|
|
name="name"
|
|
:required="true"
|
|
:rules="[
|
|
{
|
|
required: true,
|
|
message: '请输入名称',
|
|
},
|
|
{
|
|
max: 64,
|
|
message: '最多可输入64个字符',
|
|
trigger: 'change',
|
|
},
|
|
{
|
|
// pattern: /^[0-9].*$/,
|
|
// message: '不能以数字开头',
|
|
trigger: 'change',
|
|
validator: checkName,
|
|
},
|
|
{
|
|
pattern: /^[a-zA-Z0-9_\u4e00-\u9fa5]+$/,
|
|
message: '名称只能由英文、汉字、下划线、数字组成',
|
|
trigger: 'change',
|
|
},
|
|
]"
|
|
>
|
|
<j-input
|
|
v-model:value="dialog.form.name"
|
|
placeholder="请输入名称"
|
|
/>
|
|
</j-form-item>
|
|
</j-form>
|
|
</j-modal>
|
|
</page-container>
|
|
</template>
|
|
|
|
<script setup lang="ts" name="Management">
|
|
import {
|
|
getDataSourceInfo_api,
|
|
rdbTree_api,
|
|
rdbTables_api,
|
|
saveTable_api,
|
|
delSaveRow_api,
|
|
} from '@/api/system/dataSource';
|
|
import { onlyMessage } from '@/utils/comm';
|
|
import { randomString } from '@/utils/utils';
|
|
import { FormInstance } from 'ant-design-vue';
|
|
import { message } from 'jetlinks-ui-components';
|
|
import { DataNode } from 'ant-design-vue/lib/tree';
|
|
import _ from 'lodash';
|
|
import { cloneDeep } from 'lodash';
|
|
import type { dbColumnType, dictItemType, sourceItemType } from '../typing';
|
|
|
|
const id = useRoute().query.id as string;
|
|
|
|
const columns = [
|
|
{
|
|
title: '列名',
|
|
dataIndex: 'name',
|
|
key: 'name',
|
|
},
|
|
{
|
|
title: '类型',
|
|
dataIndex: 'type',
|
|
key: 'type',
|
|
},
|
|
{
|
|
title: '长度',
|
|
dataIndex: 'length',
|
|
key: 'length',
|
|
},
|
|
{
|
|
title: '精度',
|
|
dataIndex: 'scale',
|
|
key: 'scale',
|
|
},
|
|
{
|
|
title: '不能为空',
|
|
dataIndex: 'notnull',
|
|
key: 'notnull',
|
|
width: 130
|
|
},
|
|
{
|
|
title: '说明',
|
|
dataIndex: 'comment',
|
|
key: 'comment',
|
|
},
|
|
{
|
|
title: '操作',
|
|
dataIndex: 'action',
|
|
key: 'action',
|
|
},
|
|
];
|
|
const formRef = ref();
|
|
|
|
const getInfo = (_id: string) => {
|
|
getDataSourceInfo_api(_id).then((resp: any) => {
|
|
info.data = resp.result;
|
|
});
|
|
};
|
|
|
|
const info = reactive({
|
|
data: {} as sourceItemType,
|
|
});
|
|
|
|
const leftData = reactive({
|
|
searchValue: '',
|
|
sourceTree: [] as dictItemType[],
|
|
treeData: [] as any[],
|
|
selectedKeys: [] as string[],
|
|
oldKey: '',
|
|
});
|
|
|
|
const queryTables = (key: string) => {
|
|
if (key) {
|
|
rdbTables_api(id, key).then((resp: any) => {
|
|
table.data = resp.result.columns.map(
|
|
(item: object, index: number) => ({
|
|
old_id: randomString(),
|
|
...item,
|
|
index,
|
|
}),
|
|
);
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleSearch = (refresh?: boolean) => {
|
|
rdbTree_api(id)
|
|
.then((resp: any) => {
|
|
if (resp.status === 200) {
|
|
leftData.sourceTree = resp.result;
|
|
if (refresh) {
|
|
leftData.selectedKeys = [resp.result[0]?.name];
|
|
queryTables(resp.result[0]?.name);
|
|
} else {
|
|
queryTables(leftData.selectedKeys[0]);
|
|
}
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
};
|
|
|
|
const onSelect = (selectedKeys: string[], e?: any) => {
|
|
if (e?.node?.root) {
|
|
leftData.selectedKeys = [leftData.oldKey];
|
|
return;
|
|
}
|
|
if (!selectedKeys[0]) {
|
|
return;
|
|
}
|
|
leftData.oldKey = selectedKeys[0];
|
|
const key = selectedKeys[0];
|
|
queryTables(key);
|
|
};
|
|
|
|
const addTable = (e: Event) => {
|
|
e?.stopPropagation();
|
|
dialog.visible = true;
|
|
};
|
|
|
|
watch(
|
|
() => id,
|
|
(newId) => {
|
|
if (newId) {
|
|
getInfo(newId);
|
|
handleSearch(true);
|
|
}
|
|
},
|
|
{
|
|
immediate: true,
|
|
},
|
|
);
|
|
|
|
const table = reactive({
|
|
data: [] as dbColumnType[],
|
|
});
|
|
|
|
const addRow = () => {
|
|
const initData: dbColumnType = {
|
|
scale: 0,
|
|
length: 0,
|
|
notnull: false,
|
|
type: '',
|
|
comment: '',
|
|
name: '',
|
|
};
|
|
table.data.push(initData);
|
|
};
|
|
|
|
const clickDel = (row: any, index: number) => {
|
|
if (row.scale !== undefined) {
|
|
delSaveRow_api(id, leftData.selectedKeys[0], [row.name]).then(
|
|
(resp: any) => {
|
|
if (resp.status === 200) {
|
|
table.data.splice(index, 1);
|
|
}
|
|
},
|
|
);
|
|
} else {
|
|
table.data.splice(index, 1);
|
|
}
|
|
};
|
|
|
|
const clickSave = () => {
|
|
formRef.value.validate().then((_data: any) => {
|
|
const columns = cloneDeep(table.data);
|
|
columns.forEach((item: any) => {
|
|
delete item?.old_id
|
|
delete item?.index
|
|
});
|
|
if (!columns.length) {
|
|
onlyMessage('请配置数据源字段', 'error');
|
|
return;
|
|
}
|
|
const params = {
|
|
name: leftData.selectedKeys[0],
|
|
columns,
|
|
};
|
|
saveTable_api(id, params).then((resp) => {
|
|
if (resp.status === 200) {
|
|
message.success('操作成功');
|
|
queryTables(params.name);
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
const addFormRef = ref<FormInstance>();
|
|
const dialog = reactive({
|
|
visible: false,
|
|
form: {
|
|
name: '',
|
|
},
|
|
});
|
|
|
|
const handleOk = () => {
|
|
addFormRef.value &&
|
|
addFormRef.value.validate().then(() => {
|
|
const name = dialog.form.name;
|
|
leftData.sourceTree.unshift({
|
|
id: name,
|
|
name,
|
|
});
|
|
leftData.oldKey = name;
|
|
leftData.selectedKeys = [name];
|
|
table.data = [];
|
|
dialog.visible = false;
|
|
addFormRef.value?.resetFields();
|
|
});
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
dialog.visible = false;
|
|
addFormRef.value?.resetFields();
|
|
};
|
|
|
|
watch(
|
|
[() => leftData.searchValue, () => leftData.sourceTree],
|
|
([m, n]) => {
|
|
if (!!m) {
|
|
const list = n.filter((item) => {
|
|
return item.name.includes(m);
|
|
});
|
|
leftData.treeData = [
|
|
{
|
|
title: info.data.shareConfig?.schema,
|
|
key: info.data.shareConfig?.schema,
|
|
root: true,
|
|
children: list.map((item) => ({
|
|
title: item.name,
|
|
key: item.name,
|
|
})),
|
|
},
|
|
];
|
|
if (!_.map(list, 'name').includes(leftData.selectedKeys[0])) {
|
|
leftData.selectedKeys = [list[0]?.name];
|
|
queryTables(list[0]?.name);
|
|
}
|
|
} else {
|
|
leftData.treeData = [
|
|
{
|
|
title: info.data.shareConfig?.schema,
|
|
key: info.data.shareConfig?.schema,
|
|
root: true,
|
|
children: leftData.sourceTree.map((item) => ({
|
|
title: item.name,
|
|
key: item.name,
|
|
})),
|
|
},
|
|
];
|
|
}
|
|
},
|
|
{ deep: true },
|
|
);
|
|
|
|
const checkName = (_: any, value: any) =>
|
|
new Promise((resolve, reject) => {
|
|
if (value) {
|
|
const first = value.slice(0, 1);
|
|
if (typeof Number(first) === 'number' && !isNaN(Number(first))) {
|
|
reject('不能以数字开头');
|
|
} else {
|
|
resolve('');
|
|
}
|
|
} else {
|
|
resolve('');
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.manager-container {
|
|
padding: 24px;
|
|
background-color: #fff;
|
|
display: flex;
|
|
min-height: 500px;
|
|
|
|
.left {
|
|
flex-basis: 280px;
|
|
padding: 0 24px;
|
|
box-sizing: border-box;
|
|
}
|
|
.right {
|
|
width: calc(100% - 280px);
|
|
box-sizing: border-box;
|
|
border-left: 1px solid #f0f0f0;
|
|
padding-left: 24px;
|
|
|
|
.btns {
|
|
display: flex;
|
|
justify-content: right;
|
|
}
|
|
|
|
.add-row {
|
|
display: block;
|
|
text-align: center;
|
|
width: 100%;
|
|
margin: 24px 0;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.ant-form-item {
|
|
margin-bottom: 0;
|
|
}
|
|
}
|
|
}
|
|
</style>
|