wujingjing
2024-10-08 6869f6a6d3f095fee026fa384c14dd19d35b09db
src/views/project/yw/lowCode/sqlAmis/edit/SqlAmisEdit.vue
@@ -1,6 +1,6 @@
<template>
   <div class="h-full flex flex-col">
      <titleBox class="flex-0" style="background-color: #fff" :title="supervisor?.title">
   <div class="h-full flex flex-col bg-white">
      <titleBox class="flex-0 my-3.5" style="background-color: #fff" :title="supervisor?.title">
         <template v-slot:left>
            <el-button
               icon="ele-ArrowLeft"
@@ -11,52 +11,167 @@
            >
            </el-button>
         </template>
         <template #right>
            <el-button type="primary" size="large" @click="saveClick">保存</el-button>
         </template>
      </titleBox>
      <div class="grid grid-cols-2 gap-2 h-full flex-auto">
         <div class="h-full overflow-auto">
      <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="h-full"
               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 prop="url" label="请求地址" /> -->
            </el-table>
            <!-- <codemirror
               v-model="dockCode"
               :style="{ height: '100%' }"
               :autofocus="true"
               :indent-with-tab="true"
               :tab-size="2"
               :extensions="dockEditorExtensions"
               @change="log('change', $event)"
               @focus="log('focus', $event)"
               @blur="log('blur', $event)"
            /> -->
         </div>
         <div class="h-full overflow-auto" v-if="currentSql">
            <codemirror
               class="h-full overflow-auto"
               v-model="currentSql.sql"
               :style="{ height: '100%' }"
               :autofocus="true"
               :indent-with-tab="true"
               :tab-size="2"
               :extensions="sqlEditorExtensions"
               @change="sqlCodeChange"
               @focus="log('focus', $event)"
               @blur="log('blur', $event)"
            />
         </div>
      </div>
               <!-- <el-table-column label="请求地址" ></el-table-column> -->
            </el-table></Pane
         >
         <Pane :size="60" class="pl-2">
            <Splitpanes class="default-theme h100" :horizontal="true">
               <Pane :size="45" class="flex-col flex">
                  <!-- #region ====================== 关键词匹配 ======================-->
                  <div class="flex-0 flex justify-between items-center mb-1 ml-1 h-[36px]">
                     <span class="font-bold">关键词</span>
                  </div>
                  <TagInput class="mb-1" v-model="matchList"></TagInput>
                  <!-- #endregion -->
                  <div class="flex-0 flex justify-between items-center mb-1 ml-1 h-[36px]">
                     <span class="font-bold">参数</span>
                     <el-button size="small" type="default" @click="addArg">添加参数</el-button>
                  </div>
                  <el-table class="flex-auto" :data="args" border>
                     <el-table-column prop="name" width="150" label="参数名" fixed 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="type" width="120" label="类型" show-overflow-tooltip>
                        <template #default="scope">
                           <el-select v-model="scope.row.type">
                              <el-option v-for="item in Object.keys(argTypeMap)" :key="item" :value="item" :label="argTypeMap[item]"></el-option>
                           </el-select>
                        </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="required" width="56" label="必填" show-overflow-tooltip>
                        <template #default="scope">
                           <el-checkbox v-model="scope.row.required"></el-checkbox>
                        </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="55">
                  <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>
                        <el-tabs v-model="sqlActiveTab" class="overflow-auto" style="height: calc(100% - 36px)">
                           <el-tab-pane :label="sqlTabMap[SqlTabType.LinkSql]" :name="SqlTabType.LinkSql" class="h-full">
                              <codemirror
                                 class="overflow-auto"
                                 style="height: 100%"
                                 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)"
                              />
                           </el-tab-pane>
                           <el-tab-pane :label="sqlTabMap[SqlTabType.Others]" :name="SqlTabType.Others">
                              <div class="flex-column">
                                 <div class="flex-0 flex mb-3.5">
                                    <el-button class="ml-auto" type="danger" @click="deleteUnLinkSql">删除所有未使用</el-button>
                                 </div>
                                 <el-table
                                    ref="draggableTableRef"
                                    class="flex-auto"
                                    border
                                    row-class-name="cursor-pointer"
                                    :data="otherSqlList"
                                    highlight-current-row
                                 >
                                    <el-table-column prop="id" label="id" width="220" fixed="left" show-overflow-tooltip> </el-table-column>
                                    <el-table-column label="数据库" prop="database" show-overflow-tooltip>
                                       <template #default="scope">
                                          {{ getMapDatabase(scope.row.database)?.title }}
                                       </template>
                                    </el-table-column>
                                    <el-table-column label="操作" width="120" fixed="right" show-overflow-tooltip>
                                       <template #default="scope">
                                          <div class="space-x-3 items-center flex">
                                             <el-tooltip effect="dark" content="查看SQL" placement="top">
                                                <i
                                                   class="ywifont ywicon-didaima !text-[21px] text-blue-400 cursor-pointer"
                                                   @click="openShowSqlDlg(scope.row)"
                                                ></i>
                                             </el-tooltip>
                                             <el-tooltip effect="dark" content="删除" placement="top">
                                                <i
                                                   class="ywifont ywicon-shanchu !text-[17px] text-red-400 cursor-pointer"
                                                   @click="deleteCurrentSql(scope.row)"
                                                ></i>
                                             </el-tooltip>
                                          </div>
                                       </template>
                                    </el-table-column>
                                 </el-table>
                              </div>
                           </el-tab-pane>
                        </el-tabs>
                     </template>
                  </div>
               </Pane>
            </Splitpanes>
         </Pane>
      </Splitpanes>
      <SqlCodeViewDlg v-model="optDlgIsShow" :item="optDlgMapRow"></SqlCodeViewDlg>
   </div>
