|
@@ -0,0 +1,491 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import {useVModel} from "@vueuse/core";
|
|
|
+import {nextTick, onMounted, onUnmounted, ref, Ref, unref} from 'vue'
|
|
|
+import {TDataBaseSqlApi} from "@/ts-type/data-base-type";
|
|
|
+import * as monaco from "monaco-editor";
|
|
|
+import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
|
|
|
+import CssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
|
|
|
+import HtmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
|
|
|
+import TsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
|
|
|
+import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
|
|
|
+import XcElOption from "@/components/xiao-chan/xc-el-option/XcElOption.vue";
|
|
|
+import XEUtils from "xe-utils";
|
|
|
+import {publishApi, testSqlApi, updateSqlApi} from "@/api/data-base/sql-api";
|
|
|
+import {FormInstance} from "element-plus";
|
|
|
+import {xcMessage} from "@/utils/xiaochan-element-plus";
|
|
|
+import {copyStrFunc, isDev} from "@/utils/public";
|
|
|
+import {windowSizeStore} from "@/utils/store-public";
|
|
|
+
|
|
|
+// 代码提示
|
|
|
+self.MonacoEnvironment = {
|
|
|
+ getWorker(_: string, label: string) {
|
|
|
+ if (label === 'json') {
|
|
|
+ return JsonWorker();
|
|
|
+ }
|
|
|
+ if (label === 'css' || label === 'scss' || label === 'less') {
|
|
|
+ return CssWorker();
|
|
|
+ }
|
|
|
+ if (label === 'html' || label === 'handlebars' || label === 'razor') {
|
|
|
+ return HtmlWorker();
|
|
|
+ }
|
|
|
+ if (['typescript', 'javascript'].includes(label)) {
|
|
|
+ return TsWorker();
|
|
|
+ }
|
|
|
+ return EditorWorker();
|
|
|
+ },
|
|
|
+}
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ modelValue: {
|
|
|
+ type: Object,
|
|
|
+ default: null
|
|
|
+ },
|
|
|
+ dataBase: {
|
|
|
+ type: Array,
|
|
|
+ default: []
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const urlPrefix = isDev ? `http://172.16.30.61:8706/dataBase/api/data` : `http://172.16.32.160:8077/dataBase/api/data`;
|
|
|
+
|
|
|
+const emits = defineEmits(['update:modelValue'])
|
|
|
+const modelValue: Ref<TDataBaseSqlApi> = useVModel(props, 'modelValue', emits) as Ref<TDataBaseSqlApi>
|
|
|
+const sqlEditor = ref<HTMLDivElement>()
|
|
|
+const drawerDialog = ref(false)
|
|
|
+const pageSizeList = [{code: 30, name: '30'}, {code: 50, name: '50'}, {code: 100, name: '100'}]
|
|
|
+
|
|
|
+const testParams = ref({})
|
|
|
+
|
|
|
+let monacoEditor = null
|
|
|
+
|
|
|
+function handleSave() {
|
|
|
+ const dataTemp = prepareData()
|
|
|
+ console.log(dataTemp)
|
|
|
+ updateSqlApi(dataTemp)
|
|
|
+}
|
|
|
+
|
|
|
+const testDialog = ref(false)
|
|
|
+const testFormRef = ref<FormInstance>()
|
|
|
+const testRules = ref({})
|
|
|
+const testResData = ref<{
|
|
|
+ code?: string,
|
|
|
+ message?: string
|
|
|
+ data?: any
|
|
|
+}>({})
|
|
|
+
|
|
|
+function handleDialogOpen() {
|
|
|
+ testRules.value = {}
|
|
|
+ testParams.value = {}
|
|
|
+ modelValue.value.configJson.params.forEach(item => {
|
|
|
+ testParams.value[item.key] = item.defaultValue || ''
|
|
|
+ if (item.required) {
|
|
|
+ testRules.value[item.key] = [{
|
|
|
+ required: true,
|
|
|
+ message: '此项必填',
|
|
|
+ trigger: 'change',
|
|
|
+ }]
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if (modelValue.value.configJson.pageInfo.page) {
|
|
|
+ testParams.value['currentPage'] = 1
|
|
|
+
|
|
|
+ testRules.value['currentPage'] = [{
|
|
|
+ required: true,
|
|
|
+ message: '此项必填',
|
|
|
+ trigger: 'change',
|
|
|
+ }]
|
|
|
+
|
|
|
+ }
|
|
|
+ testDialog.value = true
|
|
|
+}
|
|
|
+
|
|
|
+function prepareData() {
|
|
|
+ const data = XEUtils.clone(modelValue.value, true)
|
|
|
+ data.config = XEUtils.toJSONString(data.configJson)
|
|
|
+ return data
|
|
|
+}
|
|
|
+
|
|
|
+async function handleTest() {
|
|
|
+ await testFormRef.value.validate()
|
|
|
+
|
|
|
+ const data = prepareData()
|
|
|
+ data.params = unref(testParams)
|
|
|
+
|
|
|
+ testSqlApi(data).then(res => {
|
|
|
+ console.log(res)
|
|
|
+ testResData.value.code = '200'
|
|
|
+ testResData.value.message = '成功'
|
|
|
+ testResData.value.data = res
|
|
|
+ }).catch((res) => {
|
|
|
+ testResData.value.code = '1002'
|
|
|
+ testResData.value.message = res
|
|
|
+ testResData.value.data = null
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function handlePublishApi() {
|
|
|
+ modelValue.value.enable = modelValue.value.enable === 0 ? 1 : 0
|
|
|
+ publishApi(modelValue.value.id, modelValue.value.enable)
|
|
|
+}
|
|
|
+
|
|
|
+function handleAddParams() {
|
|
|
+ modelValue.value.configJson.params.push({
|
|
|
+ key: '',
|
|
|
+ required: false,
|
|
|
+ defaultValue: '',
|
|
|
+ describe: ''
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function handleParamsClose(index) {
|
|
|
+ modelValue.value.configJson.params.splice(index, 1)
|
|
|
+}
|
|
|
+
|
|
|
+const paramsFormMap = new Map<string, FormInstance>()
|
|
|
+const paramsFormRules = {
|
|
|
+ key: [{
|
|
|
+ required: true,
|
|
|
+ message: '此项必填',
|
|
|
+ trigger: 'change',
|
|
|
+ }]
|
|
|
+}
|
|
|
+
|
|
|
+function setParamsFormMap(name: string, ref: FormInstance) {
|
|
|
+ paramsFormMap.set(name, ref)
|
|
|
+}
|
|
|
+
|
|
|
+async function drawerClose(done: (cancel?: boolean) => void) {
|
|
|
+ for (let key of paramsFormMap.keys()) {
|
|
|
+ const value = paramsFormMap.get(key)
|
|
|
+ if (value == null) break
|
|
|
+ try {
|
|
|
+ await value.validate();
|
|
|
+ } catch (e) {
|
|
|
+ xcMessage.error('请求参数中,有必填项不能为空。')
|
|
|
+ done(true)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ done(false)
|
|
|
+}
|
|
|
+
|
|
|
+function copyUrl(url: string) {
|
|
|
+ copyStrFunc(url)
|
|
|
+}
|
|
|
+
|
|
|
+function fillingFunc() {
|
|
|
+ const str = modelValue.value.sql;
|
|
|
+ const regex = /\{(.*?)\}/g;
|
|
|
+ const matches = str.match(regex);
|
|
|
+ if (!matches) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const keys = new Set()
|
|
|
+ const addKeys = new Set()
|
|
|
+
|
|
|
+ matches.forEach(item => {
|
|
|
+ addKeys.add(item.slice(1, -1).trim())
|
|
|
+ })
|
|
|
+
|
|
|
+ modelValue.value.configJson.params.forEach(item => {
|
|
|
+ keys.add(item.key)
|
|
|
+ })
|
|
|
+
|
|
|
+ const result1 = new Set([...addKeys].filter(x => !keys.has(x)));
|
|
|
+ const result2 = new Set([...keys].filter(x => !addKeys.has(x)));
|
|
|
+
|
|
|
+ XEUtils.remove(modelValue.value.configJson.params, (item) => {
|
|
|
+ return result2.has(item.key)
|
|
|
+ })
|
|
|
+
|
|
|
+ result1.forEach(item => {
|
|
|
+ modelValue.value.configJson.params.push({
|
|
|
+ key: item,
|
|
|
+ required: false,
|
|
|
+ describe: '',
|
|
|
+ defaultValue: null
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+function processingOpenProperties() {
|
|
|
+ fillingFunc()
|
|
|
+ drawerDialog.value = true
|
|
|
+}
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ monacoEditor.dispose()
|
|
|
+})
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ await nextTick()
|
|
|
+ monacoEditor = monaco.editor.create(sqlEditor.value, {
|
|
|
+ value: modelValue.value.sql,
|
|
|
+ language: 'sql',
|
|
|
+ automaticLayout: true,
|
|
|
+ theme: 'vs-dark',
|
|
|
+ foldingStrategy: 'indentation',
|
|
|
+ formatOnType: true,
|
|
|
+ renderLineHighlight: 'all',
|
|
|
+ selectOnLineNumbers: true,
|
|
|
+ minimap: {
|
|
|
+ enabled: false,
|
|
|
+ },
|
|
|
+ readOnly: false,
|
|
|
+ fontSize: 16,
|
|
|
+ scrollBeyondLastLine: false,
|
|
|
+ overviewRulerBorder: true,
|
|
|
+ })
|
|
|
+
|
|
|
+ monacoEditor.onDidChangeModelContent((val) => {
|
|
|
+ modelValue.value.sql = monacoEditor.getValue();
|
|
|
+ })
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="api_pane-container">
|
|
|
+ <div class="api_pane-header">
|
|
|
+ <span class="iconfont icon-baocun" @click="handleSave" title="保存"/>
|
|
|
+ <span class="iconfont icon-ceshi" title="测试" @click="handleDialogOpen"></span>
|
|
|
+ <el-button
|
|
|
+ @click="handlePublishApi"
|
|
|
+ :type="modelValue.enable ? 'warning' : 'success'">
|
|
|
+ {{ modelValue.enable ? '停用' : '启用' }}
|
|
|
+ </el-button>
|
|
|
+ <el-divider direction="vertical"/>
|
|
|
+ <span style="font-size: 12px;margin: 0">
|
|
|
+ 数据库:
|
|
|
+ </span>
|
|
|
+
|
|
|
+ <!-- multiple -->
|
|
|
+ <el-select v-model="modelValue.dataBaseId"
|
|
|
+ placeholder="请选择"
|
|
|
+ size="small"
|
|
|
+ style="width: 120px">
|
|
|
+ <el-option v-for="item in props.dataBase" :label="item.name" :value="item.id"/>
|
|
|
+ </el-select>
|
|
|
+ <el-divider direction="vertical"/>
|
|
|
+ <el-button @click="processingOpenProperties" type="primary">属性</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="api_pane-main">
|
|
|
+ <div ref="sqlEditor" class="editor"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-drawer v-model="drawerDialog" title="配置" size="40%" class="api_pane-drawer" :before-close="drawerClose">
|
|
|
+ <div class="api_pane-drawer-main">
|
|
|
+ <div style="height: 70%; overflow: auto">
|
|
|
+ <el-divider style="margin: 12px 0">
|
|
|
+ 请求参数
|
|
|
+ <el-button-group>
|
|
|
+ <el-button text icon="Plus" @click="handleAddParams"/>
|
|
|
+ <el-button @click="fillingFunc">填充</el-button>
|
|
|
+ </el-button-group>
|
|
|
+ </el-divider>
|
|
|
+ <div v-for="(item,index) in modelValue.configJson.params" style="padding: 0 40px 0 20px">
|
|
|
+ <el-divider style="margin: 10px 0" border-style="dashed" v-if="index !== 0"/>
|
|
|
+ <el-form label-width="50px"
|
|
|
+ :model="item"
|
|
|
+ :rules="paramsFormRules"
|
|
|
+ label-position="right"
|
|
|
+ :ref="(el) => setParamsFormMap(`form${index}`, el)">
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="7">
|
|
|
+ <el-form-item label="名称" prop="key">
|
|
|
+ <el-input v-model.trim="item.key"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="7">
|
|
|
+ <el-form-item label="必填">
|
|
|
+ <el-switch v-model.trim="item.required"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="10">
|
|
|
+ <el-form-item label="默认值">
|
|
|
+ <el-input v-model.trim="item.defaultValue"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-form-item label="描述">
|
|
|
+ <div style="width: 100%;position: relative">
|
|
|
+ <div class="params_close">
|
|
|
+ <el-icon :size="18" @click="handleParamsClose(index)">
|
|
|
+ <CircleClose/>
|
|
|
+ </el-icon>
|
|
|
+ </div>
|
|
|
+ <el-input v-model.trim="item.describe"/>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div style="flex: 1 ;overflow: auto">
|
|
|
+ <div style="padding: 0 20px">
|
|
|
+ <el-divider style="margin: 10px 0">其他</el-divider>
|
|
|
+ <el-form label-width="70px" label-position="right">
|
|
|
+ <el-row :gutter="10">
|
|
|
+
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-form-item label="去除空格">
|
|
|
+ <el-switch v-model="modelValue.configJson.removeSpaces"></el-switch>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-form-item label="请求方式">
|
|
|
+ <el-select v-model="modelValue.requestMapping">
|
|
|
+ <xc-el-option :data="[{code: 'GET', name: 'GET'},{code: 'POST', name: 'POST'}]"/>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-form-item label="分页">
|
|
|
+ <el-switch v-model="modelValue.configJson.pageInfo.page"></el-switch>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="24" v-if="modelValue.configJson.pageInfo.page">
|
|
|
+ <el-form-item label="页大写">
|
|
|
+ <el-select v-model="modelValue.configJson.pageInfo.pageSize">
|
|
|
+ <xc-el-option :data="pageSizeList"/>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-drawer>
|
|
|
+
|
|
|
+ <el-dialog title="预览" v-model="testDialog" fullscreen>
|
|
|
+ <el-descriptions
|
|
|
+ class="margin-top"
|
|
|
+ border
|
|
|
+ :title="modelValue.name"
|
|
|
+ :column="3"
|
|
|
+ size="small">
|
|
|
+ <el-descriptions-item label="地址" :span="3">
|
|
|
+ {{ `${urlPrefix}/${modelValue.id}` }}
|
|
|
+ <el-button icon="CopyDocument" circle
|
|
|
+ @click="copyUrl(`${urlPrefix}/${modelValue.id}`)"/>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="请求方式">
|
|
|
+ {{ modelValue.requestMapping }}
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="分页">
|
|
|
+ {{ modelValue.configJson.pageInfo.page ? `分页,页大小${modelValue.configJson.pageInfo.pageSize}` : '不分页' }}
|
|
|
+ </el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+
|
|
|
+ <el-divider border-style="dashed">参数</el-divider>
|
|
|
+
|
|
|
+ <el-form label-width="100px"
|
|
|
+ label-position="right"
|
|
|
+ :rules="testRules"
|
|
|
+ :model="testParams"
|
|
|
+ ref="testFormRef">
|
|
|
+ <el-form-item v-for="item in modelValue.configJson.params"
|
|
|
+ :label="item.key"
|
|
|
+ :prop="item.key">
|
|
|
+ <el-input v-model="testParams[item.key]"/>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="页码" prop="currentPage" v-if="modelValue.configJson.pageInfo.page">
|
|
|
+ <el-input-number v-model="testParams.currentPage"/>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" @click="handleTest">测试</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <el-divider border-style="dashed">结果 {{ modelValue.configJson.pageInfo.page ? '' : '测试环境默认10条' }}
|
|
|
+ </el-divider>
|
|
|
+
|
|
|
+ <div :style="{height: windowSizeStore.h / 1.8 + 'px'}" style="overflow: auto">
|
|
|
+ <JsonViewer :value="testResData"
|
|
|
+ style="height: 100%" copyable :expandDepth="3"/>
|
|
|
+ </div>
|
|
|
+
|
|
|
+
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+</template>
|
|
|
+
|
|
|
+<style lang="scss">
|
|
|
+.api_pane-container {
|
|
|
+ display: flex;
|
|
|
+ height: 100%;
|
|
|
+ flex-flow: column nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.api_pane-header {
|
|
|
+ line-height: 20px;
|
|
|
+ font-size: 20px;
|
|
|
+ padding: 5px 10px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ border-right: 1px solid #E4E7ED;
|
|
|
+ border-left: 1px solid #E4E7ED;
|
|
|
+
|
|
|
+ .iconfont {
|
|
|
+ cursor: pointer;
|
|
|
+ margin: 0 10px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.api_pane-main {
|
|
|
+ flex: 1;
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ .editor {
|
|
|
+ height: 100%;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.api_pane-database {
|
|
|
+ padding: 0 10px;
|
|
|
+ height: 40px;
|
|
|
+}
|
|
|
+
|
|
|
+.api_pane-drawer {
|
|
|
+ .api_pane-drawer-main {
|
|
|
+ height: 100%;
|
|
|
+ width: 100%;
|
|
|
+ overflow: auto;
|
|
|
+ display: flex;
|
|
|
+ flex-flow: column nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-drawer__header {
|
|
|
+ margin-bottom: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-drawer__body {
|
|
|
+ padding: 5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .params_close {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ right: -30px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ height: 100%;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|