test.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. <template>
  2. <el-dialog class="api-test" v-model="showDialog" title="测试API" width="800px" :close-on-click-modal="false" :close-on-press-escape="false">
  3. <el-descriptions :column="2" border>
  4. <el-descriptions-item label="API名称" :span="2">{{ apiData.name }}</el-descriptions-item>
  5. <el-descriptions-item label="API路径" :span="2">
  6. <div class="api-path-container">
  7. <span class="domain">{{ originUrl }}/apihub/</span>
  8. <span class="path">{{ apiData.path }}</span>
  9. <el-tooltip content="复制API完整路径" placement="top">
  10. <el-icon class="copy-icon" @click="copyApiPath"><CopyDocument /></el-icon>
  11. </el-tooltip>
  12. </div>
  13. </el-descriptions-item>
  14. <el-descriptions-item label="请求方法">
  15. <el-tag :type="getMethodTagType(apiData.method)" size="small">{{ apiData.method }}</el-tag>
  16. </el-descriptions-item>
  17. <el-descriptions-item label="数据源">{{ apiData.dataSourceName }}</el-descriptions-item>
  18. </el-descriptions>
  19. <div class="section-title">参数设置</div>
  20. <el-form :model="testParams" label-width="120px" v-if="apiData.parameters && apiData.parameters.length">
  21. <el-form-item v-for="param in apiData.parameters" :key="param.name" :label="param.name" :required="param.required">
  22. <el-input v-if="param.type === 'string'" v-model="testParams[param.name]" :placeholder="getParamPlaceholder(param)"></el-input>
  23. <el-input-number v-else-if="param.type === 'int' || param.type === 'float'" v-model="testParams[param.name]" :placeholder="getParamPlaceholder(param)"></el-input-number>
  24. <el-switch v-else-if="param.type === 'bool'" v-model="testParams[param.name]" :active-value="true" :inactive-value="false"></el-switch>
  25. <el-input v-else v-model="testParams[param.name]" :placeholder="getParamPlaceholder(param)"></el-input>
  26. <div class="param-desc" v-if="param.description">{{ param.description }}</div>
  27. </el-form-item>
  28. </el-form>
  29. <el-empty description="该API没有定义参数" v-else></el-empty>
  30. <div class="section-title">测试结果</div>
  31. <div v-if="!testResult.success && !loading" class="test-placeholder">点击下方"执行测试"按钮开始测试</div>
  32. <div v-else>
  33. <el-alert :title="testResult.message" :type="testResult.success ? 'success' : 'error'" :closable="false" show-icon></el-alert>
  34. <div v-if="testResult.success && testResult.data" class="result-container">
  35. <el-tabs v-model="activeTab">
  36. <el-tab-pane label="结果数据" name="data">
  37. <pre class="result-json">{{ formatJson(testResult.data) }}</pre>
  38. </el-tab-pane>
  39. <el-tab-pane label="原始响应" name="raw">
  40. <pre class="result-json">{{ formatJson(testResult) }}</pre>
  41. </el-tab-pane>
  42. </el-tabs>
  43. </div>
  44. </div>
  45. <template #footer>
  46. <div class="dialog-footer">
  47. <el-button @click="closeDialog">关闭</el-button>
  48. <el-button type="primary" @click="runTest" :loading="loading">执行测试</el-button>
  49. </div>
  50. </template>
  51. </el-dialog>
  52. </template>
  53. <script lang="ts" setup>
  54. import { ref, reactive, watch } from "vue";
  55. import apiHub from "/@/api/modules/apiHub";
  56. import { ElMessage } from "element-plus";
  57. import { CopyDocument } from "@element-plus/icons-vue";
  58. import getOrigin from "/@/utils/origin";
  59. const showDialog = ref(false);
  60. const loading = ref(false);
  61. const activeTab = ref("data");
  62. const originUrl: string = getOrigin("");
  63. // API数据
  64. const apiData = reactive<any>({
  65. id: null,
  66. name: "",
  67. path: "",
  68. method: "",
  69. dataSourceId: null,
  70. dataSourceName: "",
  71. sqlType: "",
  72. sqlContent: "",
  73. parameters: [],
  74. returnFormat: "",
  75. version: "",
  76. status: "",
  77. plugins: [],
  78. description: "",
  79. createdAt: "",
  80. updatedAt: "",
  81. });
  82. // 测试参数
  83. const testParams = reactive<any>({});
  84. // 测试结果
  85. const testResult = reactive<any>({
  86. success: false,
  87. message: "",
  88. data: null,
  89. });
  90. // 监听API数据变化,初始化测试参数
  91. watch(
  92. () => apiData.parameters,
  93. (newParams) => {
  94. // 清空测试参数
  95. Object.keys(testParams).forEach((key) => {
  96. delete testParams[key];
  97. });
  98. // 根据参数定义初始化测试参数
  99. if (newParams && newParams.length) {
  100. newParams.forEach((param: any) => {
  101. // 如果有默认值,使用默认值
  102. if (param.defaultValue !== undefined && param.defaultValue !== "") {
  103. // 根据类型转换默认值
  104. if (param.type === "int") {
  105. testParams[param.name] = parseInt(param.defaultValue);
  106. } else if (param.type === "float") {
  107. testParams[param.name] = parseFloat(param.defaultValue);
  108. } else if (param.type === "bool") {
  109. testParams[param.name] = param.defaultValue === "true";
  110. } else {
  111. testParams[param.name] = param.defaultValue;
  112. }
  113. } else {
  114. // 否则设置为空值
  115. if (param.type === "int" || param.type === "float") {
  116. testParams[param.name] = null;
  117. } else if (param.type === "bool") {
  118. testParams[param.name] = false;
  119. } else {
  120. testParams[param.name] = "";
  121. }
  122. }
  123. });
  124. }
  125. },
  126. { deep: true }
  127. );
  128. // 根据请求方法返回不同的标签类型
  129. const getMethodTagType = (method: string) => {
  130. switch (method?.toUpperCase()) {
  131. case "GET":
  132. return "success";
  133. case "POST":
  134. return "primary";
  135. case "PUT":
  136. return "warning";
  137. case "DELETE":
  138. return "danger";
  139. default:
  140. return "info";
  141. }
  142. };
  143. // 获取参数占位符文本
  144. const getParamPlaceholder = (param: any) => {
  145. if (param.required) {
  146. return `请输入${param.description || param.name}(必填)`;
  147. }
  148. return `请输入${param.description || param.name}(选填)`;
  149. };
  150. // 格式化JSON
  151. const formatJson = (json: any) => {
  152. try {
  153. return JSON.stringify(json, null, 2);
  154. } catch (e) {
  155. return json;
  156. }
  157. };
  158. // 关闭对话框
  159. const closeDialog = () => {
  160. showDialog.value = false;
  161. };
  162. // 执行测试
  163. const runTest = async () => {
  164. // 重置测试结果
  165. testResult.success = false;
  166. testResult.message = "";
  167. testResult.data = null;
  168. loading.value = true;
  169. apiHub
  170. .test({
  171. id: apiData.id,
  172. parameters: testParams,
  173. })
  174. .then((res: any) => {
  175. testResult.success = true;
  176. testResult.message = "测试成功";
  177. testResult.data = res;
  178. })
  179. .catch((error: any) => {
  180. testResult.success = false;
  181. testResult.message = error?.message || "测试失败";
  182. })
  183. .finally(() => {
  184. loading.value = false;
  185. });
  186. };
  187. // 复制API完整路径
  188. const copyApiPath = () => {
  189. // 实现复制功能
  190. const fullPath = `${originUrl}/apihub/${apiData.path}`;
  191. // 使用Clipboard API复制到剪贴板
  192. navigator.clipboard.writeText(fullPath)
  193. .then(() => {
  194. ElMessage.success('已复制API完整路径到剪贴板');
  195. })
  196. .catch(() => {
  197. ElMessage.error('复制失败,请手动复制');
  198. });
  199. };
  200. // 打开对话框
  201. const open = async (row: any) => {
  202. // 清空数据
  203. Object.keys(apiData).forEach((key) => {
  204. apiData[key] = undefined;
  205. });
  206. // 重置测试结果
  207. testResult.success = false;
  208. testResult.message = "";
  209. testResult.data = null;
  210. showDialog.value = true;
  211. // 实际使用时,应该调用API获取详细信息
  212. // const res = await apiHub.get(row.id)
  213. // Object.assign(apiData, res)
  214. // 这里模拟直接使用传入的行数据
  215. Object.assign(apiData, row);
  216. };
  217. defineExpose({ open });
  218. </script>
  219. <style scoped>
  220. .dialog-footer {
  221. display: flex;
  222. justify-content: flex-end;
  223. }
  224. .section-title {
  225. font-weight: bold;
  226. margin: 20px 0 10px;
  227. font-size: 16px;
  228. border-left: 4px solid #409eff;
  229. padding-left: 10px;
  230. }
  231. .param-desc {
  232. font-size: 12px;
  233. color: #909399;
  234. margin-top: 5px;
  235. }
  236. .test-placeholder {
  237. text-align: center;
  238. color: #909399;
  239. padding: 20px;
  240. background-color: #f5f7fa;
  241. border-radius: 4px;
  242. }
  243. /* 深色主题下的样式 */
  244. [data-theme='dark'] .test-placeholder {
  245. text-align: center;
  246. color: #e4e3e3;
  247. padding: 20px;
  248. background-color: #424040;
  249. border-radius: 4px;
  250. }
  251. .result-container {
  252. margin-top: 15px;
  253. border: 1px solid #e4e7ed;
  254. border-radius: 4px;
  255. }
  256. /* 深色主题下的样式 */
  257. [data-theme='dark'] .result-container {
  258. margin-top: 15px;
  259. border: 1px solid #575656;
  260. border-radius: 4px;
  261. }
  262. .result-json {
  263. background-color: #f5f7fa;
  264. padding: 10px;
  265. font-family: monospace;
  266. white-space: pre-wrap;
  267. word-break: break-all;
  268. margin: 0;
  269. max-height: 300px;
  270. overflow: auto;
  271. }
  272. /* 深色主题下的样式 */
  273. [data-theme='dark'] .result-json {
  274. background-color: #3c3b3b;
  275. color: #ffffff;
  276. padding: 10px;
  277. font-family: monospace;
  278. white-space: pre-wrap;
  279. word-break: break-all;
  280. margin: 0;
  281. max-height: 300px;
  282. overflow: auto;
  283. }
  284. .el-empty{
  285. height: 120px;
  286. }
  287. .api-path-container {
  288. display: inline-block;
  289. text-decoration: none;
  290. color: black; /* 设置整体文字颜色 */
  291. align-items: center; /* 垂直居中对齐 */
  292. }
  293. /* 深色主题下的样式 */
  294. [data-theme='dark'] .api-path-container {
  295. display: inline-block;
  296. text-decoration: none;
  297. color: #fff; /* 设置整体文字颜色 */
  298. }
  299. .api-path-container .domain {
  300. background-color: #eee; /* 设置域名部分的背景颜色 */
  301. color: black; /* 设置域名部分的文字颜色 */
  302. padding: 2px 4px; /* 添加一些内边距 */
  303. display: inline-block;
  304. border-radius: 15px; /* 增加圆角 */
  305. }
  306. /* 深色主题下的样式 */
  307. [data-theme='dark'] .api-path-container .domain{
  308. background-color: #3c3b3b; /* 设置域名部分的背景颜色 */
  309. color: white; /* 设置域名部分的文字颜色 */
  310. padding: 2px 4px; /* 添加一些内边距 */
  311. display: inline-block;
  312. border-radius: 15px; /* 增加圆角 */
  313. }
  314. .api-path-container .path {
  315. display: inline-block;
  316. }
  317. .copy-icon {
  318. cursor: pointer;
  319. color: var(--el-color-primary);
  320. transition: all 0.2s;
  321. font-size: 24px;
  322. margin-left: 8px;
  323. padding: 4px;
  324. border-radius: 4px;
  325. }
  326. .copy-icon:hover {
  327. transform: scale(1.1);
  328. color: var(--el-color-primary-dark-2);
  329. background-color: var(--el-color-primary-light-9);
  330. }
  331. </style>