</template>
@@ -65,20 +180,24 @@
import { sql } from '@codemirror/lang-sql';
import { xml } from '@codemirror/lang-xml';
import { vscodeDark } from '@uiw/codemirror-theme-vscode';
import _, { debounce } from 'lodash';
import { onMounted, ref } from 'vue';
import { Codemirror } from 'vue-codemirror';
import * as codeExample from './testData';
import titleBox from '/@/components/titleBox.vue';
import type { TableInstance } from 'element-plus';
import { ElMessage } from 'element-plus';
import { ElMessage, ElMessageBox } 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, argTypeMap } from './types';
import * as supervisorApi from '/@/api/supervisorAdmin';
import { updateSqlApi } from '/@/api/supervisorAdmin';
import titleBox from '/@/components/titleBox.vue';
import { useCompRef } from '/@/utils/types';
import { SupervisorPublished } from '../types';
import { computed } from 'vue';
import SqlCodeViewDlg from './optDlg/SqlCodeViewDlg.vue';
import TagInput from '/@/components/form/tagInput/TagInput.vue';
const props = defineProps(['supervisor']);
const emit = defineEmits(['backLastPage', 'updatePublished']);
const log = console.log;
@@ -94,6 +213,17 @@
   configList.value = [];
   sqlCode.value = '';
};
let defaultSelectDatabase = null;
const databaseSelectChange = (val) => {
   defaultSelectDatabase = val;
   if (currentDockConfig.value) {
      currentDockConfig.value.database = val;
   }
   sqlActiveTab.value = SqlTabType.LinkSql;
   updateSql(currentDockConfig.value.id, currentDockConfig.value);
   // updateSqlAndRs(currentDockConfig.value.id, currentDockConfig.value);
};
const backLastPage = () => {
   // setTimeout(() => {
   //    resetStatus();
@@ -101,18 +231,72 @@
   emit('backLastPage');
};
const currentRs = ref<AmisDockConfig>(null);
const otherSqlList = computed(() => {
   const sqlConfig = dockConfigList.value.filter((item) => {
      return item.type === AmisDockType.Sql;
   });
   if (!currentDockConfig.value) {
      return sqlConfig;
   }
   // 排除当前
   return sqlConfig.filter((item) => {
      return currentDockConfig.value.id !== item.id;
   });
});
const getWithTemplateDataCommentSql = (sql: string, data: any) => {
   const reg = new RegExp(`^\\s*(\\s*--\\s*".*"\\s*:.*\\n)+\\s*`);
   const isMatchReg = reg.test(sql);
   if (!Array.isArray(data)) {
      return isMatchReg ? sql.replace(reg, '') : sql;
   }
   const first = data[0];
   if (!_.isObjectLike(first)) {
      return isMatchReg ? sql.replace(reg, '') : sql;
   }
   const firstKeyList = Object.keys(first);
   // 值为对象
   if (_.isObjectLike(first[firstKeyList[0]])) {
      return isMatchReg ? sql.replace(reg, '') : sql;
   }
   let comment = '';
   firstKeyList.map((key, index, array) => {
      const value = JSON.stringify(first[key]);
      comment += `-- "${key}": ${value}\n`;
      // 最后一个再换行
      comment += index === array.length - 1 ? '\n' : '';
   });
   // 已经存在,一定要替换成最新的
   if (isMatchReg) {
      const replaceStr = sql.replace(reg, comment);
      return replaceStr;
   }
   const result = comment + sql;
   return result;
};
const dockRowChange = (row) => {
   currentRs.value = row;
   currentSql.value = sqlList.value.find((item) => item.id === currentRs.value.recordId) ?? {
   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 currentSql = ref(null);
const currentDockConfig = ref(null);
/** @description 路径分隔符 */
const PATH_SEPARATOR = '/';
/** @description 1 退出所有循环 -1退出当次循环 0或其他值继续 */
const travelObj = (obj, callBack?: (key?: string, value?: any, path?: string) => 1 | -1 | undefined | void | 0, path = '') => {
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;
@@ -120,7 +304,7 @@
   iterate: for (const item of entry) {
      const [key, value] = item;
      const currentPath = path ? path + PATH_SEPARATOR + key : key;
      res = callBack?.(key, value, currentPath);
      res = callBack?.(key, value, currentPath, obj);
      switch (res) {
         case 1:
            break iterate;
@@ -148,7 +332,7 @@
};
const enum AmisDockType {
   Api,
   Sql = 'SQL',
}
type AmisDockApi = string;
type AmisDockValue = AmisDockApi;
@@ -157,26 +341,50 @@
   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 = [];
   travelObj(obj, (key, value, path) => {
   extraInfoMap.value.clear();
   travelObj(obj, (key, value, path, item) => {
      if (key === 'api') {
         // const url = value.url;
         const urlPath = path + PATH_SEPARATOR + 'url';
         const randomStr = 'query_' + uuid().slice(0, 12);
         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.template ?? item.data),
         });
         // const recordId = uniqueId()
         jsonConfigList.push({
            asyncId: asyncId,
            recordId: recordId,
            type: AmisDockType.Api,
            type: AmisDockType.Sql,
            path: urlPath,
         });
      }
@@ -185,55 +393,88 @@
   return jsonConfigList;
};
const updateSqlAndRs = (id?, sqlValue?: string) => {
   const apiConfig = configList.value.filter((item) => item.type === AmisDockType.Api);
   if (apiConfig.length === 0) return;
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 = () => {
   const apiConfig = configList.value.filter((item) => item.type === AmisDockType.Sql);
   if (!checkValid(true)) {
      return;
   }
   const asyncRsList = apiConfig.map((item) => ({
      amis_path: item.path,
      async_id: item.asyncId,
      rec_id: item.recordId,
   }));
   if (id) {
      const found = sqlList.value?.find((item) => item.id === id);
      if (found) {
         found.sql = sqlValue;
      } else {
         if (!sqlList.value || sqlList.value.length === 0) {
            sqlList.value = [
               {
                  id: id,
                  sql: sqlValue,
               },
            ];
         } else {
            sqlList.value.push({
               id: id,
               sql: sqlValue,
            });
         }
      }
   }
   updateSqlApi(
      {
         id: props.supervisor.id,
         sql_json: sqlList.value.length === 0 ? null : JSON.stringify(sqlList.value),
         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),
         matchs: !matchList.value || matchList.value.length === 0 ? null : JSON.stringify(matchList.value),
      },
      {
         loading: false,
      }
   ).then(() => {
      ElMessage.success('保存成功');
      emit('updatePublished', props.supervisor.id, SupervisorPublished.N);
   });
};
const updateSql = (id?, sqlValue?: { database?: string; sql?: string; type?: AmisDockType }) => {
   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);
         }
      }
   }
};
const sqlCodeChange = debounce((val) => {
   if (!currentRs.value.recordId) return;
   updateSqlAndRs(currentRs.value.recordId, val);
   if (currentDockConfig.value) {
      if (!currentDockConfig.value.database) {
         ElMessage.warning('请先选择数据库');
         return;
      }
   }
   const id = currentDockConfig.value.id;
   const sqlValue = currentDockConfig.value;
   updateSql(id, sqlValue);
   // updateSqlAndRs();
}, 1000);
const sqlList = ref([]);
const dockConfigList = ref([]);
const rsTableRef = ref<TableInstance>(null);
let xmlJson = null;
@@ -266,36 +507,129 @@
      return Object.keys(currentItem).some((item) => originItem[item] !== currentItem[item]);
   });
};
// 匹配词
const matchList = ref([]);
//#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([]);
const getMapDatabase = (id: string) => {
   const foundItem = databaseList.value.find((item) => item.id === id);
   return foundItem;
};
//#endregion
onMounted(async () => {
   xmlJson = await supervisorApi.getLowCodeJson({
      id: props.supervisor.id,
   });
   if (!xmlJson?.amis_json) {
      ElMessage.warning('暂无SQL配置');
      return;
   }
   const originConfig =
      xmlJson.async_rs?.map(
         (item) =>
            ({
               type: AmisDockType.Api,
               type: AmisDockType.Sql,
               asyncId: item.async_id,
               recordId: item.rec_id,
               path: item.amis_path,
            } as AmisDockConfig)
      ) ?? [];
   sqlList.value = xmlJson.sql ?? null;
   dockConfigList.value = xmlJson.def_rs_json ?? null;
   args.value = xmlJson.args ?? [];
   matchList.value = xmlJson.matchs ??[];
   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]);
      // dockRowChange(configList.value[0]);
   }
   if (checkRsUpdate(originConfig, configList.value)) {
   if (!configList.value || configList.value.length === 0) {
      ElMessage.warning('暂无对接配置');
   }
   const isRsUpdate = checkRsUpdate(originConfig, configList.value);
   if (isRsUpdate) {
      // 自动更新Rs
      updateSqlAndRs();
      // updateSqlAndRs();
   }
});
//#region ====================== sql 保存历史 ======================
const enum SqlTabType {
   LinkSql = 'link-sql',
   Others = 'sql-list',
}
const sqlTabMap = {
   [SqlTabType.LinkSql]: '关联SQL',
   [SqlTabType.Others]: '其它',
};
const sqlActiveTab = ref<SqlTabType>(SqlTabType.LinkSql);
// 删除当前
const deleteCurrentSql = (row) => {
   ElMessageBox.confirm('确定要删除当前SQL吗?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning',
   }).then(async () => {
      dockConfigList.value = dockConfigList.value.filter((item) => item.id !== row.id);
      // updateSqlAndRs();
   });
};
// 删除未连接
const deleteUnLinkSql = () => {
   ElMessageBox.confirm('确定要删除所有未使用的SQL吗?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning',
   }).then(async () => {
      const apiConfig = configList.value.filter((item) => item.type === AmisDockType.Sql);
      const recordIds = apiConfig?.map((item) => item.recordId) ?? [];
      // 过滤出 apiConfig 中有的record;
      dockConfigList.value = dockConfigList.value?.filter((item) => recordIds.includes(item.id)) ?? [];
      // updateSqlAndRs();
   });
};
const optDlgIsShow = ref(false);
const optDlgMapRow = ref(null);
const openShowSqlDlg = (row?: any) => {
   optDlgMapRow.value = row;
   optDlgIsShow.value = true;
};
const saveClick = () => {
   updateSqlAndRs();
};
//#endregion
// watch(() => sqlCodemirrorRef.value, (codeMirrorRef) => {
//    codeMirrorRef.
// })
</script>