<template>
|
<div class="h-full flex flex-col">
|
<titleBox class="flex-0" style="background-color: #fff" :title="supervisor?.title">
|
<template v-slot:left>
|
<el-button
|
icon="ele-ArrowLeft"
|
link
|
style="margin-right: 10px; margin-left: 10px; width: 40px"
|
size="small"
|
@click="backLastPage"
|
>
|
</el-button>
|
</template>
|
</titleBox>
|
|
<Splitpanes class="default-theme flex-auto" :horizontal="false">
|
<Pane :size="40" class="flex-column">
|
<div class="flex-0 flex justify-between items-center mb-1 ml-1 h-[36px]">
|
<span class="font-bold">配置项</span>
|
</div>
|
<el-table
|
ref="rsTableRef"
|
:data="configList"
|
row-class-name="cursor-pointer"
|
class="flex-auto"
|
highlight-current-row
|
@current-change="dockRowChange"
|
>
|
<el-table-column prop="asyncId" label="查询 id" />
|
<el-table-column prop="path" label="配置路径" />
|
<el-table-column prop="recordId" label="SQL id" />
|
<!-- <el-table-column label="请求地址" ></el-table-column> -->
|
</el-table></Pane
|
>
|
<Pane :size="60">
|
<Splitpanes class="default-theme h100" :horizontal="true">
|
<Pane :size="35" class="flex-col flex">
|
<div class="flex-0 flex justify-between items-center mb-1 ml-1 h-[36px]">
|
<span class="font-bold">参数</span>
|
<el-button type="primary" @click="addArg">添加</el-button>
|
</div>
|
<el-table class="flex-auto" :data="args" border>
|
<el-table-column prop="name" width="150" label="名称" show-overflow-tooltip>
|
<template #default="scope">
|
<el-input v-model="scope.row.name" @input="argsInput"></el-input>
|
</template>
|
</el-table-column>
|
<el-table-column prop="prompt" width="450" label="提示词" show-overflow-tooltip>
|
<template #default="scope">
|
<el-input type="textarea" :rows="2" v-model="scope.row.prompt" @input="argsInput"></el-input>
|
</template>
|
</el-table-column>
|
<el-table-column prop="check" label="缺省值" show-overflow-tooltip>
|
<template #default="scope">
|
<el-input v-model="scope.row.check" @input="argsInput"></el-input>
|
</template>
|
</el-table-column>
|
<el-table-column label="" width="55" fixed="right" show-overflow-tooltip>
|
<template #default="scope">
|
<el-tooltip effect="dark" content="删除" placement="top">
|
<i class="ywifont ywicon-shanchu !text-[17px] text-red-400 cursor-pointer" @click="deleteArg(scope.$index)"></i>
|
</el-tooltip>
|
</template>
|
</el-table-column>
|
</el-table>
|
</Pane>
|
<Pane :size="65">
|
<div class="h-full">
|
<template v-if="currentDockConfig">
|
<div class="flex justify-between items-center my-1 ml-1 h-[36px]">
|
<el-select class="w-52 font-bold" v-model="currentDockConfig.type">
|
<el-option
|
v-for="item in Object.keys(amisDockTypeMap)"
|
:key="item"
|
:value="item"
|
:label="amisDockTypeMap[item]"
|
></el-option>
|
</el-select>
|
|
<el-select class="w-40" v-model="currentDockConfig.database" @change="databaseSelectChange">
|
<el-option v-for="item in databaseList" :key="item.id" :value="item.id" :label="item.title"></el-option>
|
</el-select>
|
</div>
|
<codemirror
|
class="overflow-auto"
|
style="height: calc(100% - 36px)"
|
v-model="currentDockConfig.sql"
|
:autofocus="true"
|
:indent-with-tab="true"
|
:tab-size="2"
|
:extensions="sqlEditorExtensions"
|
@change="sqlCodeChange"
|
@focus="log('focus', $event)"
|
@blur="log('blur', $event)"
|
/>
|
</template>
|
</div>
|
</Pane>
|
</Splitpanes>
|
</Pane>
|
</Splitpanes>
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import { json } from '@codemirror/lang-json';
|
import { sql } from '@codemirror/lang-sql';
|
import { xml } from '@codemirror/lang-xml';
|
import { vscodeDark } from '@uiw/codemirror-theme-vscode';
|
import type { TableInstance } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
import _, { debounce } from 'lodash';
|
import { Pane, Splitpanes } from 'splitpanes';
|
import 'splitpanes/dist/splitpanes.css';
|
import { v4 as uuid } from 'uuid';
|
import { onMounted, ref, watch } from 'vue';
|
import { Codemirror } from 'vue-codemirror';
|
import { SupervisorPublished } from '../types';
|
import * as codeExample from './testData';
|
import { amisDockTypeMap } from './types';
|
import * as supervisorApi from '/@/api/supervisorAdmin';
|
import { updateSqlApi } from '/@/api/supervisorAdmin';
|
import titleBox from '/@/components/titleBox.vue';
|
import { useCompRef } from '/@/utils/types';
|
|
const props = defineProps(['supervisor']);
|
const emit = defineEmits(['backLastPage', 'updatePublished']);
|
const log = console.log;
|
const jsonCode = ref(codeExample.jsonCode);
|
const dockCode = ref(codeExample.dockCode);
|
const sqlCode = ref(codeExample.sqlCode);
|
|
const jsonEditorExtensions = [json(), vscodeDark];
|
const sqlEditorExtensions = [sql(), vscodeDark];
|
const dockEditorExtensions = [xml(), vscodeDark];
|
const sqlCodeEditorRef = useCompRef(Codemirror);
|
const resetStatus = () => {
|
configList.value = [];
|
sqlCode.value = '';
|
};
|
|
let defaultSelectDatabase = null;
|
const databaseSelectChange = (val) => {
|
defaultSelectDatabase = val;
|
if (currentDockConfig.value) {
|
currentDockConfig.value.database = val;
|
}
|
updateSqlAndRs(currentDockConfig.value.id, currentDockConfig.value);
|
};
|
const backLastPage = () => {
|
// setTimeout(() => {
|
// resetStatus();
|
// }, 300);
|
emit('backLastPage');
|
};
|
const currentRs = ref<AmisDockConfig>(null);
|
|
const getWithTemplateDataCommentSql = (sql: string, data: any) => {
|
if (!Array.isArray(data)) {
|
return sql;
|
}
|
const first = data[0];
|
if (!_.isObjectLike(first)) {
|
return sql;
|
}
|
const firstKeyList = Object.keys(first);
|
// 值为对象
|
if (_.isObjectLike(first[firstKeyList[0]])) {
|
return sql;
|
}
|
|
|
|
let comment = '';
|
firstKeyList.map((key, index, array) => {
|
const value = JSON.stringify(first[key]);
|
comment += `-- "${key}": ${value}\n`;
|
});
|
|
const reg = new RegExp(`^\\s*(\\s*--\\s*".*"\\s*:.*\\n)+`)
|
// 已经存在,一定要替换成最新的
|
if (reg.test(sql)) {
|
const replaceStr = sql.replace(reg,comment);
|
return replaceStr;
|
}
|
|
const result = comment+sql;
|
return result;
|
};
|
const dockRowChange = (row) => {
|
currentRs.value = row;
|
currentDockConfig.value = dockConfigList.value.find((item) => item.id === currentRs.value.recordId) ?? {
|
id: row.recordId,
|
type: AmisDockType.Sql,
|
sql: '',
|
database: defaultSelectDatabase ?? databaseList.value[0]?.id ?? null,
|
};
|
const templateData = extraInfoMap.value.get(row.recordId).templateData;
|
|
currentDockConfig.value.sql = getWithTemplateDataCommentSql(currentDockConfig.value.sql,templateData)
|
};
|
const currentDockConfig = ref(null);
|
/** @description 路径分隔符 */
|
const PATH_SEPARATOR = '/';
|
/** @description 1 退出所有循环 -1退出当次循环 0或其他值继续 */
|
const travelObj = (
|
obj,
|
callBack?: (key?: string, value?: any, path?: string, item?: any) => 1 | -1 | undefined | void | 0,
|
path = ''
|
) => {
|
let entry = Object.entries(obj);
|
|
let res;
|
|
iterate: for (const item of entry) {
|
const [key, value] = item;
|
const currentPath = path ? path + PATH_SEPARATOR + key : key;
|
res = callBack?.(key, value, currentPath, obj);
|
switch (res) {
|
case 1:
|
break iterate;
|
case -1:
|
continue iterate;
|
default:
|
break;
|
}
|
if (!value) {
|
continue;
|
}
|
if (_.isObjectLike(value)) {
|
res = travelObj(value, callBack, currentPath);
|
switch (res) {
|
case 1:
|
break iterate;
|
case -1:
|
continue iterate;
|
default:
|
break;
|
}
|
}
|
}
|
return res;
|
};
|
|
const enum AmisDockType {
|
Sql = 'SQL',
|
}
|
type AmisDockApi = string;
|
type AmisDockValue = AmisDockApi;
|
type AmisDockConfig = {
|
type?: AmisDockType;
|
path: string;
|
asyncId: string;
|
recordId: string;
|
// url: string;
|
};
|
|
const configList = ref<AmisDockConfig[]>([]);
|
|
type ExtraInfo = {
|
url: string;
|
templateData: Array<any>;
|
};
|
const extraInfoMap = ref(new Map<string, ExtraInfo>());
|
|
const getAmisData = (data) => {
|
if (!data) return null;
|
|
const dataKeyList = Object.keys(data);
|
if (dataKeyList.length === 0) return null;
|
if (dataKeyList.length === 1) return data.items ?? data.rows ?? data;
|
|
return data;
|
};
|
const parseJSONData = (obj: any, apiDataList) => {
|
// 先清空
|
|
const jsonConfigList = [];
|
extraInfoMap.value.clear();
|
travelObj(obj, (key, value, path, item) => {
|
if (key === 'api') {
|
// const url = value.url;
|
const urlPath = path + PATH_SEPARATOR + 'url';
|
const randomStr = 'q_' + value.url + '_' + uuid().slice(0, 12);
|
|
const foundItem = apiDataList.find((item) => item.path === urlPath);
|
const asyncId = foundItem?.asyncId ?? randomStr;
|
const recordId = foundItem?.recordId ?? randomStr;
|
|
extraInfoMap.value.set(recordId, {
|
url: value.url,
|
templateData: getAmisData(item.data),
|
});
|
// const recordId = uniqueId()
|
jsonConfigList.push({
|
asyncId: asyncId,
|
recordId: recordId,
|
type: AmisDockType.Sql,
|
path: urlPath,
|
});
|
}
|
});
|
|
return jsonConfigList;
|
};
|
|
const checkValid = (showTip: boolean, tip?: string) => {
|
if (!dockConfigList.value || dockConfigList.value.length === 0) return true;
|
const foundWithNoDatabase = dockConfigList.value?.find((item) => !item.database);
|
if (foundWithNoDatabase && showTip) {
|
ElMessage.warning(tip ?? `【${foundWithNoDatabase.id}】未选择数据库`);
|
}
|
return !foundWithNoDatabase;
|
};
|
|
const updateSqlAndRs = (id?, sqlValue?: { database?: string; sql?: string; type?: AmisDockType }) => {
|
const apiConfig = configList.value.filter((item) => item.type === AmisDockType.Sql);
|
|
if (!checkValid(true)) {
|
return;
|
}
|
|
const recordIds = apiConfig?.map((item) => item.recordId) ?? [];
|
// 过滤出 apiConfig 中有的record;
|
dockConfigList.value = dockConfigList.value?.filter((item) => recordIds.includes(item.id)) ?? [];
|
const asyncRsList = apiConfig.map((item) => ({
|
amis_path: item.path,
|
async_id: item.asyncId,
|
rec_id: item.recordId,
|
}));
|
|
if (id && sqlValue) {
|
const foundIndex = dockConfigList.value?.findIndex((item) => item.id === id);
|
if (foundIndex > -1) {
|
dockConfigList.value[foundIndex] = {
|
...dockConfigList.value[foundIndex],
|
...sqlValue,
|
};
|
} else {
|
if (!sqlValue.database) {
|
ElMessage.warning('请先选择数据库');
|
return;
|
}
|
const newSqlValue = {
|
id: id,
|
database: sqlValue.database ?? null,
|
sql: sqlValue.sql ?? null,
|
type: sqlValue.type ?? AmisDockType.Sql,
|
};
|
if (!dockConfigList.value || dockConfigList.value.length === 0) {
|
dockConfigList.value = [newSqlValue];
|
} else {
|
dockConfigList.value.push(newSqlValue);
|
}
|
}
|
}
|
|
updateSqlApi(
|
{
|
id: props.supervisor.id,
|
def_rs_json: dockConfigList.value.length === 0 ? null : JSON.stringify(dockConfigList.value),
|
async_rs_json: asyncRsList.length === 0 ? null : JSON.stringify(asyncRsList),
|
args: args.value.length === 0 ? null : JSON.stringify(args.value),
|
},
|
{
|
loading: false,
|
}
|
).then(() => {
|
emit('updatePublished', props.supervisor.id, SupervisorPublished.N);
|
});
|
};
|
|
const sqlCodeChange = debounce((val) => {
|
if (!currentRs.value.recordId) return;
|
if (currentDockConfig.value) {
|
if (!currentDockConfig.value.database) {
|
ElMessage.warning('请先选择数据库');
|
return;
|
}
|
}
|
updateSqlAndRs(currentDockConfig.value.id, currentDockConfig.value);
|
}, 1000);
|
const dockConfigList = ref([]);
|
const rsTableRef = ref<TableInstance>(null);
|
let xmlJson = null;
|
|
// 检查是否需要更新 rs
|
const checkRsUpdate = (originData: AmisDockConfig[], currentData: AmisDockConfig[]) => {
|
// 都为空,不需要更新
|
if ((!originData || originData.length === 0) && (!currentData || currentData.length === 0)) {
|
return false;
|
}
|
|
// 存在一方为空,需要更新
|
if (!originData || !currentData) {
|
return true;
|
}
|
|
// 长度不一致,需要更新
|
if (originData.length !== currentData.length) {
|
return true;
|
}
|
|
return originData.some((originItem) => {
|
const id = originItem.asyncId;
|
const currentItem = currentData.find((item) => item.asyncId === id);
|
// 没找到对应项,需要更新
|
if (!currentItem) {
|
return true;
|
}
|
|
// 对象项值不相等,需要更新
|
return Object.keys(currentItem).some((item) => originItem[item] !== currentItem[item]);
|
});
|
};
|
|
//#region ====================== 可编辑表格 ======================
|
const args = ref([]);
|
const debounceUpdateInput = debounce(() => {
|
updateSqlAndRs();
|
}, 400);
|
const argsInput = (val) => {
|
debounceUpdateInput();
|
};
|
const addArg = () => {
|
const initData = {
|
name: '',
|
prompt: '',
|
check: '',
|
};
|
if (!args.value || args.value.length === 0) {
|
args.value = [initData];
|
} else {
|
args.value.push(initData);
|
}
|
|
updateSqlAndRs();
|
};
|
|
const deleteArg = (index) => {
|
args.value.splice(index, 1);
|
updateSqlAndRs();
|
};
|
|
const databaseList = ref([]);
|
//#endregion
|
onMounted(async () => {
|
xmlJson = await supervisorApi.getLowCodeJson({
|
id: props.supervisor.id,
|
});
|
|
const originConfig =
|
xmlJson.async_rs?.map(
|
(item) =>
|
({
|
type: AmisDockType.Sql,
|
asyncId: item.async_id,
|
recordId: item.rec_id,
|
path: item.amis_path,
|
} as AmisDockConfig)
|
) ?? [];
|
dockConfigList.value = xmlJson.def_rs_json ?? null;
|
args.value = xmlJson.args ?? [];
|
|
configList.value = parseJSONData(xmlJson.amis_json, originConfig);
|
const res = await supervisorApi.getAmisDatabaseList();
|
databaseList.value = res.values ?? [];
|
if (configList.value.length > 0) {
|
rsTableRef.value.setCurrentRow(configList.value[0]);
|
}
|
|
if (!configList || configList.value.length === 0) {
|
ElMessage.warning('暂无对接配置');
|
}
|
|
const isRsUpdate = checkRsUpdate(originConfig, configList.value);
|
if (isRsUpdate) {
|
// 自动更新Rs
|
updateSqlAndRs();
|
}
|
});
|
|
// watch(() => sqlCodemirrorRef.value, (codeMirrorRef) => {
|
// codeMirrorRef.
|
// })
|
</script>
|