feat(全局): 接口调试-后置条件&部分组件调整
This commit is contained in:
parent
1223602106
commit
6ee926c091
|
@ -76,6 +76,7 @@ module.exports = {
|
|||
'no-underscore-dangle': 'off',
|
||||
'vue/attributes-order': 1,
|
||||
'simple-import-sort/exports': 'error',
|
||||
'no-case-declarations': 'off',
|
||||
// 调整导入语句的顺序
|
||||
'simple-import-sort/imports': [
|
||||
'error',
|
||||
|
@ -97,6 +98,7 @@ module.exports = {
|
|||
'^color$',
|
||||
'^localforage$',
|
||||
'vue-draggable-plus',
|
||||
'jsonpath-plus',
|
||||
], // node依赖
|
||||
['.*/assets/.*', '^@/assets$'], // 项目静态资源
|
||||
['^@/components/pure/.*', '^@/components/business/.*', '.*\\.vue$'], // 组件
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
"@tiptap/vue-3": "^2.1.13",
|
||||
"@types/color": "^3.0.4",
|
||||
"@vueuse/core": "^10.4.1",
|
||||
"@xmldom/xmldom": "^0.8.10",
|
||||
"ace-builds": "^1.24.2",
|
||||
"ahooks-vue": "^0.15.1",
|
||||
"axios": "^1.6.5",
|
||||
|
@ -56,6 +57,7 @@
|
|||
"fastq": "^1.15.0",
|
||||
"hotbox-minder": "1.0.15",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"jsonpath-plus": "^8.0.0",
|
||||
"localforage": "^1.10.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
|
@ -75,7 +77,9 @@
|
|||
"vue-i18n": "^9.3.0",
|
||||
"vue-router": "^4.2.4",
|
||||
"vue3-ace-editor": "^2.2.3",
|
||||
"vue3-colorpicker": "^2.2.2"
|
||||
"vue3-colorpicker": "^2.2.2",
|
||||
"xml-beautify": "^1.2.3",
|
||||
"xpath": "^0.0.34"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@arco-plugins/vite-vue": "^1.4.5",
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import MSR from '@/api/http/index';
|
||||
import { GetApiTestList, GetApiTestListUrl } from '@/api/requrls/api-test';
|
||||
|
||||
import { APIListItemI } from '@/models/api-test';
|
||||
import { CommonList, TableQueryParams } from '@/models/common';
|
||||
|
||||
export function getTableList(params: TableQueryParams) {
|
||||
const { current, pageSize, sort, filter, keyword } = params;
|
||||
return MSR.post<CommonList<APIListItemI>>({
|
||||
return MSR.post<CommonList<any>>({
|
||||
url: GetApiTestList,
|
||||
data: { current, pageSize, sort, filter, keyword, projectId: 'test-project-id' },
|
||||
});
|
||||
}
|
||||
|
||||
export function getlist() {
|
||||
return MSR.get<CommonList<APIListItemI>>({ url: GetApiTestListUrl });
|
||||
return MSR.get<CommonList<any>>({ url: GetApiTestListUrl });
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@font-face {
|
||||
font-family: iconfont; /* Project id 3462279 */
|
||||
src: url('iconfont.woff2?t=1702433539155') format('woff2'), url('iconfont.woff?t=1702433539155') format('woff'),
|
||||
url('iconfont.ttf?t=1702433539155') format('truetype'), url('iconfont.svg?t=1702433539155#iconfont') format('svg');
|
||||
src: url('iconfont.woff2?t=1705549750803') format('woff2'), url('iconfont.woff?t=1705549750803') format('woff'),
|
||||
url('iconfont.ttf?t=1705549750803') format('truetype'), url('iconfont.svg?t=1705549750803#iconfont') format('svg');
|
||||
}
|
||||
.iconfont {
|
||||
font-size: 16px;
|
||||
|
@ -10,6 +10,18 @@
|
|||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.icon-icon_carriage_return2::before {
|
||||
content: '\e79a';
|
||||
}
|
||||
.icon-icon_carriage_return1::before {
|
||||
content: '\e799';
|
||||
}
|
||||
.icon-icon_swagger::before {
|
||||
content: '\e798';
|
||||
}
|
||||
.icon-a-icon_file-json::before {
|
||||
content: '\e797';
|
||||
}
|
||||
.icon-icon_keyboard::before {
|
||||
content: '\e796';
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,6 +5,34 @@
|
|||
"css_prefix_text": "icon-",
|
||||
"description": "DE、MS项目icon管理",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "38923289",
|
||||
"name": "icon_carriage_return",
|
||||
"font_class": "icon_carriage_return2",
|
||||
"unicode": "e79a",
|
||||
"unicode_decimal": 59290
|
||||
},
|
||||
{
|
||||
"icon_id": "38923258",
|
||||
"name": "icon_delete",
|
||||
"font_class": "icon_carriage_return1",
|
||||
"unicode": "e799",
|
||||
"unicode_decimal": 59289
|
||||
},
|
||||
{
|
||||
"icon_id": "38884170",
|
||||
"name": "icon_swagger",
|
||||
"font_class": "icon_swagger",
|
||||
"unicode": "e798",
|
||||
"unicode_decimal": 59288
|
||||
},
|
||||
{
|
||||
"icon_id": "38747707",
|
||||
"name": "icon_file- json",
|
||||
"font_class": "a-icon_file-json",
|
||||
"unicode": "e797",
|
||||
"unicode_decimal": 59287
|
||||
},
|
||||
{
|
||||
"icon_id": "38499852",
|
||||
"name": "icon_keyboard",
|
||||
|
|
|
@ -14,6 +14,14 @@
|
|||
/>
|
||||
<missing-glyph />
|
||||
|
||||
<glyph glyph-name="icon_carriage_return2" unicode="" d="M311.168 499.498667a42.666667 42.666667 0 1 0 60.330667-60.330667L273.706667 341.333333H789.333333a21.333333 21.333333 0 0 1 21.333334 21.333334V640a42.666667 42.666667 0 0 0 85.333333 0v-277.333333a106.666667 106.666667 0 0 0-106.666667-106.666667H273.706667l97.792-97.834667a42.666667 42.666667 0 0 0 3.541333-56.32l-3.541333-4.010666a42.666667 42.666667 0 0 0-60.330667 0l-170.666667 170.666666-0.938666 0.938667a42.922667 42.922667 0 0 0-2.474667 2.901333l3.413333-3.84A43.008 43.008 0 0 0 128 297.813333V299.648c0 0.938667 0.085333 1.877333 0.170667 2.816L128 298.666667a43.008 43.008 0 0 0 9.088 26.325333c1.066667 1.322667 2.176 2.645333 3.413333 3.84l170.666667 170.666667z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="icon_carriage_return1" unicode="" d="M896 725.333333a85.333333 85.333333 0 0 0 85.333333-85.333333v-512a85.333333 85.333333 0 0 0-85.333333-85.333333H336.810667a85.333333 85.333333 0 0 0-58.24 22.954666l-4.608 5.077334-222.378667 287.146666a42.666667 42.666667 0 0 0 0 52.266667l222.378667 287.189333 4.608 5.077334A85.333333 85.333333 0 0 0 336.810667 725.333333H896z m0-85.333333H337.493333l-198.229333-256 198.272-256H896V640z m-396.501333-119.168l76.501333-76.458667 76.501333 76.458667a42.666667 42.666667 0 0 0 60.330667-60.330667L636.373333 384l76.458667-76.501333a42.666667 42.666667 0 0 0-60.330667-60.330667L576 323.626667l-76.501333-76.458667a42.666667 42.666667 0 0 0-60.330667 60.330667L515.626667 384l-76.458667 76.501333a42.666667 42.666667 0 0 0 60.330667 60.330667z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="icon_swagger" unicode="" d="M298.666667 853.333333a42.666667 42.666667 0 1 0 0-85.333333c-39.125333 0-64-18.688-64-64V512c0-42.624-24.533333-77.184-63.573334-106.496l-3.456-2.474667 3.2 2.304c36.693333-27.52 60.501333-59.477333 63.488-98.261333L234.666667 298.666667v-234.666667c0-45.354667 24.874667-64 64-64a42.666667 42.666667 0 0 0 0-85.333333c-81.792 0-149.333333 50.688-149.333334 149.333333V298.666667c0 10.538667-12.074667 26.24-35.114666 42.368a249.984 249.984 0 0 1-44.714667 24.704c-35.797333 14.293333-35.797333 64.896 0 79.189333a249.984 249.984 0 0 1 44.714667 24.661333c23.04 16.170667 35.114667 31.872 35.114666 42.410667V704C149.333333 802.688 216.874667 853.333333 298.666667 853.333333z m426.666666 0c81.792 0 149.333333-50.688 149.333334-149.333333V512c0-10.538667 12.074667-26.24 35.114666-42.368a249.984 249.984 0 0 1 44.714667-24.704c35.797333-14.293333 35.797333-64.896 0-79.189333a249.984 249.984 0 0 1-44.714667-24.661334c-23.04-16.170667-35.114667-31.872-35.114666-42.410666v-234.666667c0-98.688-67.541333-149.333333-149.333334-149.333333a42.666667 42.666667 0 0 0 0 85.333333c39.125333 0 64 18.688 64 64V298.666667c0 42.624 24.533333 77.184 63.573334 106.496-36.437333 27.733333-60.245333 59.648-63.232 98.432L789.333333 512V704c0 45.354667-24.874667 64-64 64a42.666667 42.666667 0 0 0 0 85.333333z m-362.666666-384a64 64 0 1 0 0-128 64 64 0 0 0 0 128z m170.666666 0a64 64 0 1 0 0-128 64 64 0 0 0 0 128z m170.666667 0a64 64 0 1 0 0-128 64 64 0 0 0 0 128z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="a-icon_file-json" unicode="" d="M648.533333 853.333333a42.666667 42.666667 0 0 0 33.322667-16l170.325333-212.906666 1.792-2.346667 0.213334-0.341333 0.426666-0.554667 0.426667-0.768-0.853333 1.322667-1.664 2.261333 2.730666-3.925333 1.152-1.877334a40.021333 40.021333 0 0 0 4.053334-9.941333 41.258667 41.258667 0 0 0 1.109333-5.973333L861.866667 597.333333v-85.333333h42.666666a85.333333 85.333333 0 0 0 85.333334-85.333333v-298.666667a85.333333 85.333333 0 0 0-85.333334-85.333333h-42.666666v-85.333334a42.666667 42.666667 0 0 0-42.666667-42.666666h-597.333333a42.666667 42.666667 0 0 0-42.666667 42.666666v85.333334h-42.666667a85.333333 85.333333 0 0 0-85.333333 85.333333v298.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h42.666667V810.666667a42.666667 42.666667 0 0 0 42.666667 42.666666h426.666666z m128-810.666666h-512v-42.666667h512v42.666667z m128 384h-768v-298.666667h768v298.666667z m-341.333333 341.333333h-298.666667v-256h512V554.666667h-170.666666a42.666667 42.666667 0 0 0-42.666667 42.666666V768z m85.333333-25.6V640h81.92L648.533333 742.4z m-341.333333-374.698667v-141.056c0-17.749333-3.157333-32.384-9.898667-43.733333-11.690667-19.797333-32.512-29.525333-60.629333-29.525333-27.818667 0-47.957333 7.850667-58.88 24.618666-9.642667 14.848-14.293333 34.346667-14.293333 58.282667v17.066667h61.952v-16.768c0.298667-12.544 1.621333-21.077333 3.413333-24.789334 0.426667-0.810667 1.706667-1.450667 6.4-1.450666 4.48 0 5.76 0.682667 6.4 2.133333 0.810667 1.621333 1.450667 6.4 1.450667 13.568v141.653333H307.2z m277.589333 5.632c29.568 0 53.034667-8.362667 68.992-24.832 21.504-19.584 32.042667-48.341333 32.042667-85.461333 0-36.394667-10.496-65.109333-31.488-84.906667-16.512-17.066667-39.978667-25.386667-69.546667-25.386666-29.568 0-53.034667 8.362667-69.12 24.96-14.08 13.226667-23.466667 30.165333-28.288 50.474666l0.213334-3.626666c0-20.565333-8.106667-38.101333-23.765334-51.626667-15.402667-13.354667-36.565333-19.797333-62.933333-19.797333-26.666667 0-48.341333 6.272-64.597333 19.2-16.853333 13.482667-25.429333 32.256-25.429334 55.210666v12.8h55.594667l-0.426667 0.085334a114.986667 114.986667 0 0 0-23.808 9.344l-4.181333 2.56c-16.085333 11.050667-24.192 28.373333-24.192 50.474666 0 20.053333 7.509333 37.205333 22.186667 50.56 14.634667 13.312 35.328 19.712 61.482666 19.712 22.101333 0 41.386667-5.973333 57.386667-18.005333 16.938667-12.714667 25.898667-31.274667 26.666667-54.613333l0.426666-13.226667H432.64l4.693333-1.237333c9.6-2.816 17.706667-6.272 24.277334-10.453334l4.650666-3.328c8.917333-7.04 15.061333-16.042667 18.346667-26.752a144 144 0 0 0-0.938667 17.578667c0 37.12 10.581333 65.92 31.573334 84.906667 16.554667 17.066667 40.021333 25.386667 69.589333 25.386666zM371.754667 238.933333l1.322666-9.642666c0.853333-6.272 2.389333-10.410667 4.096-12.501334 3.456-4.181333 10.538667-6.698667 22.229334-6.698666 7.765333 0 13.738667 0.853333 17.834666 2.261333 5.802667 2.048 7.637333 4.48 7.637334 9.301333 0 1.621333-0.384 2.261333-1.877334 3.2-3.882667 2.389333-11.050667 4.821333-21.248 7.082667l-19.541333 4.352a319.744 319.744 0 0 0-8.448 2.048l-2.005333 0.554667z m213.034666 76.202667c-11.434667 0-19.925333-3.925333-26.538666-12.117333-7.04-8.746667-10.794667-21.930667-10.794667-39.978667 0-18.048 3.754667-31.232 10.794667-39.978667 6.613333-8.192 15.104-12.074667 26.538666-12.074666s19.84 3.882667 26.368 12.032l2.730667 4.010666c5.12 8.533333 7.850667 20.48 7.850667 36.010667 0 18.005333-3.712 31.189333-10.666667 39.978667-6.485333 8.192-14.890667 12.117333-26.282667 12.117333z m-189.866666 0.341333c-7.552 0-13.013333-1.365333-16.597334-3.797333-2.389333-1.621333-3.242667-3.2-3.242666-6.144 0-1.92 0.426667-2.56 2.346666-3.669333 2.346667-1.322667 9.514667-3.626667 20.608-6.272l21.461334-5.12-0.469334 8.789333c-0.341333 5.376-1.877333 8.874667-4.992 11.349333l-2.56 1.706667a36.309333 36.309333 0 0 1-16.554666 3.157333z m354.688 52.224l3.669333-6.442666 49.493333-86.741334v93.184h61.44V157.866667H805.546667l-3.669334 6.4-51.413333 89.344 0.042667-95.744h-61.44v209.834666h60.586666z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="icon_keyboard" unicode="" d="M853.333333 810.666667a42.666667 42.666667 0 0 0 42.666667-42.666667v-109.354667a64 64 0 0 0-64-64l-85.333333 0.042667V554.666667H896a85.333333 85.333333 0 0 0 85.333333-85.333334v-426.666666a85.333333 85.333333 0 0 0-85.333333-85.333334H128a85.333333 85.333333 0 0 0-85.333333 85.333334V469.333333a85.333333 85.333333 0 0 0 85.333333 85.333334h533.333333V616.021333a64 64 0 0 0 64 64h85.333334V768a42.666667 42.666667 0 0 0 37.674666 42.368L853.333333 810.666667z m42.666667-341.333334H128v-426.666666h768V469.333333z m-234.666667-298.666666a42.666667 42.666667 0 0 0 0-85.333334h-298.666666a42.666667 42.666667 0 0 0 0 85.333334h298.666666zM341.333333 298.666667a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z m-128 0a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z m256 0a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z m128 0a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z m128 0a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334zM298.666667 426.666667a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z m128 0a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z m128 0a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z m128 0a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z m128 0a42.666667 42.666667 0 1 0 0-85.333334 42.666667 42.666667 0 0 0 0 85.333334z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="icon_split-turn-down-left" unicode="" d="M746.666667 853.333333a149.333333 149.333333 0 0 0 42.666666-292.48v-501.674666l33.834667 33.792a42.666667 42.666667 0 0 0 56.32 3.541333l4.010667-3.541333a42.666667 42.666667 0 0 0 0-60.330667l-106.666667-106.666667a42.666667 42.666667 0 0 0-60.330667 0l-106.666666 106.666667a42.666667 42.666667 0 0 0 60.330666 60.330667l33.834667-33.834667v323.669333h-298.666667a128 128 0 0 1-128-128v-195.626666l33.834667 33.792a42.666667 42.666667 0 0 0 56.32 3.541333l4.010667-3.541333a42.666667 42.666667 0 0 0 0-60.330667l-106.666667-106.666667a42.922667 42.922667 0 0 0-3.84-3.413333l3.84 3.413333a43.008 43.008 0 0 0-28.757333-12.501333h-2.773334c-0.768 0-1.536 0.085333-2.304 0.170667l3.669334-0.170667a43.008 43.008 0 0 0-26.325334 9.088 43.349333 43.349333 0 0 0-3.84 3.413333l-106.666666 106.666667a42.666667 42.666667 0 0 0 60.330666 60.330667l33.834667-33.834667v195.669333a213.333333 213.333333 0 0 0 213.333333 213.333334h298.666667V560.853333A149.418667 149.418667 0 0 0 746.666667 853.333333z m0-85.333333a64 64 0 1 1 0-128 64 64 0 0 1 0 128z" horiz-adv-x="1024" />
|
||||
|
|
Before Width: | Height: | Size: 420 KiB After Width: | Height: | Size: 427 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -177,6 +177,12 @@
|
|||
.btn-text-sec-active();
|
||||
.btn-text-sec-disabled();
|
||||
}
|
||||
.arco-btn-text--secondary {
|
||||
color: var(--color-text-3) !important;
|
||||
.btn-text-sec-hover();
|
||||
.btn-text-sec-active();
|
||||
.btn-text-sec-disabled();
|
||||
}
|
||||
.arco-btn-text--danger {
|
||||
color: rgb(var(--danger-6)) !important;
|
||||
.btn-text-danger-hover();
|
||||
|
|
|
@ -211,7 +211,7 @@
|
|||
@select="selectAutoComplete"
|
||||
>
|
||||
<template #suffix>
|
||||
<MsIcon type="icon-icon_mock" class="ms-params-input-mock-icon" @click.stop="openParamSetting" />
|
||||
<MsIcon type="icon-icon_mock" class="ms-params-input-suffix-icon" @click.stop="openParamSetting" />
|
||||
</template>
|
||||
<template #option="{ data }">
|
||||
<div class="w-[350px]">
|
||||
|
@ -588,17 +588,23 @@
|
|||
}
|
||||
}
|
||||
.ms-params-input {
|
||||
.ms-params-input-mock-icon {
|
||||
.ms-params-input-suffix-icon,
|
||||
.ms-params-input-suffix-icon--disabled {
|
||||
@apply invisible;
|
||||
}
|
||||
&:hover,
|
||||
&.arco-input-focus {
|
||||
.ms-params-input-mock-icon {
|
||||
.ms-params-input-suffix-icon {
|
||||
@apply visible cursor-pointer;
|
||||
&:hover {
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
}
|
||||
.ms-params-input-suffix-icon--disabled {
|
||||
@apply visible cursor-not-allowed;
|
||||
|
||||
color: rgb(var(--primary-3));
|
||||
}
|
||||
}
|
||||
:deep(.arco-select-option) {
|
||||
@apply flex flex-1 p-10;
|
||||
|
@ -606,11 +612,16 @@
|
|||
}
|
||||
.ms-params-input--focus {
|
||||
border-color: rgb(var(--primary-5)) !important;
|
||||
.ms-params-input-mock-icon {
|
||||
.ms-params-input-suffix-icon {
|
||||
@apply visible cursor-pointer;
|
||||
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
.ms-params-input-suffix-icon--disabled {
|
||||
@apply visible cursor-not-allowed;
|
||||
|
||||
color: rgb(var(--primary-3));
|
||||
}
|
||||
}
|
||||
.ms-params-input-trigger {
|
||||
width: 350px;
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
<template>
|
||||
<pre ref="jr" :class="props.class" @click="pickPath"></pre>
|
||||
<input ref="ip" :value="jsonPath" class="path" type="hidden" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, Ref, ref } from 'vue';
|
||||
|
||||
import JPPicker from '@/assets/js/jsonpath-picker-vanilla/jsonpath-picker-vanilla';
|
||||
|
||||
import { Recordable } from '#/global';
|
||||
|
||||
const jr: Ref<HTMLElement | null> = ref(null);
|
||||
const ip: Ref<HTMLInputElement | null> = ref(null);
|
||||
const jsonPath = ref('');
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data: object;
|
||||
opt?: Recordable<any>;
|
||||
class?: string;
|
||||
}>(),
|
||||
{
|
||||
data: () => ({
|
||||
users: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'John',
|
||||
age: 'Number(25.0000000000000000000)',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Jane',
|
||||
age: 30,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Mike',
|
||||
age: 28,
|
||||
},
|
||||
],
|
||||
products: [
|
||||
{
|
||||
id: 101,
|
||||
name: 'iPhone',
|
||||
price: 999,
|
||||
},
|
||||
{
|
||||
id: 102,
|
||||
name: 'MacBook',
|
||||
price: 1599,
|
||||
},
|
||||
{
|
||||
id: 103,
|
||||
name: 'iPad',
|
||||
price: 799,
|
||||
},
|
||||
],
|
||||
}),
|
||||
opt: () => ({}),
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'pick', path: string): void;
|
||||
}>();
|
||||
|
||||
onMounted(() => {
|
||||
JPPicker.jsonPathPicker(jr.value, props.data, [ip.value], props.opt);
|
||||
});
|
||||
|
||||
function pickPath(ev: any) {
|
||||
if (ev.target && ev.target.classList.contains('pick-path')) {
|
||||
setTimeout(() => {
|
||||
if (ip.value) {
|
||||
jsonPath.value = ip.value.value;
|
||||
emit('pick', jsonPath.value);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
JPPicker.clearJsonPathPicker(jr.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
@import url('@/assets/js/jsonpath-picker-vanilla/jsonpath-picker.css');
|
||||
</style>
|
|
@ -1,22 +1,37 @@
|
|||
<template>
|
||||
<div ref="fullRef" class="h-full rounded-[4px] bg-[var(--color-fill-1)] p-[12px]">
|
||||
<div v-if="showTitleLine" class="mb-[12px] flex justify-between pr-[12px]">
|
||||
<slot name="title">
|
||||
<span class="font-medium">{{ title }}</span>
|
||||
</slot>
|
||||
<div v-if="showThemeChange">
|
||||
<div v-if="showTitleLine" class="mb-[12px] flex items-center justify-between pr-[12px]">
|
||||
<div>
|
||||
<a-select
|
||||
v-if="showLanguageChange"
|
||||
v-model:model-value="currentLanguage"
|
||||
:options="languageOptions"
|
||||
class="mr-[4px] w-[100px]"
|
||||
size="small"
|
||||
@change="(val) => handleLanguageChange(val as Language)"
|
||||
/>
|
||||
<a-select
|
||||
v-if="showThemeChange"
|
||||
v-model:model-value="currentTheme"
|
||||
:options="themeOptions"
|
||||
class="w-[100px]"
|
||||
size="small"
|
||||
@change="(val) => handleThemeChange(val as Theme)"
|
||||
></a-select>
|
||||
/>
|
||||
</div>
|
||||
<div v-if="showFullScreen" class="w-[96px] cursor-pointer text-right !text-[var(--color-text-4)]" @click="toggle">
|
||||
<MsIcon v-if="isFullscreen" type="icon-icon_minify_outlined" />
|
||||
<MsIcon v-else type="icon-icon_magnify_outlined" />
|
||||
{{ t('msCodeEditor.fullScreen') }}
|
||||
<div>
|
||||
<slot name="title">
|
||||
<span class="font-medium">{{ title }}</span>
|
||||
</slot>
|
||||
<div
|
||||
v-if="showFullScreen"
|
||||
class="w-[96px] cursor-pointer text-right !text-[var(--color-text-4)]"
|
||||
@click="toggle"
|
||||
>
|
||||
<MsIcon v-if="isFullscreen" type="icon-icon_minify_outlined" />
|
||||
<MsIcon v-else type="icon-icon_magnify_outlined" />
|
||||
{{ t('msCodeEditor.fullScreen') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 这里的 32px 是顶部标题的 32px -->
|
||||
|
@ -35,20 +50,23 @@
|
|||
|
||||
import './userWorker';
|
||||
import MsCodeEditorTheme from './themes';
|
||||
import { CustomTheme, editorProps, Theme } from './types';
|
||||
import { CustomTheme, editorProps, Language, LanguageEnum, Theme } from './types';
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MonacoEditor',
|
||||
props: editorProps,
|
||||
emits: ['update:modelValue', 'change', 'editorMounted'],
|
||||
emits: ['update:modelValue', 'change'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n();
|
||||
let editor: monaco.editor.IStandaloneCodeEditor;
|
||||
|
||||
const codeEditBox = ref();
|
||||
// 用于全屏的容器 ref
|
||||
const fullRef = ref<HTMLElement | null>();
|
||||
// 当前主题
|
||||
const currentTheme = ref<Theme>(props.theme);
|
||||
// 主题选项
|
||||
const themeOptions = [
|
||||
{ label: 'vs', value: 'vs' },
|
||||
{ label: 'vs-dark', value: 'vs-dark' },
|
||||
|
@ -59,7 +77,32 @@
|
|||
value: item,
|
||||
}))
|
||||
);
|
||||
const showTitleLine = computed(() => props.title || props.showThemeChange || props.showFullScreen);
|
||||
// 当前语言
|
||||
const currentLanguage = ref<Language>(props.language);
|
||||
// 语言选项
|
||||
const languageOptions = Object.values(LanguageEnum)
|
||||
.map((e) => {
|
||||
if (props.languages) {
|
||||
// 如果传入了语言种类数组,则过滤选项
|
||||
if (props.languages.includes(e)) {
|
||||
return {
|
||||
label: e,
|
||||
value: e,
|
||||
};
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
label: e,
|
||||
value: e,
|
||||
};
|
||||
})
|
||||
.filter(Boolean) as { label: string; value: Language }[];
|
||||
|
||||
// 是否显示标题栏
|
||||
const showTitleLine = computed(
|
||||
() => props.title || props.showThemeChange || props.showLanguageChange || props.showFullScreen
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.theme,
|
||||
|
@ -72,6 +115,10 @@
|
|||
monaco.editor.setTheme(val);
|
||||
}
|
||||
|
||||
function handleLanguageChange(val: Language) {
|
||||
monaco.editor.setModelLanguage(editor.getModel()!, val);
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
// 注册自定义主题
|
||||
Object.keys(MsCodeEditorTheme).forEach((e) => {
|
||||
|
@ -180,10 +227,13 @@
|
|||
isFullscreen,
|
||||
currentTheme,
|
||||
themeOptions,
|
||||
currentLanguage,
|
||||
languageOptions,
|
||||
showTitleLine,
|
||||
toggle,
|
||||
t,
|
||||
handleThemeChange,
|
||||
handleLanguageChange,
|
||||
insertContent,
|
||||
undo,
|
||||
redo,
|
||||
|
|
|
@ -4,21 +4,23 @@ export type CustomTheme = 'MS-text';
|
|||
export type Theme = 'vs' | 'hc-black' | 'vs-dark' | CustomTheme;
|
||||
export type FoldingStrategy = 'auto' | 'indentation';
|
||||
export type RenderLineHighlight = 'all' | 'line' | 'none' | 'gutter';
|
||||
export type Language =
|
||||
| 'plaintext'
|
||||
| 'javascript'
|
||||
| 'typescript'
|
||||
| 'css'
|
||||
| 'less'
|
||||
| 'sass'
|
||||
| 'html'
|
||||
| 'sql'
|
||||
| 'json'
|
||||
| 'java'
|
||||
| 'python'
|
||||
| 'xml'
|
||||
| 'yaml'
|
||||
| 'shell';
|
||||
export const LanguageEnum = {
|
||||
PLAINTEXT: 'plaintext' as const,
|
||||
JAVASCRIPT: 'javascript' as const,
|
||||
TYPESCRIPT: 'typescript' as const,
|
||||
CSS: 'css' as const,
|
||||
LESS: 'less' as const,
|
||||
SASS: 'sass' as const,
|
||||
HTML: 'html' as const,
|
||||
SQL: 'sql' as const,
|
||||
JSON: 'json' as const,
|
||||
JAVA: 'java' as const,
|
||||
PYTHON: 'python' as const,
|
||||
XML: 'xml' as const,
|
||||
YAML: 'yaml' as const,
|
||||
SHELL: 'shell' as const,
|
||||
} as const;
|
||||
export type Language = (typeof LanguageEnum)[keyof typeof LanguageEnum];
|
||||
export interface Options {
|
||||
automaticLayout: boolean; // 自适应布局
|
||||
foldingStrategy: FoldingStrategy; // 折叠方式 auto | indentation
|
||||
|
@ -87,6 +89,15 @@ export const editorProps = {
|
|||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
languages: {
|
||||
// 支持选择的语言种类
|
||||
type: Array as PropType<Array<Language>>,
|
||||
default: undefined,
|
||||
},
|
||||
showLanguageChange: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
showThemeChange: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
<template>
|
||||
<pre ref="jr" :class="['container', props.class]" @click="pickPath"></pre>
|
||||
<input ref="ip" :value="jsonPath" class="path" type="hidden" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||
import { JSONPath } from 'jsonpath-plus';
|
||||
|
||||
import JPPicker from '@/assets/js/jsonpath-picker-vanilla/jsonpath-picker-vanilla';
|
||||
|
||||
import { Recordable } from '#/global';
|
||||
|
||||
const jr: Ref<HTMLElement | null> = ref(null);
|
||||
const ip: Ref<HTMLInputElement | null> = ref(null);
|
||||
const json = ref<string | Recordable<any>>('');
|
||||
const jsonPath = ref('');
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data: string | Recordable<any>;
|
||||
opt?: Recordable<any>;
|
||||
class?: string;
|
||||
}>(),
|
||||
{
|
||||
opt: () => ({}),
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'pick', path: string, result: any[]): void;
|
||||
}>();
|
||||
|
||||
function initJsonPathPicker() {
|
||||
try {
|
||||
json.value = props.data;
|
||||
if (typeof props.data === 'string') {
|
||||
json.value = JSON.parse(props.data);
|
||||
}
|
||||
JPPicker.jsonPathPicker(jr.value, json.value, [ip.value], props.opt);
|
||||
} catch (error) {
|
||||
JPPicker.jsonPathPicker(jr.value, props.data, [ip.value], props.opt);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initJsonPathPicker();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
initJsonPathPicker();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
function pickPath(ev: any) {
|
||||
if (ev.target && ev.target.classList.contains('pick-path')) {
|
||||
setTimeout(() => {
|
||||
if (ip.value) {
|
||||
jsonPath.value = ip.value.value;
|
||||
emit('pick', jsonPath.value, JSONPath({ json: json.value, path: jsonPath.value }));
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
JPPicker.clearJsonPathPicker(jr.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import url('@/assets/js/jsonpath-picker-vanilla/jsonpath-picker.css');
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.container {
|
||||
@apply h-full overflow-y-auto;
|
||||
.ms-scroll-bar();
|
||||
|
||||
padding: 12px 1.85em;
|
||||
border-radius: var(--border-radius-small);
|
||||
:deep(.json-string) {
|
||||
color: rgb(var(--link-7));
|
||||
}
|
||||
:deep(.json-literal) {
|
||||
color: rgb(var(--primary-4));
|
||||
}
|
||||
:deep(.json-toggle),
|
||||
:deep(.json-dict > li) {
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div v-if="parsedXml">
|
||||
<div v-if="parsedXml" class="container">
|
||||
<div v-for="(node, index) in flattenedXml" :key="index">
|
||||
<span style="white-space: pre" @click="copyXPath(node.xpath)" v-html="node.content"></span>
|
||||
</div>
|
||||
|
@ -8,6 +8,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { XpathNode } from './types';
|
||||
import * as XmlBeautify from 'xml-beautify';
|
||||
|
||||
const props = defineProps<{
|
||||
xmlString: string;
|
||||
|
@ -67,7 +68,6 @@
|
|||
emit('pick', xpath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析xml
|
||||
*/
|
||||
|
@ -76,12 +76,12 @@
|
|||
const parser = new DOMParser();
|
||||
const xmlDoc = parser.parseFromString(props.xmlString, 'application/xml');
|
||||
parsedXml.value = xmlDoc;
|
||||
// 先将 XML 字符串解析转换并给每个开始标签加上复制 icon
|
||||
flattenedXml.value = props.xmlString
|
||||
// 先将 XML 字符串格式化,然后解析转换并给每个开始标签加上复制 icon
|
||||
flattenedXml.value = new XmlBeautify({ parser: DOMParser })
|
||||
.beautify(props.xmlString)
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/(<\w+\s*[^>]*>)/g, '<span style="color: rgb(var(--primary-5));cursor: pointer">$1📋</span>')
|
||||
.replace(/(<\/\w+\s*[^>]*>)/g, '<span style="color: rgb(var(--primary-5));">$1</span>')
|
||||
.replace(/(<([^/][^&]*?)>)/g, '<span style="color: rgb(var(--primary-5));cursor: pointer">$1📋</span>')
|
||||
.split(/\r?\n/)
|
||||
.map((e) => ({ content: e, xpath: '' }));
|
||||
// 解析真实 XML 并将其扁平化,得到每个节点的 xpath
|
||||
|
@ -115,3 +115,13 @@
|
|||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.container {
|
||||
@apply h-full overflow-y-auto;
|
||||
.ms-scroll-bar();
|
||||
|
||||
padding: 12px 1.85em;
|
||||
border-radius: var(--border-radius-small);
|
||||
}
|
||||
</style>
|
|
@ -16,7 +16,7 @@
|
|||
<div
|
||||
:class="`ms-split-box ${props.direction === 'horizontal' ? 'ms-split-box--left' : 'ms-split-box--top'} ${
|
||||
props.disabled && props.direction === 'horizontal' ? 'border-r border-[var(--color-text-n8)]' : ''
|
||||
}`"
|
||||
} ${props.firstContainerClass}`"
|
||||
>
|
||||
<div
|
||||
v-if="props.direction === 'horizontal' && props.expandDirection === 'right' && !props.disabled"
|
||||
|
@ -51,7 +51,11 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="`ms-split-box ${props.direction === 'horizontal' ? 'ms-split-box--right' : 'ms-split-box--bottom'}`">
|
||||
<div
|
||||
:class="`ms-split-box ${props.direction === 'horizontal' ? 'ms-split-box--right' : 'ms-split-box--bottom'} ${
|
||||
props.secondContainerClass
|
||||
}`"
|
||||
>
|
||||
<slot name="second"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -71,6 +75,8 @@
|
|||
direction?: 'horizontal' | 'vertical';
|
||||
expandDirection?: 'left' | 'right' | 'top'; // TODO: 未实现 bottom,有场景再补充。目前默认水平是 left,垂直是 top
|
||||
disabled?: boolean; // 是否禁用
|
||||
firstContainerClass?: string; // first容器类名
|
||||
secondContainerClass?: string; // second容器类名
|
||||
}>(),
|
||||
{
|
||||
size: '300px',
|
||||
|
|
|
@ -17,9 +17,10 @@
|
|||
<slot name="optional" v-bind="{ rowIndex, record }" />
|
||||
</template>
|
||||
<template #columns>
|
||||
<a-table-column v-if="attrs.selectable && props.selectedKeys" :width="60">
|
||||
<a-table-column v-if="attrs.selectable && props.selectedKeys" :width="props.firstColumnWidth || 60">
|
||||
<template #title>
|
||||
<SelectALL
|
||||
v-if="attrs.selectorType === 'checkbox'"
|
||||
:total="selectTotal"
|
||||
:current="selectCurrent"
|
||||
:show-select-all="(attrs.showPagination as boolean) && props.showSelectorAll"
|
||||
|
@ -29,9 +30,15 @@
|
|||
</template>
|
||||
<template #cell="{ record }">
|
||||
<MsCheckbox
|
||||
v-if="attrs.selectorType === 'checkbox'"
|
||||
:value="props.selectedKeys.has(record[rowKey || 'id'])"
|
||||
@change="rowSelectChange(record[rowKey || 'id'])"
|
||||
/>
|
||||
<a-radio
|
||||
v-else-if="attrs.selectorType === 'radio'"
|
||||
v-model:model-value="innerSelectedKey"
|
||||
:value="record[rowKey || 'id']"
|
||||
/>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column
|
||||
|
@ -54,7 +61,7 @@
|
|||
:tooltip="item.tooltip"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex w-full flex-row flex-nowrap items-center">
|
||||
<div :class="{ 'flex w-full flex-row flex-nowrap items-center': !item.align }">
|
||||
<slot :name="item.titleSlotName" :column-config="item">
|
||||
<div class="text-[var(--color-text-3)]">{{ t(item.title as string) }}</div>
|
||||
</slot>
|
||||
|
@ -80,7 +87,7 @@
|
|||
</div>
|
||||
</template>
|
||||
<template #cell="{ column, record, rowIndex }">
|
||||
<div :class="{ 'flex flex-row items-center': !item.isTag }">
|
||||
<div :class="{ 'flex flex-row items-center': !item.isTag && !item.align }">
|
||||
<template v-if="item.dataIndex === SpecialColumnEnum.ENABLE">
|
||||
<slot name="enable" v-bind="{ record }">
|
||||
<div v-if="record.enable" class="flex flex-row flex-nowrap items-center gap-[2px]">
|
||||
|
@ -224,6 +231,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, onMounted, ref, useAttrs } from 'vue';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsPagination from '@/components/pure/ms-pagination/index';
|
||||
|
@ -259,6 +267,7 @@
|
|||
|
||||
const props = defineProps<{
|
||||
selectedKeys: Set<string>;
|
||||
selectedKey: string;
|
||||
excludeKeys: Set<string>;
|
||||
selectorStatus: SelectAllEnum;
|
||||
actionConfig?: BatchActionConfig;
|
||||
|
@ -270,8 +279,10 @@
|
|||
rowClass?: string | any[] | Record<string, any> | ((record: TableData, rowIndex: number) => any);
|
||||
spanAll?: boolean;
|
||||
showSelectorAll?: boolean;
|
||||
firstColumnWidth?: number; // 选择、拖拽列的宽度
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:selectedKey', value: string): void;
|
||||
(e: 'batchAction', value: BatchActionParams, queryParams: BatchActionQueryParams): void;
|
||||
(e: 'pageChange', value: number): void;
|
||||
(e: 'pageSizeChange', value: number): void;
|
||||
|
@ -357,6 +368,8 @@
|
|||
}
|
||||
};
|
||||
|
||||
const innerSelectedKey = useVModel(props, 'selectedKey', emit); // 内部维护的单选选中项
|
||||
|
||||
// 全选change事件
|
||||
const handleSelectAllChange = (v: SelectAllEnum) => {
|
||||
emit('selectAllChange', v);
|
||||
|
@ -512,6 +525,11 @@
|
|||
color: rgb(var(--primary-7));
|
||||
opacity: 0;
|
||||
}
|
||||
:deep(.arco-table-cell-align-left) {
|
||||
.arco-table-td-content {
|
||||
@apply flex items-center;
|
||||
}
|
||||
}
|
||||
:deep(.arco-table-hover) {
|
||||
:not(.arco-table-dragging) {
|
||||
.arco-table-tr:not(.arco-table-tr-empty):not(.arco-table-tr-summary):hover {
|
||||
|
|
|
@ -82,7 +82,8 @@ export interface MsTableProps<T> {
|
|||
/** 选择器相关 */
|
||||
selectable?: boolean; // 是否显示选择器
|
||||
selectorType: 'none' | 'checkbox' | 'radio'; // 选择器类型
|
||||
selectedKeys: Set<string>; // 选中的key
|
||||
selectedKeys: Set<string>; // 选中的key,多选
|
||||
selectedKey: string; // 选中的key,单选
|
||||
excludeKeys: Set<string>; // 排除的key
|
||||
selectorStatus: SelectAllEnum; // 选择器状态
|
||||
showSelectorAll?: boolean; // 是否显示跨页全选选择器
|
||||
|
|
|
@ -59,7 +59,8 @@ export default function useTableProps<T>(
|
|||
rowSelection: null, // 禁用表格默认的选择器
|
||||
selectable: false, // 是否显示选择器
|
||||
selectorType: 'checkbox', // 选择器类型
|
||||
selectedKeys: new Set<string>(), // 选中的key
|
||||
selectedKeys: new Set<string>(), // 选中的key, 多选
|
||||
selectedKey: '', // 选中的key,单选
|
||||
excludeKeys: new Set<string>(), // 排除的key
|
||||
selectorStatus: SelectAllEnum.NONE, // 选择器状态
|
||||
showSelectorAll: true, // 是否显示全选
|
||||
|
|
|
@ -1,81 +1,12 @@
|
|||
import { ReviewItem } from '@/models/caseManagement/caseReview';
|
||||
import { ConditionType } from '@/models/apiTest/debug';
|
||||
|
||||
// 评审结果
|
||||
export const reviewResultMap = {
|
||||
UN_REVIEWED: {
|
||||
label: 'caseManagement.caseReview.unReview',
|
||||
color: 'var(--color-text-input-border)',
|
||||
icon: 'icon-icon_block_filled',
|
||||
},
|
||||
UNDER_REVIEWED: {
|
||||
label: 'caseManagement.caseReview.reviewing',
|
||||
color: 'rgb(var(--link-6))',
|
||||
icon: 'icon-icon_testing',
|
||||
},
|
||||
PASS: {
|
||||
label: 'caseManagement.caseReview.reviewPass',
|
||||
color: 'rgb(var(--success-6))',
|
||||
icon: 'icon-icon_succeed_filled',
|
||||
},
|
||||
UN_PASS: {
|
||||
label: 'caseManagement.caseReview.fail',
|
||||
color: 'rgb(var(--danger-6))',
|
||||
icon: 'icon-icon_close_filled',
|
||||
},
|
||||
RE_REVIEWED: {
|
||||
label: 'caseManagement.caseReview.reReview',
|
||||
color: 'rgb(var(--warning-6))',
|
||||
icon: 'icon-icon_resubmit_filled',
|
||||
},
|
||||
} as const;
|
||||
// 评审状态
|
||||
export const reviewStatusMap = {
|
||||
PREPARED: {
|
||||
label: 'caseManagement.caseReview.unStart',
|
||||
color: 'var(--color-text-n8)',
|
||||
class: '!text-[var(--color-text-1)]',
|
||||
},
|
||||
UNDERWAY: {
|
||||
label: 'caseManagement.caseReview.going',
|
||||
color: 'rgb(var(--link-2))',
|
||||
class: '!text-[rgb(var(--link-6))]',
|
||||
},
|
||||
COMPLETED: {
|
||||
label: 'caseManagement.caseReview.finished',
|
||||
color: 'rgb(var(--success-2))',
|
||||
class: '!text-[rgb(var(--success-6))]',
|
||||
},
|
||||
ARCHIVED: {
|
||||
label: 'caseManagement.caseReview.archived',
|
||||
color: 'var(--color-text-n8)',
|
||||
class: '!text-[var(--color-text-4)]',
|
||||
},
|
||||
} as const;
|
||||
// 评审详情
|
||||
export const reviewDefaultDetail: ReviewItem = {
|
||||
id: '',
|
||||
num: 0,
|
||||
moduleId: '',
|
||||
projectId: '',
|
||||
reviewPassRule: 'SINGLE',
|
||||
name: '',
|
||||
status: 'PREPARED',
|
||||
caseCount: 0,
|
||||
passCount: 0,
|
||||
unPassCount: 0,
|
||||
reviewedCount: 0,
|
||||
underReviewedCount: 0,
|
||||
pos: 5000,
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
passRate: 0,
|
||||
tags: [],
|
||||
description: '',
|
||||
createTime: 0,
|
||||
createUser: '',
|
||||
updateTime: 0,
|
||||
updateUser: '',
|
||||
reviewers: [],
|
||||
reReviewedCount: 0,
|
||||
followFlag: false,
|
||||
// 条件操作类型
|
||||
export type ConditionTypeNameMap = Record<ConditionType, string>;
|
||||
export const conditionTypeNameMap = {
|
||||
script: 'apiTestDebug.script',
|
||||
sql: 'apiTestDebug.sql',
|
||||
waitTime: 'apiTestDebug.waitTime',
|
||||
extract: 'apiTestDebug.extractParameter',
|
||||
};
|
||||
|
||||
export default {};
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import { ReviewItem, ReviewResult, ReviewStatus } from '@/models/caseManagement/caseReview';
|
||||
|
||||
// 评审结果
|
||||
export type ReviewResultMap = Record<
|
||||
ReviewResult,
|
||||
{
|
||||
label: string;
|
||||
color: string;
|
||||
icon: string;
|
||||
}
|
||||
>;
|
||||
export const reviewResultMap: ReviewResultMap = {
|
||||
UN_REVIEWED: {
|
||||
label: 'caseManagement.caseReview.unReview',
|
||||
color: 'var(--color-text-input-border)',
|
||||
icon: 'icon-icon_block_filled',
|
||||
},
|
||||
UNDER_REVIEWED: {
|
||||
label: 'caseManagement.caseReview.reviewing',
|
||||
color: 'rgb(var(--link-6))',
|
||||
icon: 'icon-icon_testing',
|
||||
},
|
||||
PASS: {
|
||||
label: 'caseManagement.caseReview.reviewPass',
|
||||
color: 'rgb(var(--success-6))',
|
||||
icon: 'icon-icon_succeed_filled',
|
||||
},
|
||||
UN_PASS: {
|
||||
label: 'caseManagement.caseReview.fail',
|
||||
color: 'rgb(var(--danger-6))',
|
||||
icon: 'icon-icon_close_filled',
|
||||
},
|
||||
RE_REVIEWED: {
|
||||
label: 'caseManagement.caseReview.reReview',
|
||||
color: 'rgb(var(--warning-6))',
|
||||
icon: 'icon-icon_resubmit_filled',
|
||||
},
|
||||
};
|
||||
// 评审状态
|
||||
export type ReviewStatusMap = Record<
|
||||
ReviewStatus,
|
||||
{
|
||||
label: string;
|
||||
color: string;
|
||||
class: string;
|
||||
}
|
||||
>;
|
||||
export const reviewStatusMap: ReviewStatusMap = {
|
||||
PREPARED: {
|
||||
label: 'caseManagement.caseReview.unStart',
|
||||
color: 'var(--color-text-n8)',
|
||||
class: '!text-[var(--color-text-1)]',
|
||||
},
|
||||
UNDERWAY: {
|
||||
label: 'caseManagement.caseReview.going',
|
||||
color: 'rgb(var(--link-2))',
|
||||
class: '!text-[rgb(var(--link-6))]',
|
||||
},
|
||||
COMPLETED: {
|
||||
label: 'caseManagement.caseReview.finished',
|
||||
color: 'rgb(var(--success-2))',
|
||||
class: '!text-[rgb(var(--success-6))]',
|
||||
},
|
||||
ARCHIVED: {
|
||||
label: 'caseManagement.caseReview.archived',
|
||||
color: 'var(--color-text-n8)',
|
||||
class: '!text-[var(--color-text-4)]',
|
||||
},
|
||||
};
|
||||
// 评审详情
|
||||
export const reviewDefaultDetail: ReviewItem = {
|
||||
id: '',
|
||||
num: 0,
|
||||
moduleId: '',
|
||||
projectId: '',
|
||||
reviewPassRule: 'SINGLE',
|
||||
name: '',
|
||||
status: 'PREPARED' as ReviewStatus,
|
||||
caseCount: 0,
|
||||
passCount: 0,
|
||||
unPassCount: 0,
|
||||
reviewedCount: 0,
|
||||
underReviewedCount: 0,
|
||||
pos: 5000,
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
passRate: 0,
|
||||
tags: [],
|
||||
description: '',
|
||||
createTime: 0,
|
||||
createUser: '',
|
||||
updateTime: 0,
|
||||
updateUser: '',
|
||||
reviewers: [],
|
||||
reReviewedCount: 0,
|
||||
followFlag: false,
|
||||
};
|
|
@ -15,7 +15,7 @@ export enum RequestComposition {
|
|||
BODY = 'BODY',
|
||||
QUERY = 'QUERY',
|
||||
REST = 'REST',
|
||||
PREFIX = 'PREFIX',
|
||||
PRECONDITION = 'PRECONDITION',
|
||||
POST_CONDITION = 'POST_CONDITION',
|
||||
ASSERTION = 'ASSERTION',
|
||||
AUTH = 'AUTH',
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
export interface APIListItemI {
|
||||
id: number;
|
||||
type: string;
|
||||
receiver: string;
|
||||
title: string;
|
||||
status: string;
|
||||
createTime: number | string;
|
||||
operator: string;
|
||||
operation: string;
|
||||
resourceId: string;
|
||||
resourceType: string;
|
||||
resourceName: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface SortItem {
|
||||
[key: string]: string;
|
||||
}
|
||||
export interface FilterItem {
|
||||
[key: string]: any;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// 条件操作类型
|
||||
export type ConditionType = 'script' | 'sql' | 'waitTime' | 'extract';
|
||||
// 表达式类型
|
||||
export type ExpressionType = 'regular' | 'JSONPath' | 'XPath';
|
||||
// 表达式配置
|
||||
export interface ExpressionConfig {
|
||||
expression: string;
|
||||
expressionType?: ExpressionType;
|
||||
regexpMatchRule?: 'expression' | 'group'; // 正则表达式匹配规则
|
||||
resultMatchRule?: 'random' | 'specify' | 'all'; // 结果匹配规则
|
||||
specifyMatchNum?: number; // 指定匹配下标
|
||||
xmlMatchContentType?: 'xml' | 'html'; // 响应内容格式
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { DOMParser } from '@xmldom/xmldom';
|
||||
import * as xpath from 'xpath';
|
||||
|
||||
/**
|
||||
* xpath 匹配 xml 文本
|
||||
* @param xmlText xml文本
|
||||
* @param xpathQuery xpath
|
||||
* @returns 匹配节点
|
||||
*/
|
||||
export function matchXMLWithXPath(xmlText: string, xpathQuery: string): xpath.SelectReturnType {
|
||||
try {
|
||||
// 解析 XML 文本
|
||||
const xmlDoc = new DOMParser().parseFromString(xmlText, 'text/xml');
|
||||
|
||||
// 使用 XPath 查询匹配的节点
|
||||
const nodes = xpath.select(xpathQuery, xmlDoc);
|
||||
|
||||
// 返回匹配结果
|
||||
return nodes;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Error parsing XML or executing XPath query:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default { matchXMLWithXPath };
|
|
@ -0,0 +1,703 @@
|
|||
<template>
|
||||
<div class="condition-content">
|
||||
<!-- 脚本操作 -->
|
||||
<template v-if="condition.type === 'script'">
|
||||
<a-radio-group v-model:model-value="condition.scriptType" size="small" class="mb-[16px]">
|
||||
<a-radio value="manual">{{ t('apiTestDebug.manual') }}</a-radio>
|
||||
<a-radio value="quote">{{ t('apiTestDebug.quote') }}</a-radio>
|
||||
</a-radio-group>
|
||||
<div
|
||||
v-if="condition.scriptType === 'manual'"
|
||||
class="relative rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[12px]"
|
||||
>
|
||||
<div v-if="isShowEditScriptNameInput" class="absolute left-[12px] z-10 w-[calc(100%-24px)]">
|
||||
<a-input
|
||||
ref="scriptNameInputRef"
|
||||
v-model:model-value="condition.name"
|
||||
:placeholder="t('apiTestDebug.preconditionScriptNamePlaceholder')"
|
||||
:max-length="255"
|
||||
show-word-limit
|
||||
size="small"
|
||||
@press-enter="isShowEditScriptNameInput = false"
|
||||
@blur="isShowEditScriptNameInput = false"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<a-tooltip :content="condition.name">
|
||||
<div class="script-name-container">
|
||||
<div class="one-line-text mr-[4px] max-w-[110px] font-medium text-[var(--color-text-1)]">
|
||||
{{ condition.name }}
|
||||
</div>
|
||||
<MsIcon type="icon-icon_edit_outlined" class="edit-script-name-icon" @click="showEditScriptNameInput" />
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<a-popover class="h-auto" position="top">
|
||||
<div class="text-[rgb(var(--primary-5))]">{{ t('apiTestDebug.scriptEx') }}</div>
|
||||
<template #content>
|
||||
<div class="mb-[8px] flex items-center justify-between">
|
||||
<div class="text-[14px] font-medium text-[var(--color-text-1)]">
|
||||
{{ t('apiTestDebug.scriptEx') }}
|
||||
</div>
|
||||
<a-button
|
||||
type="outline"
|
||||
class="arco-btn-outline--secondary p-[0_8px]"
|
||||
size="mini"
|
||||
@click="copyScriptEx"
|
||||
>
|
||||
{{ t('common.copy') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="flex h-[412px]">
|
||||
<MsCodeEditor
|
||||
v-model:model-value="scriptEx"
|
||||
class="flex-1"
|
||||
theme="MS-text"
|
||||
width="500px"
|
||||
height="388px"
|
||||
:show-full-screen="false"
|
||||
:show-theme-change="false"
|
||||
read-only
|
||||
>
|
||||
</MsCodeEditor>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
</div>
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini">
|
||||
<template #icon>
|
||||
<MsIcon type="icon-icon_undo_outlined" class="text-var(--color-text-4)" size="12" />
|
||||
</template>
|
||||
{{ t('common.revoke') }}
|
||||
</a-button>
|
||||
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini" @click="clearScript">
|
||||
<template #icon>
|
||||
<MsIcon type="icon-icon_clear" class="text-var(--color-text-4)" size="12" />
|
||||
</template>
|
||||
{{ t('common.clear') }}
|
||||
</a-button>
|
||||
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini" @click="copyCondition">
|
||||
{{ t('common.copy') }}
|
||||
</a-button>
|
||||
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini" @click="deleteCondition">
|
||||
{{ t('common.delete') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex h-[calc(100%-47px)] flex-col">
|
||||
<div class="mb-[16px] flex w-full items-center bg-[var(--color-text-n9)] p-[12px]">
|
||||
<div class="text-[var(--color-text-2)]">
|
||||
{{ condition.quoteScript.name || '-' }}
|
||||
</div>
|
||||
<a-divider margin="8px" direction="vertical" />
|
||||
<MsButton type="text" class="font-medium">
|
||||
{{ t('apiTestDebug.quote') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
<a-radio-group v-model:model-value="commonScriptShowType" size="small" type="button" class="mb-[8px] w-fit">
|
||||
<a-radio value="parameters">{{ t('apiTestDebug.parameters') }}</a-radio>
|
||||
<a-radio value="scriptContent">{{ t('apiTestDebug.scriptContent') }}</a-radio>
|
||||
</a-radio-group>
|
||||
<MsBaseTable v-show="commonScriptShowType === 'parameters'" v-bind="propsRes" v-on="propsEvent">
|
||||
<template #value="{ record }">
|
||||
<a-tooltip :content="t(record.required ? 'apiTestDebug.paramRequired' : 'apiTestDebug.paramNotRequired')">
|
||||
<div
|
||||
:class="[
|
||||
record.required ? '!text-[rgb(var(--danger-5))]' : '!text-[var(--color-text-brand)]',
|
||||
'!mr-[4px] !p-[4px]',
|
||||
]"
|
||||
>
|
||||
<div>*</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
{{ record.type }}
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
<div v-show="commonScriptShowType === 'scriptContent'" class="h-[calc(100%-76px)]">
|
||||
<MsCodeEditor
|
||||
v-model:model-value="condition.quoteScript.script"
|
||||
theme="MS-text"
|
||||
height="100%"
|
||||
:show-full-screen="false"
|
||||
:show-theme-change="false"
|
||||
read-only
|
||||
>
|
||||
</MsCodeEditor>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- SQL操作 -->
|
||||
<template v-else-if="condition.type === 'sql'">
|
||||
<div class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('common.desc') }}</div>
|
||||
<a-input
|
||||
v-model:model-value="condition.desc"
|
||||
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
||||
:max-length="255"
|
||||
show-word-limit
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-[16px] flex w-full items-center bg-[var(--color-text-n9)] p-[12px]">
|
||||
<div class="text-[var(--color-text-2)]">
|
||||
{{ condition.sqlSource.name || '-' }}
|
||||
</div>
|
||||
<a-divider margin="8px" direction="vertical" />
|
||||
<MsButton type="text" class="font-medium" @click="quoteSqlSourceDrawerVisible = true">
|
||||
{{ t('apiTestDebug.introduceSource') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.sqlScript') }}</div>
|
||||
<div class="mb-[16px] h-[300px]">
|
||||
<MsCodeEditor
|
||||
v-model:model-value="condition.sqlSource.script"
|
||||
theme="vs"
|
||||
height="276px"
|
||||
:language="LanguageEnum.SQL"
|
||||
:show-full-screen="false"
|
||||
:show-theme-change="false"
|
||||
read-only
|
||||
>
|
||||
</MsCodeEditor>
|
||||
</div>
|
||||
<div class="mb-[16px]">
|
||||
<div class="mb-[8px] flex items-center text-[var(--color-text-1)]">
|
||||
{{ t('apiTestDebug.storageType') }}
|
||||
<a-tooltip position="right">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
<template #content>
|
||||
<div>{{ t('apiTestDebug.storageTypeTip1') }}</div>
|
||||
<div>{{ t('apiTestDebug.storageTypeTip2') }}</div>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-radio-group v-model:model-value="condition.sqlSource.storageType" size="small" type="button" class="w-fit">
|
||||
<a-radio value="column">{{ t('apiTestDebug.storageByCol') }}</a-radio>
|
||||
<a-radio value="result">{{ t('apiTestDebug.storageByResult') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div v-if="condition.sqlSource.storageType === 'column'" class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.storageByCol') }}</div>
|
||||
<a-input
|
||||
v-model:model-value="condition.sqlSource.storageByCol"
|
||||
:placeholder="t('apiTestDebug.storageByColPlaceholder', { a: '{id_1}', b: '{username_1}' })"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.storageByResult') }}</div>
|
||||
<a-input
|
||||
v-model:model-value="condition.sqlSource.storageByResult"
|
||||
:placeholder="t('apiTestDebug.storageByResultPlaceholder', { a: '${result}' })"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="condition.sqlSource.storageType === 'column'" class="sql-table-container">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.extractParameter') }}</div>
|
||||
<paramTable
|
||||
v-model:params="condition.sqlSource.params"
|
||||
:columns="sqlSourceColumns"
|
||||
:selectable="false"
|
||||
@change="handleSqlSourceParamTableChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 等待时间 -->
|
||||
<div v-else-if="condition.type === 'waitTime'">
|
||||
<div class="mb-[8px] flex items-center">
|
||||
{{ t('apiTestDebug.waitTime') }}
|
||||
<div class="text-[var(--color-text-4)]">(ms)</div>
|
||||
</div>
|
||||
<a-input-number v-model:model-value="condition.time" mode="button" :step="100" :min="0" class="w-[160px]" />
|
||||
</div>
|
||||
<!-- 提取参数 -->
|
||||
<div v-else-if="condition.type === 'extract'">
|
||||
<paramTable
|
||||
ref="extractParamsTableRef"
|
||||
v-model:params="condition.extractParams"
|
||||
:default-param-item="defaultExtractParamItem"
|
||||
:columns="extractParamsColumns"
|
||||
:selectable="false"
|
||||
:scroll="{ x: '700px' }"
|
||||
:response="props.response"
|
||||
:height-used="(props.heightUsed || 0) + 62"
|
||||
@change="handleExtractParamTableChange"
|
||||
@more-action-select="handleExtractParamMoreActionSelect"
|
||||
>
|
||||
<template #expression="{ record }">
|
||||
<a-popover
|
||||
position="tl"
|
||||
:disabled="!record.expression || record.expression.trim() === ''"
|
||||
class="ms-params-input-popover"
|
||||
>
|
||||
<template #content>
|
||||
<div class="param-popover-title">
|
||||
{{ t('apiTestDebug.expression') }}
|
||||
</div>
|
||||
<div class="param-popover-value">
|
||||
{{ record.expression }}
|
||||
</div>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:model-value="record.expression"
|
||||
class="ms-params-input"
|
||||
@input="handleExpressionChange"
|
||||
@change="handleExpressionChange"
|
||||
>
|
||||
<template #suffix>
|
||||
<a-tooltip :disabled="!disabledExpressionSuffix">
|
||||
<template #content>
|
||||
<div>{{ t('apiTestDebug.expressionTip1') }}</div>
|
||||
<div>{{ t('apiTestDebug.expressionTip2') }}</div>
|
||||
<div>{{ t('apiTestDebug.expressionTip3') }}</div>
|
||||
</template>
|
||||
<MsIcon
|
||||
type="icon-icon_flashlamp"
|
||||
:size="15"
|
||||
:class="
|
||||
disabledExpressionSuffix ? 'ms-params-input-suffix-icon--disabled' : 'ms-params-input-suffix-icon'
|
||||
"
|
||||
@click.stop="() => showFastExtraction(record)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template #operationPre="{ record }">
|
||||
<a-popover
|
||||
v-model:popupVisible="record.moreSettingPopoverVisible"
|
||||
position="tl"
|
||||
trigger="click"
|
||||
:title="t('common.setting')"
|
||||
:content-style="{ width: '480px' }"
|
||||
>
|
||||
<template #content>
|
||||
<moreSetting v-model:config="activeRecord" is-popover class="mt-[12px]" />
|
||||
<div class="flex items-center justify-end gap-[8px]">
|
||||
<a-button type="secondary" size="mini" @click="record.moreSettingPopoverVisible = false">
|
||||
{{ t('common.cancel') }}
|
||||
</a-button>
|
||||
<a-button type="primary" size="mini" @click="() => applyMoreSetting(record)">
|
||||
{{ t('common.confirm') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<span class="invisible relative"></span>
|
||||
</a-popover>
|
||||
</template>
|
||||
</paramTable>
|
||||
</div>
|
||||
</div>
|
||||
<quoteSqlSourceDrawer v-model:visible="quoteSqlSourceDrawerVisible" @apply="handleQuoteSqlSourceApply" />
|
||||
<fastExtraction
|
||||
v-model:visible="fastExtractionVisible"
|
||||
:response="props.response"
|
||||
:config="activeRecord"
|
||||
@apply="handleFastExtractionApply"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useClipboard, useVModel } from '@vueuse/core';
|
||||
import { InputInstance, Message } from '@arco-design/web-vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import fastExtraction from '../fastExtraction/index.vue';
|
||||
import moreSetting from '../fastExtraction/moreSetting.vue';
|
||||
import paramTable, { type ParamTableColumn } from '../paramTable.vue';
|
||||
import quoteSqlSourceDrawer from '../quoteSqlSourceDrawer.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ExpressionConfig } from '@/models/apiTest/debug';
|
||||
|
||||
const props = defineProps<{
|
||||
data: Record<string, any>;
|
||||
response?: string; // 响应内容
|
||||
heightUsed?: number;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:data', data: Record<string, any>): void;
|
||||
(e: 'copy'): void;
|
||||
(e: 'delete', id: string): void;
|
||||
(e: 'change'): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const condition = useVModel(props, 'data', emit);
|
||||
// 是否显示脚本名称编辑框
|
||||
const isShowEditScriptNameInput = ref(false);
|
||||
const scriptNameInputRef = ref<InputInstance>();
|
||||
|
||||
function showEditScriptNameInput() {
|
||||
isShowEditScriptNameInput.value = true;
|
||||
nextTick(() => {
|
||||
scriptNameInputRef.value?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
const scriptEx = ref(`2023-12-04 11:19:28 INFO 9026fd6a 1-1 Thread started: 9026fd6a 1-1
|
||||
2023-12-04 11:19:28 ERROR 9026fd6a 1-1 Problem in JSR223 script JSR223Sampler, message: {}
|
||||
In file: inline evaluation of: prev.getResponseCode() import java.net.URI; import org.apache.http.client.method . . . '' Encountered "import" at line 2, column 1.
|
||||
in inline evaluation of: prev.getResponseCode() import java.net.URI; import org.apache.http.client.method . . . '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException
|
||||
org.apache.http.client.method . . . '' at line number 2
|
||||
`);
|
||||
const { copy, isSupported } = useClipboard();
|
||||
|
||||
function copyScriptEx() {
|
||||
if (isSupported) {
|
||||
copy(scriptEx.value);
|
||||
Message.success(t('apiTestDebug.scriptExCopySuccess'));
|
||||
} else {
|
||||
Message.warning(t('apiTestDebug.copyNotSupport'));
|
||||
}
|
||||
}
|
||||
|
||||
function clearScript() {
|
||||
condition.value.script = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制条件
|
||||
*/
|
||||
function copyCondition() {
|
||||
emit('copy');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除条件
|
||||
*/
|
||||
function deleteCondition() {
|
||||
emit('delete', condition.value.id);
|
||||
}
|
||||
|
||||
const commonScriptShowType = ref<'parameters' | 'scriptContent'>('parameters');
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'name',
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramValue',
|
||||
dataIndex: 'value',
|
||||
slotName: 'value',
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.desc',
|
||||
dataIndex: 'desc',
|
||||
showTooltip: true,
|
||||
},
|
||||
];
|
||||
const { propsRes, propsEvent } = useTable(() => Promise.resolve([]), {
|
||||
scroll: { x: '100%' },
|
||||
columns,
|
||||
});
|
||||
propsRes.value.data = [
|
||||
{
|
||||
id: new Date().getTime(),
|
||||
required: false,
|
||||
name: 'asdasd',
|
||||
type: 'string',
|
||||
value: '',
|
||||
desc: '',
|
||||
},
|
||||
{
|
||||
id: new Date().getTime(),
|
||||
required: true,
|
||||
name: '23d23d',
|
||||
type: 'string',
|
||||
value: '',
|
||||
desc: '',
|
||||
},
|
||||
] as any;
|
||||
|
||||
const sqlSourceColumns: ParamTableColumn[] = [
|
||||
{
|
||||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'name',
|
||||
slotName: 'name',
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramValue',
|
||||
dataIndex: 'value',
|
||||
slotName: 'value',
|
||||
isNormal: true,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
slotName: 'operation',
|
||||
width: 50,
|
||||
},
|
||||
];
|
||||
const quoteSqlSourceDrawerVisible = ref(false);
|
||||
function handleQuoteSqlSourceApply(sqlSource: any) {
|
||||
condition.value.sqlSource = sqlSource;
|
||||
emit('change');
|
||||
}
|
||||
|
||||
function handleSqlSourceParamTableChange(resultArr: any[], isInit?: boolean) {
|
||||
condition.value.sqlSource.params = [...resultArr];
|
||||
if (!isInit) {
|
||||
emit('change');
|
||||
}
|
||||
}
|
||||
|
||||
const extractParamsColumns: ParamTableColumn[] = [
|
||||
{
|
||||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'name',
|
||||
slotName: 'name',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramType',
|
||||
dataIndex: 'type',
|
||||
slotName: 'type',
|
||||
typeOptions: [
|
||||
{
|
||||
label: t('apiTestDebug.globalParameter'),
|
||||
value: 'global',
|
||||
},
|
||||
{
|
||||
label: t('apiTestDebug.envParameter'),
|
||||
value: 'env',
|
||||
},
|
||||
{
|
||||
label: t('apiTestDebug.tempParameter'),
|
||||
value: 'temp',
|
||||
},
|
||||
],
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.mode',
|
||||
dataIndex: 'expressionType',
|
||||
slotName: 'expressionType',
|
||||
typeOptions: [
|
||||
{
|
||||
label: t('apiTestDebug.regular'),
|
||||
value: 'regular',
|
||||
},
|
||||
{
|
||||
label: 'JSONPath',
|
||||
value: 'JSONPath',
|
||||
},
|
||||
{
|
||||
label: 'XPath',
|
||||
value: 'XPath',
|
||||
},
|
||||
],
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.range',
|
||||
dataIndex: 'range',
|
||||
slotName: 'range',
|
||||
typeOptions: [
|
||||
{
|
||||
label: 'Body',
|
||||
value: 'body',
|
||||
},
|
||||
{
|
||||
label: 'Body (unescaped)',
|
||||
value: 'body_unescaped',
|
||||
},
|
||||
{
|
||||
label: 'Body as a Document',
|
||||
value: 'body_document',
|
||||
},
|
||||
{
|
||||
label: 'URL',
|
||||
value: 'url',
|
||||
},
|
||||
{
|
||||
label: 'Request Headers',
|
||||
value: 'request_headers',
|
||||
},
|
||||
{
|
||||
label: 'Response Headers',
|
||||
value: 'response_headers',
|
||||
},
|
||||
{
|
||||
label: 'Response Code',
|
||||
value: 'response_code',
|
||||
},
|
||||
{
|
||||
label: 'Response Message',
|
||||
value: 'response_message',
|
||||
},
|
||||
],
|
||||
width: 190,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.expression',
|
||||
dataIndex: 'expression',
|
||||
slotName: 'expression',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
slotName: 'operation',
|
||||
fixed: 'right',
|
||||
moreAction: [
|
||||
{
|
||||
eventTag: 'copy',
|
||||
label: 'common.copy',
|
||||
},
|
||||
{
|
||||
eventTag: 'setting',
|
||||
label: 'common.setting',
|
||||
},
|
||||
],
|
||||
width: 80,
|
||||
},
|
||||
];
|
||||
const disabledExpressionSuffix = ref(false);
|
||||
|
||||
function handleExtractParamTableChange(resultArr: any[], isInit?: boolean) {
|
||||
condition.value.extractParams = [...resultArr];
|
||||
if (!isInit) {
|
||||
emit('change');
|
||||
}
|
||||
}
|
||||
|
||||
const extractParamsTableRef = ref<InstanceType<typeof paramTable>>();
|
||||
const defaultExtractParamItem: Record<string, any> = {
|
||||
name: '',
|
||||
type: 'temp',
|
||||
range: 'body',
|
||||
expression: '',
|
||||
expressionType: 'regular',
|
||||
regexpMatchRule: 'expression',
|
||||
resultMatchRule: 'random',
|
||||
specifyMatchNum: 1,
|
||||
xmlMatchContentType: 'xml',
|
||||
moreSettingPopoverVisible: false,
|
||||
};
|
||||
const fastExtractionVisible = ref(false);
|
||||
const activeRecord = ref<any>({ ...defaultExtractParamItem }); // 用于暂存当前操作的提取参数表格项
|
||||
|
||||
function showFastExtraction(record: Record<string, any>) {
|
||||
activeRecord.value = { ...record };
|
||||
fastExtractionVisible.value = true;
|
||||
}
|
||||
|
||||
function handleExpressionChange(val: string) {
|
||||
extractParamsTableRef.value?.addTableLine(val, 'expression');
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理提取参数表格更多操作
|
||||
*/
|
||||
function handleExtractParamMoreActionSelect(event: ActionsItem, record: Record<string, any>) {
|
||||
activeRecord.value = { ...record };
|
||||
if (event.eventTag === 'copy') {
|
||||
emit('copy');
|
||||
} else if (event.eventTag === 'setting') {
|
||||
record.moreSettingPopoverVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取参数表格-应用更多设置
|
||||
*/
|
||||
function applyMoreSetting(record: Record<string, any>) {
|
||||
condition.value.extractParams = condition.value.extractParams.map((e) => {
|
||||
if (e.id === activeRecord.value.id) {
|
||||
record.moreSettingPopoverVisible = false;
|
||||
return {
|
||||
...activeRecord.value,
|
||||
moreSettingPopoverVisible: false,
|
||||
};
|
||||
}
|
||||
return e;
|
||||
});
|
||||
emit('change');
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取参数表格-保存快速提取的配置
|
||||
*/
|
||||
function handleFastExtractionApply(config: ExpressionConfig) {
|
||||
condition.value.extractParams = condition.value.extractParams.map((e) => {
|
||||
if (e.id === activeRecord.value.id) {
|
||||
return {
|
||||
...e,
|
||||
...config,
|
||||
};
|
||||
}
|
||||
return e;
|
||||
});
|
||||
fastExtractionVisible.value = false;
|
||||
nextTick(() => {
|
||||
extractParamsTableRef.value?.addTableLine();
|
||||
});
|
||||
emit('change');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.condition-content {
|
||||
@apply flex-1 overflow-y-auto;
|
||||
.ms-scroll-bar();
|
||||
|
||||
padding: 16px;
|
||||
border: 1px solid var(--color-text-n8);
|
||||
border-radius: var(--border-radius-small);
|
||||
.script-name-container {
|
||||
@apply flex items-center;
|
||||
|
||||
margin-right: 16px;
|
||||
&:hover {
|
||||
.edit-script-name-icon {
|
||||
@apply visible;
|
||||
}
|
||||
}
|
||||
.edit-script-name-icon {
|
||||
@apply invisible cursor-pointer;
|
||||
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
.param-popover-title {
|
||||
@apply font-medium;
|
||||
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.param-popover-subtitle {
|
||||
margin-bottom: 2px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
.param-popover-value {
|
||||
min-width: 100px;
|
||||
max-width: 280px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,168 @@
|
|||
<template>
|
||||
<div class="mb-[8px] flex items-center justify-between">
|
||||
<a-dropdown @select="addPrecondition">
|
||||
<a-button type="outline">
|
||||
<template #icon>
|
||||
<icon-plus :size="14" />
|
||||
</template>
|
||||
{{ t(props.addText) }}
|
||||
</a-button>
|
||||
<template #content>
|
||||
<a-doption v-for="key of props.conditionTypes" :key="key" :value="key">
|
||||
{{ t(conditionTypeNameMap[key]) }}
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<div v-if="$slots.titleRight" class="flex items-center">
|
||||
<slot name="titleRight"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="data.length > 0" class="flex h-[calc(100%-110px)] gap-[8px]">
|
||||
<div class="h-full w-[20%] min-w-[220px]">
|
||||
<conditionList
|
||||
v-model:list="data"
|
||||
:active-id="activeItem.id"
|
||||
@active-change="handleListActiveChange"
|
||||
@change="emit('change')"
|
||||
/>
|
||||
</div>
|
||||
<conditionContent
|
||||
v-model:data="activeItem"
|
||||
:response="props.response"
|
||||
:height-used="props.heightUsed"
|
||||
@copy="copyListItem"
|
||||
@delete="deleteListItem"
|
||||
@change="emit('change')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import conditionContent from './content.vue';
|
||||
import conditionList from './list.vue';
|
||||
|
||||
import { conditionTypeNameMap } from '@/config/apiTest';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ConditionType } from '@/models/apiTest/debug';
|
||||
|
||||
const props = defineProps<{
|
||||
list: Array<Record<string, any>>;
|
||||
conditionTypes: Array<ConditionType>;
|
||||
addText: string;
|
||||
heightUsed?: number;
|
||||
response?: string; // 响应内容
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:list', list: Array<Record<string, any>>): void;
|
||||
(e: 'change'): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const data = useVModel(props, 'list', emit);
|
||||
const activeItem = ref<Record<string, any>>({});
|
||||
|
||||
function handleListActiveChange(item: Record<string, any>) {
|
||||
activeItem.value = item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制列表项
|
||||
*/
|
||||
function copyListItem() {
|
||||
const copyItem = {
|
||||
...activeItem.value,
|
||||
id: new Date().getTime(),
|
||||
};
|
||||
data.value.push(copyItem);
|
||||
activeItem.value = copyItem;
|
||||
emit('change');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除列表项
|
||||
*/
|
||||
function deleteListItem(id: string | number) {
|
||||
data.value = data.value.filter((precondition) => precondition.id !== activeItem.value.id);
|
||||
if (activeItem.value.id === id) {
|
||||
[activeItem.value] = data.value;
|
||||
}
|
||||
emit('change');
|
||||
}
|
||||
|
||||
const scriptEx = ref(`2023-12-04 11:19:28 INFO 9026fd6a 1-1 Thread started: 9026fd6a 1-1
|
||||
2023-12-04 11:19:28 ERROR 9026fd6a 1-1 Problem in JSR223 script JSR223Sampler, message: {}
|
||||
In file: inline evaluation of: prev.getResponseCode() import java.net.URI; import org.apache.http.client.method . . . '' Encountered "import" at line 2, column 1.
|
||||
in inline evaluation of: prev.getResponseCode() import java.net.URI; import org.apache.http.client.method . . . '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException
|
||||
org.apache.http.client.method . . . '' at line number 2
|
||||
`);
|
||||
|
||||
/**
|
||||
* 添加前置条件
|
||||
* @param value script | sql | waitTime
|
||||
*/
|
||||
function addPrecondition(value: string | number | Record<string, any> | undefined) {
|
||||
const id = new Date().getTime();
|
||||
switch (value) {
|
||||
case 'script':
|
||||
data.value.push({
|
||||
id,
|
||||
type: 'script',
|
||||
name: t('apiTestDebug.preconditionScriptName'),
|
||||
scriptType: 'manual',
|
||||
enable: true,
|
||||
script: '',
|
||||
quoteScript: {
|
||||
name: '',
|
||||
script: scriptEx,
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'sql':
|
||||
data.value.push({
|
||||
id,
|
||||
type: 'sql',
|
||||
desc: '',
|
||||
enable: true,
|
||||
sqlSource: {
|
||||
name: '',
|
||||
script: scriptEx,
|
||||
storageType: 'column',
|
||||
params: [],
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'waitTime':
|
||||
data.value.push({
|
||||
id,
|
||||
type: 'waitTime',
|
||||
enable: true,
|
||||
time: 1000,
|
||||
});
|
||||
break;
|
||||
case 'extract':
|
||||
data.value.push({
|
||||
id,
|
||||
type: 'extract',
|
||||
enable: true,
|
||||
extractParams: [],
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
activeItem.value = data.value[data.value.length - 1];
|
||||
emit('change');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,129 @@
|
|||
<template>
|
||||
<MsList
|
||||
v-model:active-item-key="activeItem.id"
|
||||
v-model:focus-item-key="focusItemKey"
|
||||
v-model:data="data"
|
||||
mode="static"
|
||||
item-key-field="id"
|
||||
:item-border="false"
|
||||
class="h-full rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[12px]"
|
||||
item-class="mb-[4px] bg-white !p-[4px_8px]"
|
||||
:item-more-actions="itemMoreActions"
|
||||
active-item-class="!bg-[rgb(var(--primary-1))] text-[rgb(var(--primary-5))]"
|
||||
:virtual-list-props="{ threshold: 100, height: '100%', fixedSize: true }"
|
||||
draggable
|
||||
@item-click="handleItemClick"
|
||||
@more-action-select="handleMoreActionSelect"
|
||||
@more-actions-close="focusItemKey = ''"
|
||||
>
|
||||
<template #title="{ item, index }">
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<div
|
||||
:class="`flex h-[16px] w-[16px] items-center justify-center rounded-full ${
|
||||
activeItem.id === item.id ? ' bg-white' : 'bg-[var(--color-text-n8)]'
|
||||
}`"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
<div>{{ t(conditionTypeNameMap[item.type]) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #itemRight="{ item }">
|
||||
<a-switch v-model:model-value="item.enable" size="small" type="line" @change="() => emit('change')" />
|
||||
</template>
|
||||
</MsList>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import MsList from '@/components/pure/ms-list/index.vue';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
|
||||
import { conditionTypeNameMap } from '@/config/apiTest';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const props = defineProps<{
|
||||
list: Array<Record<string, any>>;
|
||||
activeId?: string | number;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:list', list: Array<Record<string, any>>): void;
|
||||
(e: 'activeChange', item: Record<string, any>): void;
|
||||
(e: 'change'): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const data = useVModel(props, 'list', emit);
|
||||
// 当前聚焦的列表项
|
||||
const focusItemKey = ref<any>('');
|
||||
// 当前选中的列表项
|
||||
const activeItem = ref<Record<string, any>>({});
|
||||
const itemMoreActions: ActionsItem[] = [
|
||||
{
|
||||
label: 'common.copy',
|
||||
eventTag: 'copy',
|
||||
},
|
||||
{
|
||||
label: 'common.delete',
|
||||
eventTag: 'delete',
|
||||
},
|
||||
];
|
||||
|
||||
watch(
|
||||
() => props.activeId,
|
||||
(activeId) => {
|
||||
activeItem.value = data.value.find((item) => item.id === activeId) || data.value[0] || {};
|
||||
emit('activeChange', activeItem.value);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function handleItemClick(item: Record<string, any>) {
|
||||
activeItem.value = item;
|
||||
emit('activeChange', item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制列表项
|
||||
* @param item 列表项
|
||||
*/
|
||||
function copyListItem(item: Record<string, any>) {
|
||||
const copyItem = {
|
||||
...item,
|
||||
id: new Date().getTime(),
|
||||
};
|
||||
data.value.push(copyItem);
|
||||
activeItem.value = copyItem;
|
||||
emit('activeChange', activeItem.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除列表项
|
||||
* @param item 列表项
|
||||
*/
|
||||
function deleteListItem(item: Record<string, any>) {
|
||||
data.value = data.value.filter((precondition) => precondition.id !== item.id);
|
||||
if (activeItem.value.id === item.id) {
|
||||
[activeItem.value] = data.value;
|
||||
}
|
||||
emit('activeChange', activeItem.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表项-选择更多操作项
|
||||
* @param event
|
||||
* @param item
|
||||
*/
|
||||
function handleMoreActionSelect(event: ActionsItem, item: Record<string, any>) {
|
||||
if (event.eventTag === 'copy') {
|
||||
copyListItem(item);
|
||||
} else if (event.eventTag === 'delete') {
|
||||
deleteListItem(item);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,275 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
v-model:visible="innerVisible"
|
||||
:width="680"
|
||||
:title="t('apiTestDebug.fastExtraction')"
|
||||
disabled-width-drag
|
||||
@confirm="emit('apply', expressionForm)"
|
||||
>
|
||||
<div v-if="expressionForm.expressionType === 'regular'" class="h-[400px]">
|
||||
<MsCodeEditor
|
||||
:model-value="props.response"
|
||||
theme="vs"
|
||||
height="336px"
|
||||
:languages="[LanguageEnum.JSON, LanguageEnum.HTML, LanguageEnum.XML, LanguageEnum.PLAINTEXT]"
|
||||
:language="LanguageEnum.JSON"
|
||||
:show-full-screen="false"
|
||||
show-language-change
|
||||
read-only
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="expressionForm.expressionType === 'JSONPath'" class="code-container">
|
||||
<MsJsonPathPicker :data="props.response || ''" class="bg-white" @pick="handlePathPick" />
|
||||
</div>
|
||||
<div v-else-if="expressionForm.expressionType === 'XPath'" class="code-container">
|
||||
<MsXPathPicker :xml-string="props.response || ''" class="bg-white" @pick="handlePathPick" />
|
||||
</div>
|
||||
<a-form ref="expressionFormRef" :model="expressionForm" layout="vertical" class="mt-[16px]">
|
||||
<a-form-item
|
||||
v-if="expressionForm.expressionType === 'regular'"
|
||||
field="expression"
|
||||
:label="t('apiTestDebug.regularExpression')"
|
||||
:rules="[{ required: true, message: t('apiTestDebug.regularExpressionRequired') }]"
|
||||
asterisk-position="end"
|
||||
>
|
||||
<div class="form-input-wrapper">
|
||||
<a-input
|
||||
v-model:model-value="expressionForm.expression"
|
||||
:placeholder="t('apiTestDebug.regularExpressionPlaceholder', { ex: '/<title>(.*?)</title>/' })"
|
||||
class="flex-1"
|
||||
/>
|
||||
<a-button type="outline" :disabled="expressionForm.expression.trim() === ''" @click="testExpression">
|
||||
{{ t('apiTestDebug.test') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-else-if="expressionForm.expressionType === 'JSONPath'"
|
||||
field="expression"
|
||||
label="JSONPath"
|
||||
:rules="[{ required: true, message: t('apiTestDebug.JSONPathRequired') }]"
|
||||
asterisk-position="end"
|
||||
>
|
||||
<div class="form-input-wrapper">
|
||||
<a-input
|
||||
v-model:model-value="expressionForm.expression"
|
||||
:placeholder="t('apiTestDebug.JSONPathPlaceholder')"
|
||||
class="flex-1"
|
||||
/>
|
||||
<a-button type="outline" :disabled="expressionForm.expression.trim() === ''" @click="testExpression">
|
||||
{{ t('apiTestDebug.test') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-else
|
||||
field="expression"
|
||||
label="XPath"
|
||||
:rules="[{ required: true, message: t('apiTestDebug.XPathRequired') }]"
|
||||
asterisk-position="end"
|
||||
>
|
||||
<div class="form-input-wrapper">
|
||||
<a-input
|
||||
v-model:model-value="expressionForm.expression"
|
||||
:placeholder="t('apiTestDebug.XPathPlaceholder')"
|
||||
class="flex-1"
|
||||
/>
|
||||
<a-button type="outline" :disabled="expressionForm.expression.trim() === ''" @click="testExpression">
|
||||
{{ t('apiTestDebug.test') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[12px]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="text-[var(--color-text-1)]">{{ t('apiTestDebug.matchResult') }}</div>
|
||||
<a-tooltip :content-style="{ maxWidth: '500px' }">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] cursor-pointer text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
<template #content>
|
||||
<div
|
||||
>{{ t('apiTestDebug.matchExpressionTip', { prefix: `${t('apiTestDebug.matchExpression')}: ` }) }}
|
||||
</div>
|
||||
<div>{{ t('apiTestDebug.matchGroupTip', { prefix: `${t('apiTestDebug.matchGroup')}: ` }) }}</div>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-radio-group
|
||||
v-if="expressionForm.expressionType === 'regular'"
|
||||
v-model:model-value="expressionForm.regexpMatchRule"
|
||||
type="button"
|
||||
size="small"
|
||||
>
|
||||
<a-radio value="expression">{{ t('apiTestDebug.matchExpression') }}</a-radio>
|
||||
<a-radio value="group">{{ t('apiTestDebug.matchGroup') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div class="match-result">
|
||||
<div v-if="isMatched && matchResult.length === 0">{{ t('apiTestDebug.noMatchResult') }}</div>
|
||||
<pre v-for="(e, i) of matchResult" :key="i">{{ e }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<a-collapse v-model:active-key="moreSettingActive" :bordered="false" :show-expand-icon="false" class="mt-[16px]">
|
||||
<a-collapse-item :key="1">
|
||||
<template #header>
|
||||
<MsButton
|
||||
type="text"
|
||||
@click="() => (moreSettingActive.length > 0 ? (moreSettingActive = []) : (moreSettingActive = [1]))"
|
||||
>
|
||||
{{ t('apiTestDebug.moreSetting') }}
|
||||
<icon-down v-if="moreSettingActive.length > 0" class="text-rgb(var(--primary-5))" />
|
||||
<icon-right v-else class="text-rgb(var(--primary-5))" />
|
||||
</MsButton>
|
||||
</template>
|
||||
<div class="mt-[16px]">
|
||||
<moreSetting v-model:config="expressionForm" />
|
||||
</div>
|
||||
</a-collapse-item>
|
||||
</a-collapse>
|
||||
</MsDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import FormInstance from '@arco-design/web-vue';
|
||||
import { JSONPath } from 'jsonpath-plus';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import MsJsonPathPicker from '@/components/pure/ms-jsonpath-picker/index.vue';
|
||||
import MsXPathPicker from '@/components/pure/ms-jsonpath-picker/xpath.vue';
|
||||
import moreSetting from './moreSetting.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { matchXMLWithXPath } from '@/utils/xpath';
|
||||
|
||||
import { ExpressionConfig } from '@/models/apiTest/debug';
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
config: ExpressionConfig;
|
||||
response?: string; // 响应内容
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'apply', config: ExpressionConfig): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const innerVisible = useVModel(props, 'visible', emit);
|
||||
const expressionForm = ref({ ...props.config });
|
||||
const expressionFormRef = ref<typeof FormInstance>();
|
||||
const matchResult = ref<any[]>([]); // 当前匹配结果
|
||||
const isMatched = ref(false); // 是否执行过匹配
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
if (val) {
|
||||
expressionForm.value = { ...props.config };
|
||||
matchResult.value = [];
|
||||
isMatched.value = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function handlePathPick(xpath: string) {
|
||||
expressionForm.value.expression = xpath;
|
||||
}
|
||||
|
||||
/*
|
||||
* 测试表达式
|
||||
*/
|
||||
function testExpression() {
|
||||
switch (props.config.expressionType) {
|
||||
case 'XPath':
|
||||
const nodes = matchXMLWithXPath(props.response || '', expressionForm.value.expression);
|
||||
if (nodes) {
|
||||
// 直接匹配到文本信息
|
||||
if (typeof nodes === 'boolean' || typeof nodes === 'string' || typeof nodes === 'number') {
|
||||
matchResult.value = [nodes];
|
||||
} else if (Array.isArray(nodes)) {
|
||||
// 匹配到多个节点信息
|
||||
matchResult.value = nodes
|
||||
.map((node) => node.textContent?.split('\n') || false)
|
||||
.flat(Infinity)
|
||||
.filter(Boolean);
|
||||
} else {
|
||||
// 匹配到单个节点信息
|
||||
matchResult.value = nodes.textContent ? [nodes.textContent] : [];
|
||||
}
|
||||
} else {
|
||||
matchResult.value = [];
|
||||
}
|
||||
break;
|
||||
case 'JSONPath':
|
||||
try {
|
||||
matchResult.value = JSONPath({
|
||||
json: props.response ? JSON.parse(props.response) : '',
|
||||
path: expressionForm.value.expression,
|
||||
});
|
||||
} catch (error) {
|
||||
matchResult.value = JSONPath({ json: props.response || '', path: expressionForm.value.expression });
|
||||
}
|
||||
break;
|
||||
case 'regular':
|
||||
default:
|
||||
// 先把前后的/和g去掉才能生成正则表达式
|
||||
const matchesIterator = props.response?.matchAll(
|
||||
new RegExp(expressionForm.value.expression.replace(/^\/|\/$|\/g$/g, ''), 'g')
|
||||
);
|
||||
if (matchesIterator) {
|
||||
const matches = Array.from(matchesIterator);
|
||||
try {
|
||||
if (expressionForm.value.regexpMatchRule === 'expression') {
|
||||
// 匹配表达式,取第一个匹配结果,是完整匹配结果
|
||||
matchResult.value = matches.map((e) => e[0]) || [];
|
||||
} else {
|
||||
matchResult.value = matches.map((e) => e.slice(1)).flat(Infinity) || []; // 匹配分组,取匹配结果的第二项开始,是分组匹配结果
|
||||
}
|
||||
} catch (error) {
|
||||
// 读取匹配数据错误说明无对应的匹配结果
|
||||
matchResult.value = [];
|
||||
isMatched.value = true;
|
||||
}
|
||||
} else {
|
||||
matchResult.value = [];
|
||||
}
|
||||
break;
|
||||
}
|
||||
isMatched.value = true;
|
||||
}
|
||||
|
||||
const moreSettingActive = ref<number[]>([]);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.form-input-wrapper {
|
||||
@apply flex w-full items-center justify-between;
|
||||
|
||||
gap: 12px;
|
||||
}
|
||||
.code-container {
|
||||
padding: 12px;
|
||||
height: 400px;
|
||||
border-radius: var(--border-radius-small);
|
||||
background-color: var(--color-text-n9);
|
||||
}
|
||||
.match-result {
|
||||
@apply overflow-y-auto bg-white;
|
||||
.ms-scroll-bar();
|
||||
|
||||
margin-top: 12px;
|
||||
padding: 12px;
|
||||
min-height: 32px;
|
||||
max-height: 300px;
|
||||
border-radius: var(--border-radius-small);
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,114 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="expressionForm.expressionType === 'regular' && props.isPopover" class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[14px] text-[var(--color-text-1)]">
|
||||
{{ t('apiTestDebug.expressionMatchRule') }}
|
||||
</div>
|
||||
<a-radio-group v-model:model-value="expressionForm.regexpMatchRule" size="small">
|
||||
<a-radio value="expression">
|
||||
<div class="flex items-center">
|
||||
{{ t('apiTestDebug.matchExpression') }}
|
||||
<a-tooltip :content="t('apiTestDebug.matchExpressionTip')" :content-style="{ maxWidth: '500px' }">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] cursor-pointer text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-radio>
|
||||
<a-radio value="group">
|
||||
<div class="flex items-center">
|
||||
{{ t('apiTestDebug.matchGroup') }}
|
||||
<a-tooltip :content="t('apiTestDebug.matchGroupTip')" :content-style="{ maxWidth: '500px' }">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] cursor-pointer text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[14px] text-[var(--color-text-1)]">
|
||||
{{ t('apiTestDebug.resultMatchRule') }}
|
||||
</div>
|
||||
<a-radio-group v-model:model-value="expressionForm.resultMatchRule" size="small">
|
||||
<a-radio value="random">
|
||||
<div class="flex items-center">
|
||||
{{ t('apiTestDebug.randomMatch') }}
|
||||
<a-tooltip :content="t('apiTestDebug.randomMatchTip')" :content-style="{ maxWidth: '400px' }">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] cursor-pointer text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-radio>
|
||||
<a-radio value="specify">
|
||||
<div class="flex items-center">
|
||||
{{ t('apiTestDebug.specifyMatch') }}
|
||||
<a-tooltip :content="t('apiTestDebug.specifyMatchTip')" :content-style="{ maxWidth: '400px' }">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] cursor-pointer text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-radio>
|
||||
<a-radio value="all">
|
||||
<div class="flex items-center">
|
||||
{{ t('apiTestDebug.allMatch') }}
|
||||
<a-tooltip :content="t('apiTestDebug.allMatchTip')" :content-style="{ maxWidth: '400px' }">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] cursor-pointer text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div v-if="expressionForm.resultMatchRule === 'specify'" class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">
|
||||
{{ t('apiTestDebug.specifyMatchResult') }}
|
||||
</div>
|
||||
<div class="flex items-center gap-[8px]">
|
||||
{{ t('apiTestDebug.index') }}
|
||||
<a-input-number v-model:model-value="expressionForm.specifyMatchNum" :min="1" class="w-[80px]" />
|
||||
{{ t('apiTestDebug.unit') }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="expressionForm.expressionType === 'XPath'" class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">
|
||||
{{ t('apiTestDebug.contentType') }}
|
||||
</div>
|
||||
<a-radio-group v-model:model-value="expressionForm.xmlMatchContentType" size="small">
|
||||
<a-radio value="xml"> XML </a-radio>
|
||||
<a-radio value="html"> HTML </a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { ExpressionConfig } from '@/models/apiTest/debug';
|
||||
|
||||
const props = defineProps<{
|
||||
config: ExpressionConfig;
|
||||
isPopover?: boolean; // 是否是弹出框展示,弹出框展示时才显示表达式类型
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:config', config: ExpressionConfig): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const expressionForm = useVModel(props, 'config', emit);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<MsBaseTable v-bind="propsRes" id="headerTable" :hoverable="false" no-disable v-on="propsEvent">
|
||||
<MsBaseTable v-bind="propsRes" :hoverable="false" no-disable v-on="propsEvent">
|
||||
<!-- 表格头 slot -->
|
||||
<template #encodeTitle>
|
||||
<div class="flex items-center text-[var(--color-text-3)]">
|
||||
|
@ -42,7 +42,7 @@
|
|||
v-model:model-value="record[columnConfig.dataIndex as string]"
|
||||
:placeholder="t('apiTestDebug.paramNamePlaceholder')"
|
||||
class="param-input"
|
||||
@input="(val) => addTableLine(val)"
|
||||
@input="(val) => addTableLine(val, 'name')"
|
||||
/>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
@ -69,6 +69,25 @@
|
|||
@change="(val) => handleTypeChange(val, record)"
|
||||
/>
|
||||
</template>
|
||||
<template #expressionType="{ record, columnConfig }">
|
||||
<a-select
|
||||
v-model:model-value="record.expressionType"
|
||||
:options="columnConfig.typeOptions || []"
|
||||
class="param-input w-[110px]"
|
||||
@change="(val) => handleExpressionTypeChange(val)"
|
||||
/>
|
||||
</template>
|
||||
<template #range="{ record, columnConfig }">
|
||||
<a-select
|
||||
v-model:model-value="record.range"
|
||||
:options="columnConfig.typeOptions || []"
|
||||
class="param-input w-[180px]"
|
||||
@change="(val) => handleRangeChange(val)"
|
||||
/>
|
||||
</template>
|
||||
<template #expression="{ record, rowIndex, columnConfig }">
|
||||
<slot name="expression" :record="record" :row-index="rowIndex" :column-config="columnConfig"></slot>
|
||||
</template>
|
||||
<template #value="{ record, columnConfig }">
|
||||
<a-popover
|
||||
v-if="columnConfig.isNormal"
|
||||
|
@ -88,13 +107,13 @@
|
|||
v-model:model-value="record.value"
|
||||
class="param-input"
|
||||
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
||||
@input="(val) => addTableLine(val)"
|
||||
@input="(val) => addTableLine(val, 'value')"
|
||||
/>
|
||||
</a-popover>
|
||||
<MsParamsInput
|
||||
v-else
|
||||
v-model:value="record.value"
|
||||
@change="addTableLine"
|
||||
@change="(val) => addTableLine(val, 'value')"
|
||||
@dblclick="quickInputParams(record)"
|
||||
@apply="handleParamSettingApply"
|
||||
/>
|
||||
|
@ -104,16 +123,16 @@
|
|||
<a-input-number
|
||||
v-model:model-value="record.min"
|
||||
:placeholder="t('apiTestDebug.paramMin')"
|
||||
class="param-input"
|
||||
@input="(val) => addTableLine(val)"
|
||||
></a-input-number>
|
||||
class="param-input param-input-number"
|
||||
@input="(val) => addTableLine(val || '', 'min')"
|
||||
/>
|
||||
<div class="mx-[4px]">~</div>
|
||||
<a-input-number
|
||||
v-model:model-value="record.max"
|
||||
:placeholder="t('apiTestDebug.paramMax')"
|
||||
class="param-input"
|
||||
@input="(val) => addTableLine(val)"
|
||||
></a-input-number>
|
||||
@input="(val) => addTableLine(val || '', 'max')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #tag="{ record, columnConfig }">
|
||||
|
@ -134,14 +153,14 @@
|
|||
v-model:model-value="record[columnConfig.dataIndex as string]"
|
||||
:max-tag-count="1"
|
||||
class="param-input"
|
||||
@change="addTableLine"
|
||||
@change="(val) => addTableLine(val, 'tag')"
|
||||
/>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template #desc="{ record, columnConfig }">
|
||||
<paramDescInput
|
||||
v-model:desc="record[columnConfig.dataIndex as string]"
|
||||
@input="addTableLine"
|
||||
@input="(val) => addTableLine(val, 'desc')"
|
||||
@dblclick="quickInputDesc(record)"
|
||||
@change="handleDescChange"
|
||||
/>
|
||||
|
@ -152,13 +171,19 @@
|
|||
size="small"
|
||||
class="param-input-switch"
|
||||
type="line"
|
||||
@change="(val) => addTableLine(val.toString())"
|
||||
@change="(val) => addTableLine(val.toString(), 'encode')"
|
||||
/>
|
||||
</template>
|
||||
<template #mustContain="{ record, columnConfig }">
|
||||
<a-checkbox v-model:model-value="record[columnConfig.dataIndex as string]" @change="(val) => addTableLine(val)" />
|
||||
</template>
|
||||
<template #operation="{ record, rowIndex, columnConfig }">
|
||||
<slot name="operationPre" :record="record" :row-index="rowIndex" :column-config="columnConfig"></slot>
|
||||
<MsTableMoreAction
|
||||
v-if="columnConfig.moreAction"
|
||||
:list="getMoreActionList(columnConfig.moreAction, record)"
|
||||
@select="(e) => handleMoreActionSelect(e, record)"
|
||||
/>
|
||||
<a-trigger
|
||||
v-if="columnConfig.format && columnConfig.format !== RequestBodyFormat.X_WWW_FORM_URLENCODED"
|
||||
trigger="click"
|
||||
|
@ -172,7 +197,7 @@
|
|||
v-model:model-value="record.contentType"
|
||||
:options="Object.values(RequestContentTypeEnum).map((e) => ({ label: e, value: e }))"
|
||||
allow-create
|
||||
@change="(val) => addTableLine(val as string)"
|
||||
@change="(val) => addTableLine(val as string, 'contentType')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -182,7 +207,7 @@
|
|||
v-model:model-value="record.enable"
|
||||
size="small"
|
||||
type="line"
|
||||
@change="(val) => addTableLine(val)"
|
||||
@change="(val) => addTableLine(val, 'enable')"
|
||||
/>
|
||||
<icon-minus-circle
|
||||
v-if="paramsLength > 1 && rowIndex !== paramsLength - 1"
|
||||
|
@ -261,6 +286,8 @@
|
|||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumnData } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import MsTagsGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
|
||||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||
import MsParamsInput from '@/components/business/ms-params-input/index.vue';
|
||||
|
@ -295,6 +322,7 @@
|
|||
typeOptions?: { label: string; value: string }[]; // 用于 type 列选择器选项
|
||||
typeTitleTooltip?: string; // 用于 type 表头列展示的 tooltip
|
||||
hasEnable?: boolean; // 用于 operation 列区分是否有 enable 开关
|
||||
moreAction?: ActionsItem[]; // 用于 operation 列更多操作按钮配置
|
||||
format?: RequestBodyFormat | 'query' | 'rest'; // 用于 operation 列区分是否有请求体格式选择器
|
||||
};
|
||||
|
||||
|
@ -317,6 +345,7 @@
|
|||
disabled?: boolean; // 是否禁用
|
||||
showSelectorAll?: boolean; // 是否显示全选
|
||||
isSimpleSetting?: boolean; // 是否简单Column设置
|
||||
response?: string; // 响应内容
|
||||
}>(),
|
||||
{
|
||||
selectable: true,
|
||||
|
@ -342,14 +371,18 @@
|
|||
const emit = defineEmits<{
|
||||
(e: 'update:params', value: any[]): void;
|
||||
(e: 'change', data: any[], isInit?: boolean): void;
|
||||
(e: 'moreActionSelect', event: ActionsItem, record: Record<string, any>): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const tableStore = useTableStore();
|
||||
if (props.showSetting && props.tableKey) {
|
||||
await tableStore.initColumn(props.tableKey, props.columns);
|
||||
async function initColumns() {
|
||||
if (props.showSetting && props.tableKey) {
|
||||
await tableStore.initColumn(props.tableKey, props.columns);
|
||||
}
|
||||
}
|
||||
initColumns();
|
||||
|
||||
const { propsRes, propsEvent } = useTable(() => Promise.resolve([]), {
|
||||
tableKey: props.showSetting ? props.tableKey : undefined,
|
||||
|
@ -399,14 +432,21 @@
|
|||
/**
|
||||
* 当表格输入框变化时,给参数表格添加一行数据行
|
||||
* @param val 输入值
|
||||
* @param key 当前列的 key
|
||||
* @param isForce 是否强制添加
|
||||
*/
|
||||
function addTableLine(val?: string | number | boolean | (string | number | boolean)[], isForce?: boolean) {
|
||||
function addTableLine(
|
||||
val?: string | number | boolean | (string | number | boolean)[],
|
||||
key?: string,
|
||||
isForce?: boolean
|
||||
) {
|
||||
const lastData = propsRes.value.data[propsRes.value.data.length - 1];
|
||||
const isNotChange = Object.keys(props.defaultParamItem).every(
|
||||
(key) => JSON.stringify(lastData[key]) === JSON.stringify(props.defaultParamItem[key])
|
||||
);
|
||||
if (isForce || (val !== '' && val !== undefined && !isNotChange)) {
|
||||
// 当不传入输入值或对应列的 key 时,遍历整个数据对象判断是否有变化;当传入输入值或对应列的 key 时,判断对应列的值是否有变化
|
||||
const isNotChange =
|
||||
val === undefined || key === undefined
|
||||
? Object.keys(props.defaultParamItem).every((e) => lastData[e] === props.defaultParamItem[e])
|
||||
: JSON.stringify(lastData[key]) === JSON.stringify(props.defaultParamItem[key]);
|
||||
if (isForce || (val !== '' && !isNotChange)) {
|
||||
propsRes.value.data.push({
|
||||
id: new Date().getTime(),
|
||||
...props.defaultParamItem,
|
||||
|
@ -434,12 +474,12 @@
|
|||
activeQuickInputRecord.value.value = quickInputParamValue.value;
|
||||
showQuickInputParam.value = false;
|
||||
clearQuickInputParam();
|
||||
addTableLine(quickInputParamValue.value, true);
|
||||
addTableLine(quickInputParamValue.value, 'value', true);
|
||||
emit('change', propsRes.value.data);
|
||||
}
|
||||
|
||||
function handleParamSettingApply(val: string | number) {
|
||||
addTableLine(val);
|
||||
addTableLine(val, 'value');
|
||||
}
|
||||
|
||||
const showQuickInputDesc = ref(false);
|
||||
|
@ -460,7 +500,7 @@
|
|||
activeQuickInputRecord.value.desc = quickInputDescValue.value;
|
||||
showQuickInputDesc.value = false;
|
||||
clearQuickInputDesc();
|
||||
addTableLine(quickInputDescValue.value, true);
|
||||
addTableLine(quickInputDescValue.value, 'desc', true);
|
||||
emit('change', propsRes.value.data);
|
||||
}
|
||||
|
||||
|
@ -472,7 +512,7 @@
|
|||
val: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[],
|
||||
record: Partial<Param>
|
||||
) {
|
||||
addTableLine(val as string);
|
||||
addTableLine(val as string, 'type');
|
||||
// 根据参数类型自动推断 Content-Type 类型
|
||||
if (record.contentType) {
|
||||
if (val === 'file') {
|
||||
|
@ -484,6 +524,42 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleExpressionTypeChange(
|
||||
val: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
|
||||
) {
|
||||
addTableLine(val as string, 'expressionType');
|
||||
}
|
||||
|
||||
function handleRangeChange(
|
||||
val: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
|
||||
) {
|
||||
addTableLine(val as string, 'range');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取更多操作按钮列表
|
||||
* @param actions 按钮列表
|
||||
* @param record 当前行数据
|
||||
*/
|
||||
function getMoreActionList(actions: ActionsItem[], record: Record<string, any>) {
|
||||
if (props.columns.findIndex((e) => e.dataIndex === 'expression') !== -1) {
|
||||
// 如果有expression列,就需要根据expression的值来判断按钮列表是否禁用
|
||||
if (record.expression === '' || record.expression === undefined || record.expression === null) {
|
||||
return actions.map((e) => ({ ...e, disabled: true }));
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
function handleMoreActionSelect(event: ActionsItem, record: Record<string, any>) {
|
||||
emit('moreActionSelect', event, record);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
addTableLine,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -491,10 +567,12 @@
|
|||
background-color: var(--color-text-n9);
|
||||
}
|
||||
:deep(.arco-table-cell-align-left) {
|
||||
padding: 16px 4px;
|
||||
padding: 16px 12px;
|
||||
}
|
||||
:deep(.arco-table-cell) {
|
||||
padding: 11px 4px;
|
||||
:deep(.arco-table-td) {
|
||||
.arco-table-cell {
|
||||
padding: 12px 2px;
|
||||
}
|
||||
}
|
||||
:deep(.param-input:not(.arco-input-focus, .arco-select-view-focus)) {
|
||||
&:not(:hover) {
|
||||
|
@ -510,6 +588,24 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
:deep(.param-input-number) {
|
||||
@apply pr-0;
|
||||
.arco-input {
|
||||
@apply text-right;
|
||||
}
|
||||
.arco-input-suffix {
|
||||
@apply hidden;
|
||||
}
|
||||
&:hover,
|
||||
&.arco-input-focus {
|
||||
.arco-input {
|
||||
@apply text-left;
|
||||
}
|
||||
.arco-input-suffix {
|
||||
@apply inline-flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
.content-type-trigger-content {
|
||||
@apply bg-white;
|
||||
|
||||
|
@ -517,20 +613,6 @@
|
|||
border-radius: var(--border-radius-small);
|
||||
box-shadow: 0 4px 10px -1px rgb(100 100 102 / 15%);
|
||||
}
|
||||
.param-input {
|
||||
.param-input-mock-icon {
|
||||
@apply invisible;
|
||||
}
|
||||
&:hover,
|
||||
&.arco-input-focus {
|
||||
.param-input-mock-icon {
|
||||
@apply visible cursor-pointer;
|
||||
&:hover {
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.param-popover-title {
|
||||
@apply font-medium;
|
||||
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
v-model:visible="innerVisible"
|
||||
:width="960"
|
||||
:title="t('apiTestDebug.quoteSource')"
|
||||
:ok-disabled="selectedKey === ''"
|
||||
@confirm="handleConfirm"
|
||||
>
|
||||
<div class="mb-[16px] flex items-center justify-between">
|
||||
<div class="text-[var(--color-text-1)]">{{ t('apiTestDebug.sourceList') }}</div>
|
||||
<a-input-search
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('project.projectVersion.searchPlaceholder')"
|
||||
class="w-[230px]"
|
||||
allow-clear
|
||||
@search="searchSource"
|
||||
@press-enter="searchSource"
|
||||
/>
|
||||
</div>
|
||||
<MsBaseTable v-bind="propsRes" v-model:selected-key="selectedKey" v-on="propsEvent">
|
||||
<template #timeout="{ record }">
|
||||
<a-tooltip :content="record.timeout?.toLocaleString()">
|
||||
<div class="one-line-text">{{ record.timeout?.toLocaleString() }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
</MsDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
selectedKey?: string;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'apply', value: any): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const innerVisible = useVModel(props, 'visible', emit);
|
||||
const keyword = ref('');
|
||||
const selectedKey = ref(props.selectedKey || '');
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'apiTestDebug.sqlSourceName',
|
||||
dataIndex: 'name',
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.driver',
|
||||
dataIndex: 'driver',
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.username',
|
||||
dataIndex: 'username',
|
||||
showTooltip: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.maxConnection',
|
||||
dataIndex: 'maxConnection',
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.timeout',
|
||||
dataIndex: 'timeout',
|
||||
slotName: 'timeout',
|
||||
align: 'right',
|
||||
width: 120,
|
||||
},
|
||||
];
|
||||
async function loadSource() {
|
||||
return Promise.resolve({
|
||||
list: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'test',
|
||||
driver: 'com.mysql.cj.jdbc.Driver',
|
||||
username: 'root',
|
||||
maxConnection: 10,
|
||||
timeout: 1000,
|
||||
storageType: 'column',
|
||||
params: [],
|
||||
script: 'select * from test1',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'test2',
|
||||
driver: 'com.mysql.cj.jdbc.Driver',
|
||||
username: 'root',
|
||||
maxConnection: 10,
|
||||
timeout: 1000,
|
||||
storageType: 'column',
|
||||
params: [],
|
||||
script: 'select * from test2',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'test3',
|
||||
driver: 'com.mysql.cj.jdbc.Driver',
|
||||
username: 'root',
|
||||
maxConnection: 10,
|
||||
timeout: 10000000000,
|
||||
storageType: 'result',
|
||||
params: [],
|
||||
script: 'select * from test3',
|
||||
},
|
||||
],
|
||||
total: 99,
|
||||
});
|
||||
}
|
||||
const { propsRes, propsEvent, setLoadListParams, loadList } = useTable(loadSource, {
|
||||
columns,
|
||||
scroll: { x: '100%' },
|
||||
heightUsed: 300,
|
||||
selectable: true,
|
||||
showSelectorAll: false,
|
||||
selectorType: 'radio',
|
||||
firstColumnWidth: 44,
|
||||
});
|
||||
function searchSource() {
|
||||
setLoadListParams({
|
||||
keyword: keyword.value,
|
||||
});
|
||||
loadList();
|
||||
}
|
||||
searchSource();
|
||||
|
||||
function handleConfirm() {
|
||||
innerVisible.value = false;
|
||||
emit(
|
||||
'apply',
|
||||
propsRes.value.data.find((item) => item.id === selectedKey.value)
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-table-cell-align-left) {
|
||||
padding-left: 9px;
|
||||
}
|
||||
</style>
|
|
@ -4,12 +4,24 @@
|
|||
</a-button>
|
||||
<MsDrawer
|
||||
v-model:visible="showBatchAddParamDrawer"
|
||||
:title="t('common.batchAdd')"
|
||||
:width="680"
|
||||
:ok-text="t('apiTestDebug.apply')"
|
||||
disabled-width-drag
|
||||
@confirm="applyBatchParams"
|
||||
>
|
||||
<template #title>
|
||||
{{ t('common.batchAdd') }}
|
||||
<a-tooltip position="right">
|
||||
<icon-exclamation-circle
|
||||
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
<template #content>
|
||||
<div>{{ t('apiTestDebug.batchAddParamsTip2') }} </div>
|
||||
<div>{{ t('apiTestDebug.batchAddParamsTip3') }} </div>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<div class="flex h-full">
|
||||
<MsCodeEditor
|
||||
v-if="showBatchAddParamDrawer"
|
||||
|
@ -20,13 +32,8 @@
|
|||
:show-full-screen="false"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex flex-col">
|
||||
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||
{{ t('apiTestDebug.batchAddParamsTip') }}
|
||||
</div>
|
||||
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||
{{ t('apiTestDebug.batchAddParamsTip2') }}
|
||||
</div>
|
||||
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||
{{ t('apiTestDebug.batchAddParamsTip') }}
|
||||
</div>
|
||||
</template>
|
||||
</MsCodeEditor>
|
||||
|
@ -74,29 +81,27 @@
|
|||
*/
|
||||
function applyBatchParams() {
|
||||
const arr = batchParamsCode.value.replaceAll('\r', '\n').split('\n'); // 先将回车符替换成换行符,避免粘贴的代码是以回车符分割的,然后以换行符分割
|
||||
const resultArr = arr
|
||||
.map((item, i) => {
|
||||
const [name, value] = item.split(':');
|
||||
if (name || value) {
|
||||
return {
|
||||
id: new Date().getTime() + i,
|
||||
name: name?.trim(),
|
||||
value: value?.trim(),
|
||||
required: false,
|
||||
type: 'string',
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
contentType: RequestContentTypeEnum.TEXT,
|
||||
desc: '',
|
||||
encode: false,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter((item) => item);
|
||||
const tempObj: Record<string, any> = {}; // 同名参数去重,保留最新的
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const [name, value] = arr[i].split(':');
|
||||
if (name) {
|
||||
tempObj[name.trim()] = {
|
||||
id: new Date().getTime() + i,
|
||||
name: name.trim(),
|
||||
value: value?.trim(),
|
||||
required: false,
|
||||
type: 'string',
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
contentType: RequestContentTypeEnum.TEXT,
|
||||
desc: '',
|
||||
encode: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
showBatchAddParamDrawer.value = false;
|
||||
batchParamsCode.value = '';
|
||||
emit('apply', resultArr);
|
||||
emit('apply', Object.values(tempObj));
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
|
||||
import paramTable, { type ParamTableColumn } from '../../../components/paramTable.vue';
|
||||
import batchAddKeyVal from './batchAddKeyVal.vue';
|
||||
|
||||
|
@ -154,6 +155,7 @@
|
|||
title: 'apiTestDebug.paramLengthRange',
|
||||
dataIndex: 'lengthRange',
|
||||
slotName: 'lengthRange',
|
||||
align: 'center',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
|
@ -244,12 +246,12 @@
|
|||
// 当前代码编辑器的语言
|
||||
const currentCodeLanguage = computed(() => {
|
||||
if (format.value === RequestBodyFormat.JSON) {
|
||||
return 'json';
|
||||
return LanguageEnum.JSON;
|
||||
}
|
||||
if (format.value === RequestBodyFormat.XML) {
|
||||
return 'xml';
|
||||
return LanguageEnum.XML;
|
||||
}
|
||||
return 'plaintext';
|
||||
return LanguageEnum.PLAINTEXT;
|
||||
});
|
||||
|
||||
function formatChange() {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
:columns="columns"
|
||||
:height-used="heightUsed"
|
||||
:scroll="scroll"
|
||||
draggable
|
||||
@change="handleParamTableChange"
|
||||
/>
|
||||
</template>
|
||||
|
@ -57,31 +58,14 @@
|
|||
},
|
||||
];
|
||||
|
||||
const heightUsed = ref<number | undefined>(undefined);
|
||||
const heightUsed = computed(() => {
|
||||
if (props.layout === 'horizontal') {
|
||||
return 422;
|
||||
}
|
||||
return 422 + props.secondBoxHeight;
|
||||
});
|
||||
const scroll = computed(() => (props.layout === 'horizontal' ? { x: '700px' } : { x: '100%' }));
|
||||
|
||||
watch(
|
||||
() => props.layout,
|
||||
(val) => {
|
||||
heightUsed.value = val === 'horizontal' ? 422 : 422 + props.secondBoxHeight;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.secondBoxHeight,
|
||||
(val) => {
|
||||
if (props.layout === 'vertical') {
|
||||
heightUsed.value = 422 + val;
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 批量参数代码转换为参数表格数据
|
||||
*/
|
||||
|
|
|
@ -66,6 +66,7 @@
|
|||
:max="0.98"
|
||||
min="10px"
|
||||
:direction="activeLayout"
|
||||
second-container-class="!overflow-y-hidden"
|
||||
@expand-change="handleExpandChange"
|
||||
>
|
||||
<template #first>
|
||||
|
@ -103,8 +104,14 @@
|
|||
@change="handleActiveDebugChange"
|
||||
/>
|
||||
<precondition
|
||||
v-else-if="activeDebug.activeTab === RequestComposition.PREFIX"
|
||||
v-else-if="activeDebug.activeTab === RequestComposition.PRECONDITION"
|
||||
v-model:params="activeDebug.preconditions"
|
||||
@change="handleActiveDebugChange"
|
||||
/>
|
||||
<postcondition
|
||||
v-else-if="activeDebug.activeTab === RequestComposition.POST_CONDITION"
|
||||
v-model:params="activeDebug.postConditions"
|
||||
:response="activeDebug.response.body"
|
||||
:layout="activeLayout"
|
||||
:second-box-height="secondBoxHeight"
|
||||
@change="handleActiveDebugChange"
|
||||
|
@ -166,6 +173,7 @@
|
|||
import debugAuth from './auth.vue';
|
||||
import debugBody, { BodyParams } from './body.vue';
|
||||
import debugHeader from './header.vue';
|
||||
import postcondition from './postcondition.vue';
|
||||
import precondition from './precondition.vue';
|
||||
import debugQuery from './query.vue';
|
||||
import debugRest from './rest.vue';
|
||||
|
@ -191,33 +199,105 @@
|
|||
binarySend: false,
|
||||
raw: '',
|
||||
};
|
||||
const debugTabs = ref<TabItem[]>([
|
||||
{
|
||||
id: initDefaultId,
|
||||
moduleProtocol: 'http',
|
||||
activeTab: RequestComposition.HEADER,
|
||||
label: t('apiTestDebug.newApi'),
|
||||
closable: true,
|
||||
method: RequestMethods.GET,
|
||||
unSave: false,
|
||||
headerParams: [],
|
||||
bodyParams: cloneDeep(defaultBodyParams),
|
||||
queryParams: [],
|
||||
restParams: [],
|
||||
authParams: {
|
||||
authType: 'none',
|
||||
account: '',
|
||||
password: '',
|
||||
},
|
||||
preconditions: [],
|
||||
setting: {
|
||||
connectTimeout: 60000,
|
||||
responseTimeout: 60000,
|
||||
certificateAlias: '',
|
||||
redirect: 'follow',
|
||||
},
|
||||
const defaultDebugParams = {
|
||||
id: initDefaultId,
|
||||
moduleProtocol: 'http',
|
||||
activeTab: RequestComposition.HEADER,
|
||||
label: t('apiTestDebug.newApi'),
|
||||
closable: true,
|
||||
method: RequestMethods.GET,
|
||||
unSave: false,
|
||||
headerParams: [],
|
||||
bodyParams: cloneDeep(defaultBodyParams),
|
||||
queryParams: [],
|
||||
restParams: [],
|
||||
authParams: {
|
||||
authType: 'none',
|
||||
account: '',
|
||||
password: '',
|
||||
},
|
||||
]);
|
||||
preconditions: [],
|
||||
postConditions: [],
|
||||
setting: {
|
||||
connectTimeout: 60000,
|
||||
responseTimeout: 60000,
|
||||
certificateAlias: '',
|
||||
redirect: 'follow',
|
||||
},
|
||||
response: {
|
||||
status: 200,
|
||||
headers: [],
|
||||
body: `{
|
||||
"type": "team",
|
||||
"test": {
|
||||
"testPage": "tools/testing/run-tests.htm",
|
||||
"enabled": true
|
||||
},
|
||||
"search": {
|
||||
"excludeFolders": [
|
||||
".git",
|
||||
"node_modules",
|
||||
"tools/bin",
|
||||
"tools/counts",
|
||||
"tools/policheck",
|
||||
"tools/tfs_build_extensions",
|
||||
"tools/testing/jscoverage",
|
||||
"tools/testing/qunit",
|
||||
"tools/testing/chutzpah",
|
||||
"server.net"
|
||||
]
|
||||
},
|
||||
"languages": {
|
||||
"vs.languages.typescript": {
|
||||
"validationSettings": [{
|
||||
"scope":"/",
|
||||
"noImplicitAny":true,
|
||||
"noLib":false,
|
||||
"extraLibs":[],
|
||||
"semanticValidation":true,
|
||||
"syntaxValidation":true,
|
||||
"codeGenTarget":"ES5",
|
||||
"moduleGenTarget":"",
|
||||
"lint": {
|
||||
"emptyBlocksWithoutComment": "warning",
|
||||
"curlyBracketsMustNotBeOmitted": "warning",
|
||||
"comparisonOperatorsNotStrict": "warning",
|
||||
"missingSemicolon": "warning",
|
||||
"unknownTypeOfResults": "warning",
|
||||
"semicolonsInsteadOfBlocks": "warning",
|
||||
"functionsInsideLoops": "warning",
|
||||
"functionsWithoutReturnType": "warning",
|
||||
"tripleSlashReferenceAlike": "warning",
|
||||
"unusedImports": "warning",
|
||||
"unusedVariables": "warning",
|
||||
"unusedFunctions": "warning",
|
||||
"unusedMembers": "warning"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope":"/client",
|
||||
"baseUrl":"/client",
|
||||
"moduleGenTarget":"amd"
|
||||
},
|
||||
{
|
||||
"scope":"/server",
|
||||
"moduleGenTarget":"commonjs"
|
||||
},
|
||||
{
|
||||
"scope":"/build",
|
||||
"moduleGenTarget":"commonjs"
|
||||
},
|
||||
{
|
||||
"scope":"/node_modules/nake",
|
||||
"moduleGenTarget":"commonjs"
|
||||
}],
|
||||
"allowMultipleWorkers": true
|
||||
}
|
||||
}
|
||||
}`,
|
||||
}, // 调试返回的响应内容
|
||||
};
|
||||
const debugTabs = ref<TabItem[]>([cloneDeep(defaultDebugParams)]);
|
||||
const debugUrl = ref('');
|
||||
const activeDebug = ref<TabItem>(debugTabs.value[0]);
|
||||
|
||||
|
@ -232,28 +312,8 @@
|
|||
function addDebugTab() {
|
||||
const id = `debug-${Date.now()}`;
|
||||
debugTabs.value.push({
|
||||
...cloneDeep(defaultDebugParams),
|
||||
id,
|
||||
moduleProtocol: 'http',
|
||||
activeTab: RequestComposition.HEADER,
|
||||
label: t('apiTestDebug.newApi'),
|
||||
closable: true,
|
||||
method: RequestMethods.GET,
|
||||
unSave: false,
|
||||
headerParams: [],
|
||||
bodyParams: cloneDeep(defaultBodyParams),
|
||||
queryParams: [],
|
||||
restParams: [],
|
||||
authParams: {
|
||||
authType: 'none',
|
||||
account: '',
|
||||
password: '',
|
||||
},
|
||||
setting: {
|
||||
connectTimeout: 60000,
|
||||
responseTimeout: 60000,
|
||||
certificateAlias: '',
|
||||
redirect: 'follow',
|
||||
},
|
||||
});
|
||||
activeTab.value = id;
|
||||
}
|
||||
|
@ -295,12 +355,12 @@
|
|||
label: RequestComposition.REST,
|
||||
},
|
||||
{
|
||||
value: RequestComposition.PREFIX,
|
||||
value: RequestComposition.PRECONDITION,
|
||||
label: t('apiTestDebug.prefix'),
|
||||
},
|
||||
{
|
||||
value: RequestComposition.POST_CONDITION,
|
||||
label: t('apiTestDebug.postCondition'),
|
||||
label: t('apiTestDebug.post'),
|
||||
},
|
||||
{
|
||||
value: RequestComposition.ASSERTION,
|
||||
|
@ -331,8 +391,14 @@
|
|||
watch(
|
||||
() => splitBoxSize.value,
|
||||
debounce((val) => {
|
||||
// 动画 300ms
|
||||
if (splitContainerRef.value) {
|
||||
secondBoxHeight.value = splitContainerRef.value.clientHeight * (1 - val);
|
||||
if (typeof val === 'string' && val.includes('px')) {
|
||||
val = Number(val.split('px')[0]);
|
||||
secondBoxHeight.value = splitContainerRef.value.clientHeight - val;
|
||||
} else {
|
||||
secondBoxHeight.value = splitContainerRef.value.clientHeight * (1 - val);
|
||||
}
|
||||
}
|
||||
}, 300),
|
||||
{
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<condition
|
||||
v-model:list="postConditions"
|
||||
:condition-types="['script', 'sql', 'waitTime', 'extract']"
|
||||
add-text="apiTestDebug.postCondition"
|
||||
:response="props.response"
|
||||
:height-used="heightUsed"
|
||||
@change="emit('change')"
|
||||
>
|
||||
<template #titleRight>
|
||||
<a-switch v-model:model-value="openGlobalPostCondition" size="small" type="line"></a-switch>
|
||||
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.openGlobalPostCondition') }}</div>
|
||||
<a-tooltip :content="t('apiTestDebug.openGlobalPostConditionTip')" position="left">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</condition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import condition from '../../../components/condition/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const props = defineProps<{
|
||||
params: any[];
|
||||
secondBoxHeight?: number;
|
||||
layout: 'horizontal' | 'vertical';
|
||||
response?: string; // 响应内容
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:params', params: any[]): void;
|
||||
(e: 'change'): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
// 是否开启全局后置条件
|
||||
const openGlobalPostCondition = ref(false);
|
||||
const postConditions = useVModel(props, 'params', emit);
|
||||
const heightUsed = computed(() => {
|
||||
if (props.layout === 'horizontal') {
|
||||
return 422;
|
||||
}
|
||||
return 422 + (props.secondBoxHeight || 0);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -1,320 +1,32 @@
|
|||
<template>
|
||||
<div class="mb-[8px] flex items-center justify-between">
|
||||
<a-dropdown @select="addPrecondition">
|
||||
<a-button type="outline">
|
||||
<template #icon>
|
||||
<icon-plus :size="14" />
|
||||
</template>
|
||||
{{ t('apiTestDebug.precondition') }}
|
||||
</a-button>
|
||||
<template #content>
|
||||
<a-doption value="script">{{ t('apiTestDebug.script') }}</a-doption>
|
||||
<a-doption value="sql">{{ t('apiTestDebug.sql') }}</a-doption>
|
||||
<a-doption value="waitTime">{{ t('apiTestDebug.waitTime') }}</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<div class="flex items-center">
|
||||
<condition
|
||||
v-model:list="preconditions"
|
||||
:condition-types="['script', 'sql', 'waitTime']"
|
||||
add-text="apiTestDebug.precondition"
|
||||
@change="emit('change')"
|
||||
>
|
||||
<template #titleRight>
|
||||
<a-switch v-model:model-value="openGlobalPrecondition" size="small" type="line"></a-switch>
|
||||
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.openGlobalPrecondition') }}</div>
|
||||
<a-tooltip :content="t('apiTestDebug.openGlobalPreconditionTip')" position="top">
|
||||
<a-tooltip :content="t('apiTestDebug.openGlobalPreconditionTip')" position="left">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="preconditions.length > 0" class="flex h-[calc(100%-110px)] gap-[8px]">
|
||||
<div class="h-full w-[20%] min-w-[220px]">
|
||||
<MsList
|
||||
v-model:active-item-key="activeItem.id"
|
||||
v-model:focus-item-key="focusItemKey"
|
||||
v-model:data="preconditions"
|
||||
mode="static"
|
||||
item-key-field="id"
|
||||
:item-border="false"
|
||||
class="h-full rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[12px]"
|
||||
item-class="mb-[4px] bg-white !p-[4px_8px]"
|
||||
:item-more-actions="itemMoreActions"
|
||||
active-item-class="!bg-[rgb(var(--primary-1))] text-[rgb(var(--primary-5))]"
|
||||
:virtual-list-props="{ height: '100%', fixedSize: true }"
|
||||
draggable
|
||||
@item-click="handlePreconditionItemClick"
|
||||
@more-action-select="handlePreconditionMoreActionSelect"
|
||||
@more-actions-close="focusItemKey = ''"
|
||||
>
|
||||
<template #title="{ item, index }">
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<div
|
||||
:class="`flex h-[16px] w-[16px] items-center justify-center rounded-full ${
|
||||
activeItem.id === item.id ? ' bg-white' : 'bg-[var(--color-text-n8)]'
|
||||
}`"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
<div>{{ typeMap[item.type] }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #itemRight="{ item }">
|
||||
<a-switch v-model:model-value="item.enable" size="small" type="line"></a-switch>
|
||||
</template>
|
||||
</MsList>
|
||||
</div>
|
||||
<div class="precondition-content">
|
||||
<!-- 前置条件-脚本操作 -->
|
||||
<template v-if="activeItem.type === 'script'">
|
||||
<a-radio-group v-model:model-value="activeItem.scriptType" size="small" class="mb-[16px]">
|
||||
<a-radio value="manual">{{ t('apiTestDebug.manual') }}</a-radio>
|
||||
<a-radio value="quote">{{ t('apiTestDebug.quote') }}</a-radio>
|
||||
</a-radio-group>
|
||||
<div
|
||||
v-if="activeItem.scriptType === 'manual'"
|
||||
class="relative rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[12px]"
|
||||
>
|
||||
<div v-if="isShowEditScriptNameInput" class="absolute left-[12px] z-10 w-[calc(100%-24px)]">
|
||||
<a-input
|
||||
ref="scriptNameInputRef"
|
||||
v-model:model-value="activeItem.name"
|
||||
:placeholder="t('apiTestDebug.preconditionScriptNamePlaceholder')"
|
||||
:max-length="255"
|
||||
show-word-limit
|
||||
size="small"
|
||||
@press-enter="isShowEditScriptNameInput = false"
|
||||
@blur="isShowEditScriptNameInput = false"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<a-tooltip :content="activeItem.name">
|
||||
<div class="script-name-container">
|
||||
<div class="one-line-text mr-[4px] max-w-[110px] font-medium text-[var(--color-text-1)]">
|
||||
{{ activeItem.name }}
|
||||
</div>
|
||||
<MsIcon
|
||||
type="icon-icon_edit_outlined"
|
||||
class="edit-script-name-icon"
|
||||
@click="showEditScriptNameInput"
|
||||
/>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<a-popover class="h-auto" position="top">
|
||||
<div class="text-[rgb(var(--primary-5))]">{{ t('apiTestDebug.scriptEx') }}</div>
|
||||
<template #content>
|
||||
<div class="mb-[8px] flex items-center justify-between">
|
||||
<div class="text-[14px] font-medium text-[var(--color-text-1)]">
|
||||
{{ t('apiTestDebug.scriptEx') }}
|
||||
</div>
|
||||
<a-button
|
||||
type="outline"
|
||||
class="arco-btn-outline--secondary p-[0_8px]"
|
||||
size="mini"
|
||||
@click="copyScriptEx"
|
||||
>
|
||||
{{ t('common.copy') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="flex h-[412px]">
|
||||
<MsCodeEditor
|
||||
v-model:model-value="scriptEx"
|
||||
class="flex-1"
|
||||
theme="MS-text"
|
||||
width="500px"
|
||||
height="388px"
|
||||
:show-full-screen="false"
|
||||
:show-theme-change="false"
|
||||
read-only
|
||||
>
|
||||
</MsCodeEditor>
|
||||
</div>
|
||||
</template>
|
||||
</a-popover>
|
||||
</div>
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini">
|
||||
<template #icon>
|
||||
<MsIcon type="icon-icon_undo_outlined" class="text-var(--color-text-4)" size="12" />
|
||||
</template>
|
||||
{{ t('common.revoke') }}
|
||||
</a-button>
|
||||
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini" @click="clearScript">
|
||||
<template #icon>
|
||||
<MsIcon type="icon-icon_clear" class="text-var(--color-text-4)" size="12" />
|
||||
</template>
|
||||
{{ t('common.clear') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="outline"
|
||||
class="arco-btn-outline--secondary p-[0_8px]"
|
||||
size="mini"
|
||||
@click="() => copyPrecondition(activeItem)"
|
||||
>
|
||||
{{ t('common.copy') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="outline"
|
||||
class="arco-btn-outline--secondary p-[0_8px]"
|
||||
size="mini"
|
||||
@click="() => deletePrecondition(activeItem)"
|
||||
>
|
||||
{{ t('common.delete') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex h-[calc(100%-47px)] flex-col">
|
||||
<div class="mb-[16px] flex w-full items-center bg-[var(--color-text-n9)] p-[12px]">
|
||||
<div class="text-[var(--color-text-2)]">
|
||||
{{ activeItem.quoteScript.name || '-' }}
|
||||
</div>
|
||||
<a-divider margin="8px" direction="vertical" />
|
||||
<MsButton type="text" class="font-medium">
|
||||
{{ t('apiTestDebug.quote') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
<a-radio-group v-model:model-value="commonScriptShowType" size="small" type="button" class="mb-[8px] w-fit">
|
||||
<a-radio value="parameters">{{ t('apiTestDebug.parameters') }}</a-radio>
|
||||
<a-radio value="scriptContent">{{ t('apiTestDebug.scriptContent') }}</a-radio>
|
||||
</a-radio-group>
|
||||
<MsBaseTable v-show="commonScriptShowType === 'parameters'" v-bind="propsRes" v-on="propsEvent">
|
||||
<template #value="{ record }">
|
||||
<a-tooltip :content="t(record.required ? 'apiTestDebug.paramRequired' : 'apiTestDebug.paramNotRequired')">
|
||||
<div
|
||||
:class="[
|
||||
record.required ? '!text-[rgb(var(--danger-5))]' : '!text-[var(--color-text-brand)]',
|
||||
'!mr-[4px] !p-[4px]',
|
||||
]"
|
||||
>
|
||||
<div>*</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
{{ record.type }}
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
<div v-show="commonScriptShowType === 'scriptContent'" class="h-[calc(100%-76px)]">
|
||||
<MsCodeEditor
|
||||
v-model:model-value="activeItem.quoteScript.script"
|
||||
theme="MS-text"
|
||||
height="100%"
|
||||
:show-full-screen="false"
|
||||
:show-theme-change="false"
|
||||
read-only
|
||||
>
|
||||
</MsCodeEditor>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 前置条件-SQL操作 -->
|
||||
<template v-else-if="activeItem.type === 'sql'">
|
||||
<div class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('common.desc') }}</div>
|
||||
<a-input
|
||||
v-model:model-value="activeItem.desc"
|
||||
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
||||
:max-length="255"
|
||||
show-word-limit
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-[16px] flex w-full items-center bg-[var(--color-text-n9)] p-[12px]">
|
||||
<div class="text-[var(--color-text-2)]">
|
||||
{{ activeItem.sqlSource.name || '-' }}
|
||||
</div>
|
||||
<a-divider margin="8px" direction="vertical" />
|
||||
<MsButton type="text" class="font-medium">
|
||||
{{ t('apiTestDebug.quoteSource') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.sqlScript') }}</div>
|
||||
<div class="mb-[16px] h-[400px]">
|
||||
<MsCodeEditor
|
||||
v-model:model-value="activeItem.sqlSource.script"
|
||||
theme="MS-text"
|
||||
height="376px"
|
||||
:show-full-screen="false"
|
||||
:show-theme-change="false"
|
||||
read-only
|
||||
>
|
||||
</MsCodeEditor>
|
||||
</div>
|
||||
<div class="mb-[16px]">
|
||||
<div class="mb-[8px] flex items-center text-[var(--color-text-1)]">
|
||||
{{ t('apiTestDebug.storageType') }}
|
||||
<a-tooltip position="right">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
<template #content>
|
||||
<div>{{ t('apiTestDebug.storageTypeTip1') }}</div>
|
||||
<div>{{ t('apiTestDebug.storageTypeTip2') }}</div>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-radio-group
|
||||
v-model:model-value="activeItem.sqlSource.storageType"
|
||||
size="small"
|
||||
type="button"
|
||||
class="w-fit"
|
||||
>
|
||||
<a-radio value="column">{{ t('apiTestDebug.storageByCol') }}</a-radio>
|
||||
<a-radio value="result">{{ t('apiTestDebug.storageByResult') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div v-if="activeItem.sqlSource.storageType === 'column'" class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.storageByCol') }}</div>
|
||||
<a-input
|
||||
v-model:model-value="activeItem.sqlSource.storageByCol"
|
||||
:placeholder="t('apiTestDebug.storageByColPlaceholder', { a: '{id_1}', b: '{username_1}' })"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.storageByResult') }}</div>
|
||||
<a-input
|
||||
v-model:model-value="activeItem.sqlSource.storageByResult"
|
||||
:placeholder="t('apiTestDebug.storageByResultPlaceholder', { a: '${result}' })"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-[16px]">
|
||||
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.extractParameter') }}</div>
|
||||
<paramTable
|
||||
v-model:params="activeItem.sqlSource.params"
|
||||
:columns="sqlSourceColumns"
|
||||
:selectable="false"
|
||||
@change="handleParamTableChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 前置条件-等待时间 -->
|
||||
<div v-else>
|
||||
<div class="mb-[8px] flex items-center">
|
||||
{{ t('apiTestDebug.waitTime') }}
|
||||
<div class="text-[var(--color-text-4)]">(ms)</div>
|
||||
</div>
|
||||
<a-input-number v-model:model-value="activeItem.time" mode="button" :step="100" :min="0" class="w-[160px]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</condition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useClipboard, useVModel } from '@vueuse/core';
|
||||
import { InputInstance, Message } from '@arco-design/web-vue';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsList from '@/components/pure/ms-list/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import paramTable, { type ParamTableColumn } from '../../../components/paramTable.vue';
|
||||
import condition from '../../../components/condition/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const props = defineProps<{
|
||||
params: any[];
|
||||
layout: 'horizontal' | 'vertical';
|
||||
secondBoxHeight: number;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:params', params: any[]): void;
|
||||
|
@ -325,273 +37,6 @@
|
|||
// 是否开启全局前置条件
|
||||
const openGlobalPrecondition = ref(false);
|
||||
const preconditions = useVModel(props, 'params', emit);
|
||||
// 当前聚焦的前置条件
|
||||
const focusItemKey = ref<any>('');
|
||||
// 当前选中的前置条件
|
||||
const activeItem = ref(preconditions.value[0] || {});
|
||||
const typeMap = {
|
||||
script: t('apiTestDebug.script'),
|
||||
sql: t('apiTestDebug.sql'),
|
||||
waitTime: t('apiTestDebug.waitTime'),
|
||||
};
|
||||
const itemMoreActions: ActionsItem[] = [
|
||||
{
|
||||
label: 'common.copy',
|
||||
eventTag: 'copy',
|
||||
},
|
||||
{
|
||||
label: 'project.fileManagement.delete',
|
||||
eventTag: 'delete',
|
||||
},
|
||||
];
|
||||
const scriptEx = ref(`2023-12-04 11:19:28 INFO 9026fd6a 1-1 Thread started: 9026fd6a 1-1
|
||||
2023-12-04 11:19:28 ERROR 9026fd6a 1-1 Problem in JSR223 script JSR223Sampler, message: {}
|
||||
In file: inline evaluation of: prev.getResponseCode() import java.net.URI; import org.apache.http.client.method . . . '' Encountered "import" at line 2, column 1.
|
||||
in inline evaluation of: prev.getResponseCode() import java.net.URI; import org.apache.http.client.method . . . '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException '' at line number 2
|
||||
javax.script.ScriptException
|
||||
org.apache.http.client.method . . . '' at line number 2
|
||||
`);
|
||||
const { copy, isSupported } = useClipboard();
|
||||
|
||||
function copyScriptEx() {
|
||||
if (isSupported) {
|
||||
copy(scriptEx.value);
|
||||
Message.success(t('apiTestDebug.scriptExCopySuccess'));
|
||||
} else {
|
||||
Message.warning(t('apiTestDebug.copyNotSupport'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加前置条件
|
||||
* @param value script | sql | waitTime
|
||||
*/
|
||||
function addPrecondition(value: string | number | Record<string, any> | undefined) {
|
||||
const id = new Date().getTime();
|
||||
switch (value) {
|
||||
case 'script':
|
||||
preconditions.value.push({
|
||||
id,
|
||||
type: 'script',
|
||||
name: t('apiTestDebug.preconditionScriptName'),
|
||||
scriptType: 'manual',
|
||||
enable: true,
|
||||
script: '',
|
||||
quoteScript: {
|
||||
name: '',
|
||||
script: scriptEx,
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'sql':
|
||||
preconditions.value.push({
|
||||
id,
|
||||
type: 'sql',
|
||||
desc: '',
|
||||
enable: true,
|
||||
sqlSource: {
|
||||
name: '',
|
||||
script: scriptEx,
|
||||
storageType: 'column',
|
||||
params: [],
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'waitTime':
|
||||
preconditions.value.push({
|
||||
id,
|
||||
type: 'waitTime',
|
||||
enable: true,
|
||||
time: 1000,
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
activeItem.value = preconditions.value[preconditions.value.length - 1];
|
||||
emit('change');
|
||||
}
|
||||
|
||||
function handlePreconditionItemClick(item: any) {
|
||||
activeItem.value = item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制前置条件
|
||||
* @param item 前置条件项
|
||||
*/
|
||||
function copyPrecondition(item: Record<string, any>) {
|
||||
const copyItem = {
|
||||
...item,
|
||||
id: new Date().getTime(),
|
||||
};
|
||||
preconditions.value.push(copyItem);
|
||||
activeItem.value = copyItem;
|
||||
emit('change');
|
||||
}
|
||||
|
||||
function deletePrecondition(item: Record<string, any>) {
|
||||
preconditions.value = preconditions.value.filter((precondition) => precondition.id !== item.id);
|
||||
if (activeItem.value.id === item.id) {
|
||||
[activeItem.value] = preconditions.value;
|
||||
}
|
||||
emit('change');
|
||||
}
|
||||
|
||||
/**
|
||||
* 前置条件列表项-选择更多操作项
|
||||
* @param event
|
||||
* @param item
|
||||
*/
|
||||
function handlePreconditionMoreActionSelect(event: ActionsItem, item: Record<string, any>) {
|
||||
if (event.eventTag === 'copy') {
|
||||
copyPrecondition(item);
|
||||
} else if (event.eventTag === 'delete') {
|
||||
deletePrecondition(item);
|
||||
}
|
||||
}
|
||||
|
||||
function clearScript() {
|
||||
activeItem.value.script = '';
|
||||
}
|
||||
|
||||
// 是否显示前置脚本名称编辑框
|
||||
const isShowEditScriptNameInput = ref(false);
|
||||
const scriptNameInputRef = ref<InputInstance>();
|
||||
|
||||
function showEditScriptNameInput() {
|
||||
isShowEditScriptNameInput.value = true;
|
||||
nextTick(() => {
|
||||
scriptNameInputRef.value?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
const commonScriptShowType = ref<'parameters' | 'scriptContent'>('parameters');
|
||||
const heightUsed = ref<number | undefined>(undefined);
|
||||
const scroll = computed(() => (props.layout === 'horizontal' ? { x: '700px' } : { x: '100%' }));
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'name',
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramValue',
|
||||
dataIndex: 'value',
|
||||
slotName: 'value',
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.desc',
|
||||
dataIndex: 'desc',
|
||||
showTooltip: true,
|
||||
},
|
||||
];
|
||||
const { propsRes, propsEvent } = useTable(() => Promise.resolve([]), {
|
||||
scroll: scroll.value,
|
||||
heightUsed: heightUsed.value,
|
||||
columns,
|
||||
});
|
||||
propsRes.value.data = [
|
||||
{
|
||||
id: new Date().getTime(),
|
||||
required: false,
|
||||
name: 'asdasd',
|
||||
type: 'string',
|
||||
value: '',
|
||||
desc: '',
|
||||
},
|
||||
{
|
||||
id: new Date().getTime(),
|
||||
required: true,
|
||||
name: '23d23d',
|
||||
type: 'string',
|
||||
value: '',
|
||||
desc: '',
|
||||
},
|
||||
] as any;
|
||||
watch(
|
||||
() => props.layout,
|
||||
(val) => {
|
||||
heightUsed.value = val === 'horizontal' ? 422 : 422 + props.secondBoxHeight;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.secondBoxHeight,
|
||||
(val) => {
|
||||
if (props.layout === 'vertical') {
|
||||
heightUsed.value = 422 + val;
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
const sqlSourceColumns: ParamTableColumn[] = [
|
||||
{
|
||||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'name',
|
||||
slotName: 'name',
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramValue',
|
||||
dataIndex: 'value',
|
||||
slotName: 'value',
|
||||
isNormal: true,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
slotName: 'operation',
|
||||
width: 50,
|
||||
},
|
||||
];
|
||||
|
||||
function handleParamTableChange(resultArr: any[], isInit?: boolean) {
|
||||
activeItem.value.sqlSource.params = [...resultArr];
|
||||
if (!isInit) {
|
||||
emit('change');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.precondition-content {
|
||||
@apply flex-1 overflow-y-auto;
|
||||
.ms-scroll-bar();
|
||||
|
||||
padding: 16px;
|
||||
border: 1px solid rgb(var(--color-text-n8));
|
||||
border-radius: var(--border-radius-small);
|
||||
}
|
||||
.script-name-container {
|
||||
@apply flex items-center;
|
||||
|
||||
margin-right: 16px;
|
||||
&:hover {
|
||||
.edit-script-name-icon {
|
||||
@apply visible;
|
||||
}
|
||||
}
|
||||
.edit-script-name-icon {
|
||||
@apply invisible cursor-pointer;
|
||||
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
}
|
||||
:deep(.arco-table-th) {
|
||||
background-color: var(--color-text-n9);
|
||||
}
|
||||
:deep(.arco-table-cell) {
|
||||
padding: 16px 12px;
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped></style>
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-[8px] flex items-center gap-[8px]">
|
||||
<a-input
|
||||
v-model:model-value="moduleKeyword"
|
||||
:placeholder="t('caseManagement.caseReview.folderSearchPlaceholder')"
|
||||
allow-clear
|
||||
/>
|
||||
<a-input v-model:model-value="moduleKeyword" :placeholder="t('apiTestDebug.searchTip')" allow-clear />
|
||||
<a-dropdown @select="handleSelect">
|
||||
<a-button type="primary">{{ t('apiTestDebug.newApi') }}</a-button>
|
||||
<template #content>
|
||||
|
@ -17,7 +13,7 @@
|
|||
<div v-if="!props.isModal" class="folder">
|
||||
<div class="folder-text">
|
||||
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
|
||||
<div class="folder-name">{{ t('caseManagement.caseReview.allReviews') }}</div>
|
||||
<div class="folder-name">{{ t('apiTestDebug.allRequest') }}</div>
|
||||
<div class="folder-count">({{ allFileCount }})</div>
|
||||
</div>
|
||||
<div class="ml-auto flex items-center">
|
||||
|
@ -224,9 +220,9 @@
|
|||
function deleteFolder(node: MsTreeNodeData) {
|
||||
openModal({
|
||||
type: 'error',
|
||||
title: t('caseManagement.caseReview.deleteFolderTipTitle', { name: node.name }),
|
||||
content: t('caseManagement.caseReview.deleteFolderTipContent'),
|
||||
okText: t('caseManagement.caseReview.deleteConfirm'),
|
||||
title: t('apiTestDebug.deleteFolderTipTitle', { name: node.name }),
|
||||
content: t('apiTestDebug.deleteFolderTipContent'),
|
||||
okText: t('apiTestDebug.deleteConfirm'),
|
||||
okButtonProps: {
|
||||
status: 'danger',
|
||||
},
|
||||
|
@ -234,7 +230,7 @@
|
|||
onBeforeOk: async () => {
|
||||
try {
|
||||
await deleteReviewModule(node.id);
|
||||
Message.success(t('caseManagement.caseReview.deleteSuccess'));
|
||||
Message.success(t('apiTestDebug.deleteSuccess'));
|
||||
initModules();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -293,7 +289,7 @@
|
|||
dropNodeId: dropNode.id || '',
|
||||
dropPosition,
|
||||
});
|
||||
Message.success(t('caseManagement.caseReview.moduleMoveSuccess'));
|
||||
Message.success(t('apiTestDebug.moduleMoveSuccess'));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
|
|
|
@ -8,7 +8,7 @@ export default {
|
|||
'apiTestDebug.header': 'Header',
|
||||
'apiTestDebug.body': 'Body',
|
||||
'apiTestDebug.prefix': 'Prefix',
|
||||
'apiTestDebug.postCondition': 'Post condition',
|
||||
'apiTestDebug.post': 'Post',
|
||||
'apiTestDebug.assertion': 'Assertion',
|
||||
'apiTestDebug.auth': 'Auth',
|
||||
'apiTestDebug.setting': 'Setting',
|
||||
|
@ -24,8 +24,9 @@ export default {
|
|||
'apiTestDebug.desc': 'Description',
|
||||
'apiTestDebug.apply': 'Apply',
|
||||
'apiTestDebug.batchAddParamsTip': 'Writing format: parameter name: parameter value; such as nama: natural',
|
||||
'apiTestDebug.batchAddParamsTip2':
|
||||
'Note: Multiple records are separated by newlines. Parameter names in batch addition are repeated. By default, the last data is the latest data.',
|
||||
'apiTestDebug.batchAddParamsTip2': 'Multiple records are separated by newlines.',
|
||||
'apiTestDebug.batchAddParamsTip3':
|
||||
'Parameter names in batch addition are repeated. By default, the last data is the latest data.',
|
||||
'apiTestDebug.quickInputParamsTip': 'Support Mock/JMeter/Json/Text/String, etc.',
|
||||
'apiTestDebug.descPlaceholder': 'Please enter content',
|
||||
};
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue