feat(接口测试): 接口测试-请求头&参数值输入组件&可增减 tab
This commit is contained in:
parent
39a1d0d910
commit
7f2b0ca14a
|
@ -37,7 +37,7 @@
|
|||
"dependencies": {
|
||||
"@7polo/kity": "2.0.8",
|
||||
"@7polo/kityminder-core": "1.4.53",
|
||||
"@arco-design/web-vue": "^2.52.1",
|
||||
"@arco-design/web-vue": "^2.53.3",
|
||||
"@arco-themes/vue-ms-theme-default": "^0.0.30",
|
||||
"@form-create/arco-design": "^3.1.23",
|
||||
"@halo-dev/richtext-editor": "0.0.0-alpha.32",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@font-face {
|
||||
font-family: iconfont; /* Project id 3462279 */
|
||||
src: url('iconfont.woff2?t=1700812414583') format('woff2'), url('iconfont.woff?t=1700812414583') format('woff'),
|
||||
url('iconfont.ttf?t=1700812414583') format('truetype'), url('iconfont.svg?t=1700812414583#iconfont') format('svg');
|
||||
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');
|
||||
}
|
||||
.iconfont {
|
||||
font-size: 16px;
|
||||
|
@ -10,6 +10,27 @@
|
|||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.icon-icon_keyboard::before {
|
||||
content: '\e796';
|
||||
}
|
||||
.icon-icon_split-turn-down-left::before {
|
||||
content: '\e795';
|
||||
}
|
||||
.icon-icon_checkbox1::before {
|
||||
content: '\e794';
|
||||
}
|
||||
.icon-icon_flashlamp::before {
|
||||
content: '\e793';
|
||||
}
|
||||
.icon-icon_clear::before {
|
||||
content: '\e792';
|
||||
}
|
||||
.icon-icon_expand-text-input::before {
|
||||
content: '\e791';
|
||||
}
|
||||
.icon-icon_mock::before {
|
||||
content: '\e790';
|
||||
}
|
||||
.icon-icon_text-wrap-overflow::before {
|
||||
content: '\e78f';
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,6 +5,55 @@
|
|||
"css_prefix_text": "icon-",
|
||||
"description": "DE、MS项目icon管理",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "38499852",
|
||||
"name": "icon_keyboard",
|
||||
"font_class": "icon_keyboard",
|
||||
"unicode": "e796",
|
||||
"unicode_decimal": 59286
|
||||
},
|
||||
{
|
||||
"icon_id": "38496991",
|
||||
"name": "icon_split-turn-down-left",
|
||||
"font_class": "icon_split-turn-down-left",
|
||||
"unicode": "e795",
|
||||
"unicode_decimal": 59285
|
||||
},
|
||||
{
|
||||
"icon_id": "38462218",
|
||||
"name": "icon_checkbox",
|
||||
"font_class": "icon_checkbox1",
|
||||
"unicode": "e794",
|
||||
"unicode_decimal": 59284
|
||||
},
|
||||
{
|
||||
"icon_id": "38454074",
|
||||
"name": "icon_flashlamp",
|
||||
"font_class": "icon_flashlamp",
|
||||
"unicode": "e793",
|
||||
"unicode_decimal": 59283
|
||||
},
|
||||
{
|
||||
"icon_id": "38353463",
|
||||
"name": "icon_clear",
|
||||
"font_class": "icon_clear",
|
||||
"unicode": "e792",
|
||||
"unicode_decimal": 59282
|
||||
},
|
||||
{
|
||||
"icon_id": "38293413",
|
||||
"name": "icon_expand-text-input",
|
||||
"font_class": "icon_expand-text-input",
|
||||
"unicode": "e791",
|
||||
"unicode_decimal": 59281
|
||||
},
|
||||
{
|
||||
"icon_id": "38293039",
|
||||
"name": "icon_mock",
|
||||
"font_class": "icon_mock",
|
||||
"unicode": "e790",
|
||||
"unicode_decimal": 59280
|
||||
},
|
||||
{
|
||||
"icon_id": "38195845",
|
||||
"name": "icon_text-wrap-overflow",
|
||||
|
|
|
@ -14,6 +14,20 @@
|
|||
/>
|
||||
<missing-glyph />
|
||||
|
||||
<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" />
|
||||
|
||||
<glyph glyph-name="icon_checkbox1" unicode="" d="M832 810.666667A106.666667 106.666667 0 0 0 938.666667 704v-640a106.666667 106.666667 0 0 0-106.666667-106.666667h-640A106.666667 106.666667 0 0 0 85.333333 64v640A106.666667 106.666667 0 0 0 192 810.666667h640z m0-85.333334h-640a21.333333 21.333333 0 0 1-21.333333-21.333333v-640a21.333333 21.333333 0 0 1 21.333333-21.333333h640a21.333333 21.333333 0 0 1 21.333333 21.333333v640a21.333333 21.333333 0 0 1-21.333333 21.333333zM682.666667 597.333333a42.666667 42.666667 0 0 0 42.666666-42.666666v-341.333334a42.666667 42.666667 0 0 0-42.666666-42.666666H341.333333a42.666667 42.666667 0 0 0-42.666666 42.666666V554.666667a42.666667 42.666667 0 0 0 42.666666 42.666666h341.333334z m-42.666667-85.333333H384v-256h256V512z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="icon_flashlamp" unicode="" d="M512 853.333333c259.2 0 469.333333-210.133333 469.333333-469.333333s-210.133333-469.333333-469.333333-469.333333S42.666667 124.8 42.666667 384 252.8 853.333333 512 853.333333z m0-85.333333a384 384 0 1 1 0-768 384 384 0 0 1 0 768z m-2.261333-132.522667a42.666667 42.666667 0 0 0 19.072-57.216L453.034667 426.666667H640a42.666667 42.666667 0 0 0 40.106667-57.216l-1.962667-4.522667-106.666667-213.333333a42.666667 42.666667 0 0 0-76.288 38.144L570.88 341.333333H384a42.666667 42.666667 0 0 0-40.106667 57.216l1.962667 4.522667 106.666667 213.333333a42.666667 42.666667 0 0 0 57.216 19.072z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="icon_clear" unicode="" d="M713.6 751.018667l274.432-364.202667a42.666667 42.666667 0 0 0-8.405333-59.733333l-298.410667-224.853334-54.101333-37.632h322.218666a42.666667 42.666667 0 0 0 0-85.333333l-445.312-0.085333H278.016a42.666667 42.666667 0 0 0-34.048 17.024l-55.466667 73.6L51.2 251.904a42.666667 42.666667 0 0 0 8.405333 59.733333l214.4 161.578667 0.426667 0.298667 379.349333 285.866666a42.666667 42.666667 0 0 0 59.733334-8.362666z m-420.693333-370.432L145.066667 269.226667l111.573333-148.053334 42.624-56.618666 189.141333 0.042666 36.693334 29.354667-232.192 286.634667z m378.197333 285.013333l-309.973333-233.557333 232.32-286.890667 37.717333 26.197333 262.997333 198.229334L671.104 665.6z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="icon_expand-text-input" unicode="" d="M128 384a42.666667 42.666667 0 0 0 42.666667-42.666667v-298.666666h298.666666a42.666667 42.666667 0 0 0 42.368-37.674667L512 0a42.666667 42.666667 0 0 0-42.666667-42.666667H128a42.666667 42.666667 0 0 0-42.666667 42.666667v341.333333a42.666667 42.666667 0 0 0 42.666667 42.666667zM896 810.666667a42.666667 42.666667 0 0 0 42.666667-42.666667v-341.333333a42.666667 42.666667 0 0 0-85.333334 0V725.333333h-298.666666a42.666667 42.666667 0 0 0-42.368 37.674667L512 768a42.666667 42.666667 0 0 0 42.666667 42.666667h341.333333z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="icon_mock" unicode="" d="M789.333333 341.333333a192 192 0 1 0 0-384 192 192 0 0 0 0 384zM896 810.666667a85.333333 85.333333 0 0 0 85.333333-85.333334v-441.045333a235.733333 235.733333 0 0 1-85.333333 74.112V725.333333H128v-640h435.498667c8.96-31.701333 24.405333-60.629333 44.8-85.333333H128a85.333333 85.333333 0 0 0-85.333333 85.333333V725.333333a85.333333 85.333333 0 0 0 85.333333 85.333334h768z m-169.856-554.666667H691.2a8.533333 8.533333 0 0 1-8.533333-8.533333v-196.266667c0-4.693333 3.84-8.533333 8.533333-8.533333h24.448a8.533333 8.533333 0 0 1 8.533333 8.533333v141.056l57.301334-127.914667a8.533333 8.533333 0 0 1 15.616 0l56.576 127.914667V51.2c0-4.693333 3.84-8.533333 8.533333-8.533333h25.258667a8.533333 8.533333 0 0 1 8.533333 8.533333v196.266667a8.533333 8.533333 0 0 1-8.533333 8.533333h-34.944a8.533333 8.533333 0 0 1-7.808-5.077333L789.333333 126.592l-55.381333 124.330667a8.533333 8.533333 0 0 1-7.808 5.077333zM512 640a42.666667 42.666667 0 0 0 42.666667-42.666667v-384a42.666667 42.666667 0 0 0-85.333334 0V597.333333a42.666667 42.666667 0 0 0 42.666667 42.666667zM341.333333 554.666667a42.666667 42.666667 0 0 0 42.666667-42.666667v-298.666667a42.666667 42.666667 0 0 0-85.333333 0V512a42.666667 42.666667 0 0 0 42.666666 42.666667z m341.333334-85.333334a42.666667 42.666667 0 0 0 42.666666-42.666666v-51.498667a234.026667 234.026667 0 0 1-85.333333-44.8V426.666667a42.666667 42.666667 0 0 0 42.666667 42.666666z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="icon_text-wrap-overflow" unicode="" d="M746.666667 488.32a234.666667 234.666667 0 0 0 0-469.333333h-78.208v-52.181334a26.069333 26.069333 0 0 0-41.728-20.821333l-111.232 83.413333a52.138667 52.138667 0 0 0 0 83.456l111.232 83.413334a26.069333 26.069333 0 0 0 41.728-20.864v-52.138667H746.666667a130.389333 130.389333 0 1 1 0 260.736H94.805333a52.138667 52.138667 0 0 0 0 104.32H746.666667zM303.402667 123.264a52.138667 52.138667 0 0 0 0-104.277333H94.805333a52.138667 52.138667 0 0 0 0 104.277333h208.64zM94.805333 853.333333h834.389334a52.138667 52.138667 0 0 0 0-104.277333H94.805333a52.138667 52.138667 0 1 0 0 104.277333z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="icon_template_filled" unicode="" d="M728.96 853.333333H170.666667a42.666667 42.666667 0 0 1-42.666667-42.666666v-853.333334a42.666667 42.666667 0 0 1 42.666667-42.666666h682.666666a42.666667 42.666667 0 0 1 42.666667 42.666666V686.250667a42.666667 42.666667 0 0 1-12.501333 30.165333l-124.330667 124.416A42.666667 42.666667 0 0 1 729.002667 853.333333zM298.666667 533.333333a21.333333 21.333333 0 0 0 21.333333 21.333334h384a21.333333 21.333333 0 0 0 21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 0-21.333333-21.333334h-384a21.333333 21.333333 0 0 0-21.333333 21.333334v42.666666z m170.666666-170.666666a21.333333 21.333333 0 0 0 21.333334 21.333333h213.333333a21.333333 21.333333 0 0 0 21.333333-21.333333v-213.333334a21.333333 21.333333 0 0 0-21.333333-21.333333h-213.333333a21.333333 21.333333 0 0 0-21.333334 21.333333v213.333334z m-170.666666 0a21.333333 21.333333 0 0 0 21.333333 21.333333h42.666667a21.333333 21.333333 0 0 0 21.333333-21.333333v-213.333334a21.333333 21.333333 0 0 0-21.333333-21.333333h-42.666667a21.333333 21.333333 0 0 0-21.333333 21.333333v213.333334z" horiz-adv-x="1024" />
|
||||
|
|
Before Width: | Height: | Size: 413 KiB After Width: | Height: | Size: 420 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -34,6 +34,12 @@
|
|||
.arco-tabs-tab {
|
||||
padding: 13px 0 !important;
|
||||
}
|
||||
.arco-tabs-nav-button-left {
|
||||
@apply ml-0 !shadow-none;
|
||||
}
|
||||
.arco-tabs-nav-button-right {
|
||||
@apply mr-0 !shadow-none;
|
||||
}
|
||||
|
||||
/** Modal对话框 **/
|
||||
.arco-modal {
|
||||
|
|
|
@ -203,7 +203,7 @@
|
|||
}
|
||||
|
||||
/** 容器内部上下阴影类 **/
|
||||
.ms-container--shadow() {
|
||||
.ms-container--shadow-y() {
|
||||
@apply relative;
|
||||
&::before {
|
||||
position: absolute;
|
||||
|
@ -234,3 +234,39 @@
|
|||
box-shadow: inset 0 -10px 6px -10px rgb(0 0 0 / 15%);
|
||||
}
|
||||
}
|
||||
|
||||
/** 输入框组合-选择器后缀样式类 **/
|
||||
.ms-input-group--append() {
|
||||
:deep(.arco-input-append) {
|
||||
@apply border-none;
|
||||
}
|
||||
:deep(.select-input-append) {
|
||||
@apply z-10;
|
||||
|
||||
margin-left: -16px !important;
|
||||
border-radius: 0 4px 4px 0 !important;
|
||||
background-color: var(--color-text-n8) !important;
|
||||
&:hover {
|
||||
border-color: rgb(var(--primary-5)) !important;
|
||||
background-color: var(--color-text-n8) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 输入框组合-选择器前缀样式类 **/
|
||||
.ms-input-group--prepend() {
|
||||
:deep(.arco-input-prepend) {
|
||||
@apply border-none;
|
||||
}
|
||||
:deep(.select-input-prepend) {
|
||||
@apply z-10;
|
||||
|
||||
margin-right: -16px !important;
|
||||
border-radius: 4px 0 0 4px !important;
|
||||
background-color: var(--color-text-n8) !important;
|
||||
&:hover {
|
||||
border-color: rgb(var(--primary-5)) !important;
|
||||
background-color: var(--color-text-n8) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -245,7 +245,7 @@
|
|||
<style lang="less" scoped>
|
||||
.ms-card-list-container {
|
||||
@apply h-full overflow-hidden;
|
||||
.ms-container--shadow();
|
||||
.ms-container--shadow-y();
|
||||
.ms-card-list {
|
||||
@apply grid max-h-full overflow-auto;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
v-model="innerValue"
|
||||
class="ms-cascader"
|
||||
:options="props.options"
|
||||
:trigger-props="{ contentClass: 'ms-cascader-popper' }"
|
||||
:trigger-props="{ contentClass: `ms-cascader-popper ms-cascader-popper--${props.optionSize}` }"
|
||||
multiple
|
||||
allow-clear
|
||||
check-strictly
|
||||
|
@ -13,30 +13,31 @@
|
|||
:virtual-list-props="props.virtualListProps"
|
||||
:placeholder="props.placeholder"
|
||||
:loading="props.loading"
|
||||
value-key="value"
|
||||
:value-key="props.valueKey"
|
||||
:path-mode="props.pathMode"
|
||||
@change="handleMsCascaderChange"
|
||||
@clear="clearValues"
|
||||
>
|
||||
<template #prefix>
|
||||
<template v-if="props.prefix" #prefix>
|
||||
{{ props.prefix }}
|
||||
</template>
|
||||
<template #label="{ data }">
|
||||
<a-tooltip :content="data.label" position="top" :mouse-enter-delay="500" mini>
|
||||
<div class="one-line-text inline-block">{{ data.label }}</div>
|
||||
<a-tooltip :content="getInputLabel(data)" position="top" :mouse-enter-delay="500" mini>
|
||||
<div class="one-line-text inline-block">{{ getInputLabel(data) }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #option="{ data }">
|
||||
<a-tooltip :content="data.label" position="top" :mouse-enter-delay="500" mini>
|
||||
<a-tooltip :content="t(data.label)" position="top" :mouse-enter-delay="500" mini>
|
||||
<a-radio
|
||||
v-if="data.level === 0"
|
||||
v-model:model-value="innerLevel"
|
||||
:value="data.value.value"
|
||||
:value="data.value[props.valueKey]"
|
||||
size="mini"
|
||||
@change="handleLevelChange"
|
||||
>
|
||||
<div class="one-line-text" :style="getOptionComputedStyle">{{ data.label }}</div>
|
||||
<div class="one-line-text" :style="getOptionComputedStyle">{{ t(data.label) }}</div>
|
||||
</a-radio>
|
||||
<div v-else class="one-line-text" :style="getOptionComputedStyle">{{ data.label }}</div>
|
||||
<div v-else class="one-line-text" :style="getOptionComputedStyle">{{ t(data.label) }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-cascader>
|
||||
|
@ -46,7 +47,7 @@
|
|||
v-model="innerValue"
|
||||
class="ms-cascader"
|
||||
:options="props.options"
|
||||
:trigger-props="{ contentClass: 'ms-cascader-popper' }"
|
||||
:trigger-props="{ contentClass: `ms-cascader-popper ms-cascader-popper--${props.optionSize}` }"
|
||||
:multiple="props.multiple"
|
||||
allow-clear
|
||||
:check-strictly="props.strictly"
|
||||
|
@ -54,19 +55,30 @@
|
|||
:placeholder="props.placeholder"
|
||||
:virtual-list-props="props.virtualListProps"
|
||||
:loading="props.loading"
|
||||
:value-key="props.valueKey"
|
||||
:path-mode="props.pathMode"
|
||||
@change="(val) => emit('change', val)"
|
||||
>
|
||||
<template #prefix>
|
||||
<template v-if="props.prefix" #prefix>
|
||||
{{ props.prefix }}
|
||||
</template>
|
||||
<template #label="{ data }">
|
||||
<a-tooltip :content="data.label" position="top" :mouse-enter-delay="500" mini>
|
||||
<div class="one-line-text inline translate-y-[15%]">{{ data.label }}</div>
|
||||
</a-tooltip>
|
||||
<slot name="label" :data="{ ...data, [props.labelKey]: getInputLabel(data) }">
|
||||
<a-tooltip :content="getInputLabel(data)" position="top" :mouse-enter-delay="500" mini>
|
||||
<div class="one-line-text inline translate-y-[15%]">
|
||||
{{ getInputLabel(data) }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</slot>
|
||||
</template>
|
||||
<template #option="{ data }">
|
||||
<a-tooltip :content="data.label" position="top" :mouse-enter-delay="500" mini>
|
||||
<div class="one-line-text" :style="getOptionComputedStyle">{{ data.label }}</div>
|
||||
</a-tooltip>
|
||||
<slot name="option" :data="data">
|
||||
<a-tooltip :content="t(data.label)" position="top" :mouse-enter-delay="500" mini>
|
||||
<div class="one-line-text" :style="getOptionComputedStyle">
|
||||
{{ t(data.label) }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</slot>
|
||||
</template>
|
||||
</a-cascader>
|
||||
</template>
|
||||
|
@ -74,6 +86,7 @@
|
|||
<script setup lang="ts">
|
||||
import { Ref, ref, watch } from 'vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useSelect from '@/hooks/useSelect';
|
||||
|
||||
import type { CascaderOption } from '@arco-design/web-vue';
|
||||
|
@ -94,12 +107,22 @@
|
|||
panelWidth?: number; // 下拉框宽度,默认为 150px
|
||||
placeholder?: string;
|
||||
loading?: boolean;
|
||||
optionSize?: 'small' | 'default';
|
||||
pathMode?: boolean; // 是否开启路径模式
|
||||
valueKey?: string;
|
||||
labelKey?: string; // 传入自定义的 labelKey
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<MsCascaderProps>(), {
|
||||
mode: 'MS',
|
||||
optionSize: 'default',
|
||||
pathMode: false,
|
||||
valueKey: 'value',
|
||||
labelKey: 'label',
|
||||
});
|
||||
const emit = defineEmits(['update:modelValue', 'update:level']);
|
||||
const emit = defineEmits(['update:modelValue', 'update:level', 'change']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const innerValue = ref<CascaderModelValue>([]);
|
||||
const innerLevel = ref(''); // 顶级选项,该级别为单选选项
|
||||
|
@ -190,12 +213,21 @@
|
|||
calculateMaxTag();
|
||||
}
|
||||
|
||||
// TODO: 临时解决 arco-design 的 cascader 组件绑定值只能是 path-mode 的问题,如果实际值也包含了 ‘-’,则不要取这个值,而是取绑定的 v-model 的值
|
||||
function getInputLabel(data: CascaderOption) {
|
||||
if (!props.pathMode) {
|
||||
return t(data[props.labelKey].split('-').pop());
|
||||
}
|
||||
return t(data[props.labelKey]);
|
||||
}
|
||||
|
||||
function clearValues() {
|
||||
innerLevel.value = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
/* stylelint-disable value-keyword-case */
|
||||
.ms-cascader {
|
||||
@apply overflow-hidden;
|
||||
.arco-select-view-inner {
|
||||
|
@ -208,6 +240,9 @@
|
|||
.ms-cascader-popper {
|
||||
.arco-cascader-panel {
|
||||
.arco-cascader-panel-column {
|
||||
.arco-cascader-column-content {
|
||||
padding: 4px 0;
|
||||
}
|
||||
.arco-virtual-list {
|
||||
.ms-scroll-bar();
|
||||
}
|
||||
|
@ -226,5 +261,14 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.ms-cascader-popper--small {
|
||||
.arco-cascader-panel {
|
||||
.arco-cascader-panel-column {
|
||||
.arco-cascader-option {
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@/hooks/useSelect
|
||||
|
|
|
@ -0,0 +1,903 @@
|
|||
/* eslint-disable no-template-curly-in-string */
|
||||
import { MockParamItem } from './types';
|
||||
|
||||
// mock基础分组
|
||||
export const mockBaseGroup: MockParamItem[] = [
|
||||
{
|
||||
label: 'ms.paramsInput.bool',
|
||||
value: '@bool',
|
||||
desc: 'ms.paramsInput.boolDesc',
|
||||
},
|
||||
];
|
||||
// mock数字分组
|
||||
export const mockNumberGroup: MockParamItem[] = [
|
||||
{
|
||||
label: 'ms.paramsInput.natural',
|
||||
value: '@natural',
|
||||
desc: 'ms.paramsInput.naturalDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.naturalRange',
|
||||
value: '@natural(1,100)',
|
||||
desc: 'ms.paramsInput.naturalRangeDesc',
|
||||
inputGroup: [
|
||||
{
|
||||
type: 'number',
|
||||
value: 1,
|
||||
label: 'ms.paramsInput.min',
|
||||
placeholder: 'ms.paramsInput.minNaturalPlaceholder',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
value: 100,
|
||||
label: 'ms.paramsInput.max',
|
||||
placeholder: 'ms.paramsInput.maxNaturalPlaceholder',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.integer',
|
||||
value: '@integer',
|
||||
desc: 'ms.paramsInput.integerDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.integerRange',
|
||||
value: '@integer(1,100)',
|
||||
desc: 'ms.paramsInput.integerRangeDesc',
|
||||
inputGroup: [
|
||||
{
|
||||
type: 'number',
|
||||
value: 1,
|
||||
label: 'ms.paramsInput.min',
|
||||
placeholder: 'ms.paramsInput.minIntegerPlaceholder',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
value: 100,
|
||||
label: 'ms.paramsInput.max',
|
||||
placeholder: 'ms.paramsInput.maxIntegerPlaceholder',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.float',
|
||||
value: '@floatNumber(1,10,2,5)',
|
||||
desc: 'ms.paramsInput.floatDesc',
|
||||
inputGroup: [
|
||||
{
|
||||
type: 'number',
|
||||
value: 1,
|
||||
label: 'ms.paramsInput.floatIntegerMin',
|
||||
placeholder: 'ms.paramsInput.commonPlaceholder',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
value: 10,
|
||||
label: 'ms.paramsInput.floatIntegerMax',
|
||||
placeholder: 'ms.paramsInput.commonPlaceholder',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
value: 2,
|
||||
label: 'ms.paramsInput.floatMin',
|
||||
placeholder: 'ms.paramsInput.commonPlaceholder',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
value: 5,
|
||||
label: 'ms.paramsInput.floatMax',
|
||||
placeholder: 'ms.paramsInput.commonPlaceholder',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.integerArray',
|
||||
value: '@range(1,100,1)',
|
||||
desc: 'ms.paramsInput.integerArrayDesc',
|
||||
inputGroup: [
|
||||
{
|
||||
type: 'number',
|
||||
value: 1,
|
||||
label: 'start',
|
||||
placeholder: 'ms.paramsInput.integerArrayStartPlaceholder',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
value: 100,
|
||||
label: 'end',
|
||||
placeholder: 'ms.paramsInput.integerArrayEndPlaceholder',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
value: 1,
|
||||
label: 'step',
|
||||
placeholder: 'ms.paramsInput.integerArrayStepPlaceholder',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
// mock字符串分组
|
||||
export const mockStringGroup: MockParamItem[] = [
|
||||
{
|
||||
label: 'ms.paramsInput.character',
|
||||
value: '@character(pool)',
|
||||
desc: 'ms.paramsInput.characterDesc',
|
||||
inputGroup: [
|
||||
{
|
||||
type: 'input',
|
||||
value: '',
|
||||
label: 'ms.paramsInput.character',
|
||||
placeholder: 'ms.paramsInput.commonPlaceholder',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.characterLower',
|
||||
value: "@character('lower')",
|
||||
desc: 'ms.paramsInput.characterLowerDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.characterUpper',
|
||||
value: "@character('upper')",
|
||||
desc: 'ms.paramsInput.characterUpperDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.characterSymbol',
|
||||
value: "@character('symbol')",
|
||||
desc: 'ms.paramsInput.characterSymbolDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.string',
|
||||
value: '@string(1,10)',
|
||||
desc: 'ms.paramsInput.stringDesc',
|
||||
inputGroup: [
|
||||
{
|
||||
type: 'number',
|
||||
value: 1,
|
||||
label: 'ms.paramsInput.stringMin',
|
||||
placeholder: 'ms.paramsInput.commonPlaceholder',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
value: 10,
|
||||
label: 'ms.paramsInput.stringMax',
|
||||
placeholder: 'ms.paramsInput.commonPlaceholder',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
// 字符串变量特殊处理
|
||||
export const specialStringVars = [
|
||||
'@character(pool)',
|
||||
"@character('lower')",
|
||||
"@character('upper')",
|
||||
"@character('symbol')",
|
||||
];
|
||||
// mock日期分组
|
||||
export const mockDateGroup: MockParamItem[] = [
|
||||
{
|
||||
label: 'ms.paramsInput.date',
|
||||
value: "@date('yyyy-MM-dd')",
|
||||
desc: 'ms.paramsInput.dateDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.time',
|
||||
value: "@time('HH:mm:ss')",
|
||||
desc: 'ms.paramsInput.timeDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.dateTime',
|
||||
value: "@dateTime('yyyy-MM-dd HH:mm:ss')",
|
||||
desc: 'ms.paramsInput.dateTimeDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.nowDateTime',
|
||||
value: "@now('yyyy-MM-dd HH:mm:ss')",
|
||||
desc: 'ms.paramsInput.nowDateTimeDesc',
|
||||
},
|
||||
];
|
||||
// mockWeb变量分组
|
||||
export const mockWebMap: MockParamItem[] = [
|
||||
{
|
||||
label: 'ms.paramsInput.url',
|
||||
value: "@url('http')",
|
||||
desc: 'ms.paramsInput.urlDesc',
|
||||
inputGroup: [
|
||||
{
|
||||
type: 'inputAppendSelect',
|
||||
value: '',
|
||||
label: 'ms.paramsInput.url',
|
||||
inputValue: '',
|
||||
selectValue: 'http',
|
||||
placeholder: 'ms.paramsInput.urlPlaceholder',
|
||||
options: [
|
||||
{
|
||||
label: 'http',
|
||||
value: 'http',
|
||||
},
|
||||
{
|
||||
label: 'https',
|
||||
value: 'https',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.protocol',
|
||||
value: '@protocol',
|
||||
desc: 'ms.paramsInput.protocolDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.domain',
|
||||
value: '@domain',
|
||||
desc: 'ms.paramsInput.domainDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.topDomain',
|
||||
value: '@tld',
|
||||
desc: 'ms.paramsInput.topDomainDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.email',
|
||||
value: '@email',
|
||||
desc: 'ms.paramsInput.emailDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.ip',
|
||||
value: '@ip',
|
||||
desc: 'ms.paramsInput.ipDesc',
|
||||
},
|
||||
];
|
||||
// mock地区分组
|
||||
export const mockLocationMap: MockParamItem[] = [
|
||||
{
|
||||
label: 'ms.paramsInput.location',
|
||||
value: '@region',
|
||||
desc: 'ms.paramsInput.locationDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.province',
|
||||
value: '@province',
|
||||
desc: 'ms.paramsInput.provinceDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.city',
|
||||
value: '@city',
|
||||
desc: 'ms.paramsInput.cityDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.county',
|
||||
value: '@county',
|
||||
desc: 'ms.paramsInput.countyDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.provinceCityCounty',
|
||||
value: '@county(true)',
|
||||
desc: 'ms.paramsInput.provinceCityCountyDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.zip',
|
||||
value: '@zip',
|
||||
desc: 'ms.paramsInput.zipDesc',
|
||||
},
|
||||
];
|
||||
// mock个人信息分组
|
||||
export const mockPersonalMap: MockParamItem[] = [
|
||||
{
|
||||
label: 'ms.paramsInput.idCard',
|
||||
value: '@idCard',
|
||||
desc: 'ms.paramsInput.idCardDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.specifyIdCard',
|
||||
value: '@idCard(birth)',
|
||||
desc: 'ms.paramsInput.specifyIdCardDesc',
|
||||
inputGroup: [
|
||||
{
|
||||
type: 'date',
|
||||
value: '',
|
||||
label: 'ms.paramsInput.birthday',
|
||||
placeholder: 'ms.paramsInput.birthdayPlaceholder',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.phone',
|
||||
value: '@phoneNumber',
|
||||
desc: 'ms.paramsInput.phoneDesc',
|
||||
},
|
||||
];
|
||||
// mock英文名分组
|
||||
export const mockEnglishNameGroupMap: MockParamItem[] = [
|
||||
{
|
||||
label: 'ms.paramsInput.englishName',
|
||||
value: '@first',
|
||||
desc: 'ms.paramsInput.englishNameDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.englishSurname',
|
||||
value: '@last',
|
||||
desc: 'ms.paramsInput.englishSurnameDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.englishFullName',
|
||||
value: '@name',
|
||||
desc: 'ms.paramsInput.englishFullNameDesc',
|
||||
},
|
||||
];
|
||||
// mock中文名分组
|
||||
export const mockChineseNameGroupMap: MockParamItem[] = [
|
||||
{
|
||||
label: 'ms.paramsInput.chineseName',
|
||||
value: '@cfirst',
|
||||
desc: 'ms.paramsInput.chineseNameDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.chineseSurname',
|
||||
value: '@clast',
|
||||
desc: 'ms.paramsInput.chineseSurnameDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.chineseFullName',
|
||||
value: '@cname',
|
||||
desc: 'ms.paramsInput.chineseFullNameDesc',
|
||||
},
|
||||
];
|
||||
// mock颜色分组
|
||||
export const mockColorGroupMap: MockParamItem[] = [
|
||||
{
|
||||
label: 'ms.paramsInput.color',
|
||||
value: '@color',
|
||||
desc: 'ms.paramsInput.colorDesc',
|
||||
},
|
||||
{
|
||||
label: 'RGB',
|
||||
value: '@rgb',
|
||||
desc: 'ms.paramsInput.RGBDesc',
|
||||
},
|
||||
{
|
||||
label: 'RGBA',
|
||||
value: '@rgba',
|
||||
desc: 'ms.paramsInput.RGBADesc',
|
||||
},
|
||||
{
|
||||
label: 'HSL',
|
||||
value: '@hsl',
|
||||
desc: 'ms.paramsInput.hslDesc',
|
||||
},
|
||||
];
|
||||
// mock英文文本分组
|
||||
export const mockEnglishTextGroupMap: MockParamItem[] = [
|
||||
{
|
||||
label: 'ms.paramsInput.englishText',
|
||||
value: '@paragraph',
|
||||
desc: 'ms.paramsInput.englishTextDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.englishSentence',
|
||||
value: '@sentence',
|
||||
desc: 'ms.paramsInput.englishSentenceDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.englishWord',
|
||||
value: '@word',
|
||||
desc: 'ms.paramsInput.englishWordDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.englishTitle',
|
||||
value: '@title',
|
||||
desc: 'ms.paramsInput.englishTitleDesc',
|
||||
},
|
||||
];
|
||||
// mock中文文本分组
|
||||
export const mockChineseTextGroupMap: MockParamItem[] = [
|
||||
{
|
||||
label: 'ms.paramsInput.chineseText',
|
||||
value: '@cparagraph',
|
||||
desc: 'ms.paramsInput.chineseTextDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.chineseSentence',
|
||||
value: '@csentence',
|
||||
desc: 'ms.paramsInput.chineseSentenceDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.chineseWord',
|
||||
value: '@cword',
|
||||
desc: 'ms.paramsInput.chineseWordDesc',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.chineseTitle',
|
||||
value: '@ctitle',
|
||||
desc: 'ms.paramsInput.chineseTitleDesc',
|
||||
},
|
||||
];
|
||||
// mock正则表达式分组
|
||||
export const mockRegExpMap: MockParamItem[] = [
|
||||
{
|
||||
label: 'ms.paramsInput.regexp',
|
||||
value: '@regexp',
|
||||
desc: 'ms.paramsInput.regexpDesc',
|
||||
inputGroup: [
|
||||
{
|
||||
type: 'input',
|
||||
value: '',
|
||||
label: 'ms.paramsInput.regexp',
|
||||
placeholder: 'ms.paramsInput.commonPlaceholder',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
// mock函数
|
||||
export const mockFunctions: MockParamItem[] = [
|
||||
{
|
||||
label: 'md5',
|
||||
value: 'md5',
|
||||
desc: 'ms.paramsInput.md5Desc',
|
||||
},
|
||||
{
|
||||
label: 'base64',
|
||||
value: 'base64',
|
||||
desc: 'ms.paramsInput.base64Desc',
|
||||
},
|
||||
{
|
||||
label: 'unbase64',
|
||||
value: 'unbase64',
|
||||
desc: 'ms.paramsInput.unbase64Desc',
|
||||
},
|
||||
{
|
||||
label: 'substr',
|
||||
value: 'substr',
|
||||
desc: 'ms.paramsInput.substrDesc',
|
||||
inputGroup: [
|
||||
{
|
||||
type: 'number',
|
||||
value: NaN,
|
||||
label: 'start',
|
||||
placeholder: 'ms.paramsInput.substrStartPlaceholder',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
value: NaN,
|
||||
label: 'end',
|
||||
placeholder: 'ms.paramsInput.substrEndPlaceholder',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'concatconcat',
|
||||
value: 'concatconcat',
|
||||
desc: 'ms.paramsInput.concatconcatDesc',
|
||||
inputGroup: [
|
||||
{
|
||||
type: 'input',
|
||||
value: '',
|
||||
label: 'ms.paramsInput.concatconcat',
|
||||
placeholder: 'ms.paramsInput.commonPlaceholder',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'lconcat',
|
||||
value: 'lconcat',
|
||||
desc: 'ms.paramsInput.lconcatDesc',
|
||||
inputGroup: [
|
||||
{
|
||||
type: 'input',
|
||||
value: '',
|
||||
label: 'ms.paramsInput.lconcat',
|
||||
placeholder: 'ms.paramsInput.commonPlaceholder',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'sha1',
|
||||
value: 'sha1',
|
||||
desc: 'ms.paramsInput.sha1Desc',
|
||||
},
|
||||
{
|
||||
label: 'sha224',
|
||||
value: 'sha224',
|
||||
desc: 'ms.paramsInput.sha224Desc',
|
||||
},
|
||||
{
|
||||
label: 'sha256',
|
||||
value: 'sha256',
|
||||
desc: 'ms.paramsInput.sha256Desc',
|
||||
},
|
||||
{
|
||||
label: 'sha384',
|
||||
value: 'sha384',
|
||||
desc: 'ms.paramsInput.sha384Desc',
|
||||
},
|
||||
{
|
||||
label: 'sha512',
|
||||
value: 'sha512',
|
||||
desc: 'ms.paramsInput.sha512Desc',
|
||||
},
|
||||
{
|
||||
label: 'lower',
|
||||
value: 'lower',
|
||||
desc: 'ms.paramsInput.lowerDesc',
|
||||
},
|
||||
{
|
||||
label: 'upper',
|
||||
value: 'upper',
|
||||
desc: 'ms.paramsInput.upperDesc',
|
||||
},
|
||||
{
|
||||
label: 'length',
|
||||
value: 'length',
|
||||
desc: 'ms.paramsInput.lengthDesc',
|
||||
},
|
||||
{
|
||||
label: 'number',
|
||||
value: 'number',
|
||||
desc: 'ms.paramsInput.numberDesc',
|
||||
},
|
||||
];
|
||||
// mock所有变量
|
||||
export const mockAllParams: MockParamItem[] = [
|
||||
...mockBaseGroup,
|
||||
...mockNumberGroup,
|
||||
...mockStringGroup,
|
||||
...mockDateGroup,
|
||||
...mockWebMap,
|
||||
...mockLocationMap,
|
||||
...mockPersonalMap,
|
||||
...mockEnglishNameGroupMap,
|
||||
...mockChineseNameGroupMap,
|
||||
...mockColorGroupMap,
|
||||
...mockEnglishTextGroupMap,
|
||||
...mockChineseTextGroupMap,
|
||||
...mockRegExpMap,
|
||||
];
|
||||
// mock所有分组
|
||||
export const mockAllGroup = [
|
||||
{
|
||||
value: 'base',
|
||||
label: 'ms.paramsInput.base',
|
||||
children: mockBaseGroup,
|
||||
},
|
||||
{
|
||||
value: 'string',
|
||||
label: 'ms.paramsInput.string',
|
||||
children: mockStringGroup,
|
||||
},
|
||||
{
|
||||
value: 'personal',
|
||||
label: 'ms.paramsInput.personal',
|
||||
children: mockPersonalMap,
|
||||
},
|
||||
{
|
||||
value: 'dateTime',
|
||||
label: 'ms.paramsInput.dateOrTime',
|
||||
children: mockDateGroup,
|
||||
},
|
||||
{
|
||||
value: 'cname',
|
||||
label: 'ms.paramsInput.chineseFullName',
|
||||
children: mockChineseNameGroupMap,
|
||||
},
|
||||
{
|
||||
value: 'ename',
|
||||
label: 'ms.paramsInput.englishFullName',
|
||||
children: mockEnglishNameGroupMap,
|
||||
},
|
||||
{
|
||||
value: 'ctext',
|
||||
label: 'ms.paramsInput.cText',
|
||||
children: mockChineseTextGroupMap,
|
||||
},
|
||||
{
|
||||
value: 'etext',
|
||||
label: 'ms.paramsInput.eText',
|
||||
children: mockEnglishTextGroupMap,
|
||||
},
|
||||
{
|
||||
value: 'web',
|
||||
label: 'ms.paramsInput.web',
|
||||
children: mockWebMap,
|
||||
},
|
||||
{
|
||||
value: 'number',
|
||||
label: 'ms.paramsInput.number',
|
||||
children: mockNumberGroup,
|
||||
},
|
||||
{
|
||||
value: 'location',
|
||||
label: 'ms.paramsInput.county',
|
||||
children: mockLocationMap,
|
||||
},
|
||||
{
|
||||
value: 'color',
|
||||
label: 'ms.paramsInput.color',
|
||||
children: mockColorGroupMap,
|
||||
},
|
||||
{
|
||||
value: 'regexp',
|
||||
label: 'ms.paramsInput.regexp',
|
||||
children: mockRegExpMap,
|
||||
},
|
||||
];
|
||||
|
||||
// JMeter变量分组
|
||||
export const JMeterVariableGroup = [
|
||||
{
|
||||
label: 'ms.paramsInput.randomFromMultipleVars',
|
||||
value: '${__RandomFromMultipleVars}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.split',
|
||||
value: '${__split}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.eval',
|
||||
value: '${__eval}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.evalVar',
|
||||
value: '${__evalVar}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.V',
|
||||
value: '${__V}',
|
||||
},
|
||||
];
|
||||
// JMeter编码分组
|
||||
export const JMeterCodeGroup = [
|
||||
{
|
||||
label: 'ms.paramsInput.escapeHtml',
|
||||
value: '${__escapeHtml}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.escapeXml',
|
||||
value: '${__escapeXml}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.unescape',
|
||||
value: '${__unescape}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.unescapeHtml',
|
||||
value: '${__unescapeHtml}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.urldecode',
|
||||
value: '${__urldecode}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.urlencode',
|
||||
value: '${__urlencode}',
|
||||
},
|
||||
];
|
||||
// JMeter脚本分组
|
||||
export const JMeterScriptGroup = [
|
||||
{
|
||||
label: 'ms.paramsInput.groovy',
|
||||
value: '${__groovy}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.BeanShell',
|
||||
value: '${__BeanShell}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.javaScript',
|
||||
value: '${__javaScript}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.jexl2',
|
||||
value: '${__jexl2}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.jexl3',
|
||||
value: '${__jexl3}',
|
||||
},
|
||||
];
|
||||
// JMeter时间分组
|
||||
export const JMeterTimeGroup = [
|
||||
{
|
||||
label: 'ms.paramsInput.jmeterTime',
|
||||
value: '${__time}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.timeShift',
|
||||
value: '${__timeShift}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.dateTimeConvert',
|
||||
value: '${__dateTimeConvert}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.RandomDate',
|
||||
value: '${__RandomDate}',
|
||||
},
|
||||
];
|
||||
// JMeter属性分组
|
||||
export const JMeterPropertyGroup = [
|
||||
{
|
||||
label: 'ms.paramsInput.isPropDefined',
|
||||
value: '${__isPropDefined}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.readProperty',
|
||||
value: '${__property}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.P',
|
||||
value: '${__P}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.setProperty',
|
||||
value: '${__setProperty}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.isVarDefined',
|
||||
value: '${__isVarDefined}',
|
||||
},
|
||||
];
|
||||
// JMeter数字分组
|
||||
export const JMeterNumberGroup = [
|
||||
{
|
||||
label: 'ms.paramsInput.counter',
|
||||
value: '${__counter}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.intSum',
|
||||
value: '${__intSum}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.longSum',
|
||||
value: '${__longSum}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.Random',
|
||||
value: '${__Random}',
|
||||
},
|
||||
];
|
||||
// JMeter文件分组
|
||||
export const JMeterFileGroup = [
|
||||
{
|
||||
label: 'ms.paramsInput.StringFromFile',
|
||||
value: '${__StringFromFile}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.FileToString',
|
||||
value: '${__FileToString}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.CSVRead',
|
||||
value: '${__CSVRead}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.XPath',
|
||||
value: '${__XPath}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.StringToFile',
|
||||
value: '${__StringToFile}',
|
||||
},
|
||||
];
|
||||
// JMeter信息分组
|
||||
export const JMeterInfoGroup = [
|
||||
{
|
||||
label: 'ms.paramsInput.digest',
|
||||
value: '${__digest}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.threadNum',
|
||||
value: '${__threadNum}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.threadGroupName',
|
||||
value: '${__threadGroupName}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.samplerName',
|
||||
value: '${__samplerName}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.machineIP',
|
||||
value: '${__machineIP}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.machineName',
|
||||
value: '${__machineName}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.TestPlanName',
|
||||
value: '${__TestPlanName}',
|
||||
},
|
||||
];
|
||||
// JMeter字符串分组
|
||||
export const JMeterStringGroup = [
|
||||
{
|
||||
label: 'ms.paramsInput.log',
|
||||
value: '${__log}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.logn',
|
||||
value: '${__logn}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.RandomString',
|
||||
value: '${__RandomString}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.UUID',
|
||||
value: '${__UUID}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.char',
|
||||
value: '${__char}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.changeCase',
|
||||
value: '${__changeCase}',
|
||||
},
|
||||
{
|
||||
label: 'ms.paramsInput.regexFunction',
|
||||
value: '${__regexFunction}',
|
||||
},
|
||||
];
|
||||
// JMeter所有分组
|
||||
export const JMeterAllGroup = [
|
||||
{
|
||||
value: 'variable',
|
||||
label: 'ms.paramsInput.variable',
|
||||
children: JMeterVariableGroup,
|
||||
},
|
||||
{
|
||||
value: 'code',
|
||||
label: 'ms.paramsInput.code',
|
||||
children: JMeterCodeGroup,
|
||||
},
|
||||
{
|
||||
value: 'script',
|
||||
label: 'ms.paramsInput.script',
|
||||
children: JMeterScriptGroup,
|
||||
},
|
||||
{
|
||||
value: 'time',
|
||||
label: 'ms.paramsInput.time',
|
||||
children: JMeterTimeGroup,
|
||||
},
|
||||
{
|
||||
value: 'property',
|
||||
label: 'ms.paramsInput.property',
|
||||
children: JMeterPropertyGroup,
|
||||
},
|
||||
{
|
||||
value: 'number',
|
||||
label: 'ms.paramsInput.number',
|
||||
children: JMeterNumberGroup,
|
||||
},
|
||||
{
|
||||
value: 'file',
|
||||
label: 'ms.paramsInput.file',
|
||||
children: JMeterFileGroup,
|
||||
},
|
||||
{
|
||||
value: 'info',
|
||||
label: 'ms.paramsInput.info',
|
||||
children: JMeterInfoGroup,
|
||||
},
|
||||
{
|
||||
value: 'string',
|
||||
label: 'ms.paramsInput.string',
|
||||
children: JMeterStringGroup,
|
||||
},
|
||||
];
|
||||
// JMeter所有变量
|
||||
export const JMeterAllVars = [
|
||||
...JMeterVariableGroup,
|
||||
...JMeterCodeGroup,
|
||||
...JMeterScriptGroup,
|
||||
...JMeterTimeGroup,
|
||||
...JMeterPropertyGroup,
|
||||
...JMeterNumberGroup,
|
||||
...JMeterFileGroup,
|
||||
...JMeterInfoGroup,
|
||||
...JMeterStringGroup,
|
||||
];
|
|
@ -0,0 +1,642 @@
|
|||
<template>
|
||||
<a-trigger
|
||||
v-model:popup-visible="paramSettingVisible"
|
||||
trigger="click"
|
||||
:popup-translate="[0, 16]"
|
||||
position="bl"
|
||||
class="ms-params-input-setting-trigger"
|
||||
>
|
||||
<span class="invisible"></span>
|
||||
<template #content>
|
||||
<div class="ms-params-input-setting-trigger-content">
|
||||
<div class="mb-[16px] flex items-center justify-between">
|
||||
<div class="font-semibold text-[var(--color-text-1)]">{{ t('ms.paramsInput.paramSetting') }}</div>
|
||||
<a-radio-group
|
||||
v-model:model-value="paramSettingType"
|
||||
type="button"
|
||||
size="small"
|
||||
@change="handleParamSettingChange"
|
||||
>
|
||||
<a-radio value="mock">Mock</a-radio>
|
||||
<a-radio value="jmeter">JMeter</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div class="ms-params-input-setting-trigger-content-scroll">
|
||||
<a-form ref="paramFormRef" :model="paramForm" layout="vertical">
|
||||
<template v-if="paramSettingType === 'mock'">
|
||||
<a-form-item
|
||||
field="type"
|
||||
:label="t('ms.paramsInput.mockType')"
|
||||
:rules="[{ required: true, message: t('ms.paramsInput.mockTypePlaceholder') }]"
|
||||
asterisk-position="end"
|
||||
class="mb-[16px]"
|
||||
>
|
||||
<MsCascader
|
||||
v-model:model-value="paramForm.type"
|
||||
mode="native"
|
||||
:options="paramTypeOptions"
|
||||
:placeholder="t('ms.paramsInput.mockTypePlaceholder')"
|
||||
option-size="small"
|
||||
label-key="value"
|
||||
value-key="key"
|
||||
@change="handleParamTypeChange"
|
||||
>
|
||||
<template #label="{ data }">
|
||||
<a-tooltip :content="`${t(data.label.split('/').pop().trim())} ${paramForm.type}`">
|
||||
<div class="one-line-text inline-flex w-full items-center justify-between pr-[8px]" title="">
|
||||
{{ t(data.label.split('/').pop().trim()) }}
|
||||
<div class="max-w-[60%] text-[var(--color-text-4)]" title="">
|
||||
{{ paramForm.type }}
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #option="{ data }">
|
||||
<div
|
||||
:class="`flex ${data.isLeaf ? 'mr-[-26px] w-[270px]' : 'w-[120px]'} items-center justify-between`"
|
||||
title=""
|
||||
>
|
||||
<a-tooltip :content="t(data.label)">
|
||||
<div :class="`one-line-text ${data.isLeaf ? 'max-w-[50%]' : ''}`" title="">
|
||||
{{ t(data.label) }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="data.isLeaf" :content="data.value">
|
||||
<div class="one-line-text max-w-[50%] text-[var(--color-text-4)]">
|
||||
{{ data.value }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</MsCascader>
|
||||
</a-form-item>
|
||||
<paramsInputGroup
|
||||
v-if="currentParamsInputGroup.length > 0"
|
||||
:param-form="paramForm"
|
||||
:input-group="currentParamsInputGroup"
|
||||
/>
|
||||
<a-form-item :label="t('ms.paramsInput.addFunc')" class="mb-[16px]">
|
||||
<a-select
|
||||
v-model:model-value="paramForm.func"
|
||||
:options="paramFuncOptions"
|
||||
:placeholder="t('ms.paramsInput.commonSelectPlaceholder')"
|
||||
@change="(val) => handleParamFuncChange(val as string)"
|
||||
>
|
||||
<template #label="{ data }">
|
||||
<a-tooltip :content="`${t(data.label)} ${t(data.desc)}`">
|
||||
<div class="one-line-text inline-flex w-full items-center justify-between pr-[8px]" title="">
|
||||
{{ data.value }}
|
||||
<div class="max-w-[60%] text-[var(--color-text-4)]" title="">
|
||||
{{ t(data.desc) }}
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #option="{ data }">
|
||||
<div class="flex w-[420px] items-center justify-between">
|
||||
{{ t(data.label) }}
|
||||
<a-tooltip :content="t(data.desc)">
|
||||
<div class="one-line-text max-w-[70%] text-[var(--color-text-4)]">
|
||||
{{ t(data.desc) }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<paramsInputGroup
|
||||
v-if="currentParamsFuncInputGroup.length > 0"
|
||||
type="func"
|
||||
:param-form="paramForm"
|
||||
:input-group="currentParamsFuncInputGroup"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-form-item
|
||||
field="JMeterType"
|
||||
:label="t('ms.paramsInput.jmeterType')"
|
||||
:rules="[{ required: true, message: t('ms.paramsInput.mockTypePlaceholder') }]"
|
||||
asterisk-position="end"
|
||||
class="mb-[16px]"
|
||||
>
|
||||
<MsCascader
|
||||
v-model:model-value="paramForm.JMeterType"
|
||||
mode="native"
|
||||
:options="JMeterVarsOptions"
|
||||
:placeholder="t('ms.paramsInput.mockTypePlaceholder')"
|
||||
option-size="small"
|
||||
label-key="value"
|
||||
value-key="key"
|
||||
@change="handleJMeterTypeChange"
|
||||
>
|
||||
<template #label="{ data }">
|
||||
<div class="inline-flex w-full items-center justify-between">
|
||||
<a-tooltip :content="t(data.label.split('/').pop().trim())">
|
||||
<div class="one-line-text max-w-[50%]" title="">
|
||||
{{ t(data.label.split('/').pop().trim()) }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<a-tooltip :content="`${t(data.label.split('/').pop().trim())} ${paramForm.JMeterType}`">
|
||||
<div class="max-w-[50%] text-[var(--color-text-4)]" title="">
|
||||
{{ paramForm.JMeterType }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<template #option="{ data }">
|
||||
<div
|
||||
:class="`flex ${data.isLeaf ? 'mr-[-26px] w-[270px]' : 'w-[120px]'} items-center justify-between`"
|
||||
>
|
||||
<a-tooltip :content="t(data.label)">
|
||||
<div :class="`one-line-text ${data.isLeaf ? 'max-w-[50%]' : ''}`" title="">
|
||||
{{ t(data.label) }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="data.isLeaf" :content="data.value">
|
||||
<div class="one-line-text max-w-[50%] text-[var(--color-text-4)]">
|
||||
{{ data.value }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</MsCascader>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
<div class="mb-[16px] flex items-center gap-[16px] bg-[var(--color-text-n9)] p-[5px_8px]">
|
||||
<div class="text-[var(--color-text-3)]">{{ t('ms.paramsInput.preview') }}</div>
|
||||
<div class="text-[var(--color-text-1)]">{{ paramPreview }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-[8px]">
|
||||
<a-button type="secondary" size="mini" @click="cancel">{{ t('common.cancel') }}</a-button>
|
||||
<a-button type="primary" size="mini" @click="apply">{{ t('ms.paramsInput.apply') }}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-trigger>
|
||||
<a-popover
|
||||
v-model:popup-visible="popoverVisible"
|
||||
position="tl"
|
||||
:disabled="disabledPopover"
|
||||
class="ms-params-input-popover"
|
||||
>
|
||||
<template #content>
|
||||
<div class="ms-params-popover-title !mb-[8px]">
|
||||
{{ t('ms.paramsInput.value') }}
|
||||
</div>
|
||||
<div class="ms-params-popover-subtitle">
|
||||
{{ t('ms.paramsInput.value') }}
|
||||
</div>
|
||||
<div class="ms-params-popover-value mb-[8px]">
|
||||
{{ innerValue }}
|
||||
</div>
|
||||
<div class="ms-params-popover-subtitle">
|
||||
{{ t('ms.paramsInput.preview') }}
|
||||
</div>
|
||||
<div class="ms-params-popover-value">
|
||||
{{ innerValue }}
|
||||
</div>
|
||||
</template>
|
||||
<a-auto-complete
|
||||
ref="autoCompleteRef"
|
||||
v-model:model-value="innerValue"
|
||||
:data="autoCompleteParams"
|
||||
:placeholder="t('ms.paramsInput.placeholder', { at: '@' })"
|
||||
:class="`ms-params-input ${paramSettingVisible ? 'ms-params-input--focus' : ''}`"
|
||||
:trigger-props="{ contentClass: 'ms-params-input-trigger' }"
|
||||
:filter-option="false"
|
||||
@search="handleSearchParams"
|
||||
@change="(val) => emit('change', val)"
|
||||
@select="selectAutoComplete"
|
||||
>
|
||||
<template #suffix>
|
||||
<MsIcon type="icon-icon_mock" class="ms-params-input-mock-icon" @click.stop="openParamSetting" />
|
||||
</template>
|
||||
<template #option="{ data }">
|
||||
<div class="w-[350px]">
|
||||
{{ data.raw.value }}
|
||||
<a-tooltip :content="t(data.raw.desc)" position="bl" :mouse-enter-delay="300">
|
||||
<div class="one-line-text max-w-[320px] text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||
{{ t(data.raw.desc) }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</a-auto-complete>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useEventListener, useVModel } from '@vueuse/core';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsCascader from '@/components/business/ms-cascader/index.vue';
|
||||
import paramsInputGroup from './paramsInputGroup.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import {
|
||||
JMeterAllGroup,
|
||||
JMeterAllVars,
|
||||
mockAllGroup,
|
||||
mockAllParams,
|
||||
mockFunctions,
|
||||
specialStringVars,
|
||||
} from './config';
|
||||
import type { MockParamInputGroupItem, MockParamItem } from './types';
|
||||
import type { AutoComplete, CascaderOption, FormInstance } from '@arco-design/web-vue';
|
||||
|
||||
const props = defineProps<{
|
||||
value: string;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', val: string): void;
|
||||
(e: 'change', val: string): void;
|
||||
(e: 'dblclick'): void;
|
||||
(e: 'apply', val: string): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const innerValue = useVModel(props, 'value', emit);
|
||||
const autoCompleteParams = ref<MockParamItem[]>([]);
|
||||
const isFocusAutoComplete = ref(false);
|
||||
const popoverVisible = ref(false);
|
||||
const lastTenParams = ref<MockParamItem[]>(JSON.parse(localStorage.getItem('ms-lastTenParams') || '[]')); // 用户最近使用的前 10 个变量
|
||||
|
||||
/**
|
||||
* 搜索变量
|
||||
* @param val 变量名
|
||||
*/
|
||||
function handleSearchParams(val: string) {
|
||||
if (val === '@') {
|
||||
autoCompleteParams.value = [...lastTenParams.value];
|
||||
if (autoCompleteParams.value.length < 10) {
|
||||
// 最近使用的函数列表不满 10 条,补全
|
||||
let lastLength = 10 - autoCompleteParams.value.length; // 剩余需要补全的长度
|
||||
for (let i = 0; i < mockAllParams.length; i++) {
|
||||
const mockParam = mockAllParams[i];
|
||||
if (!autoCompleteParams.value.find((e) => e.value === mockParam.value)) {
|
||||
// 避免重复
|
||||
autoCompleteParams.value.push(mockParam);
|
||||
lastLength--;
|
||||
}
|
||||
if (lastLength === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (/^@/.test(val)) {
|
||||
autoCompleteParams.value = mockAllParams.filter((e) => {
|
||||
return e.value.includes(val);
|
||||
});
|
||||
} else {
|
||||
autoCompleteParams.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最近使用的前 10 个变量
|
||||
* @param val 变量名
|
||||
*/
|
||||
function setLastTenParams(val: string) {
|
||||
const index = lastTenParams.value.findIndex((e) => e.value === val);
|
||||
const lastParamsItem = lastTenParams.value.find((e) => e.value === val);
|
||||
if (index > -1 && lastParamsItem) {
|
||||
// 如果已经存在,移动到第一位
|
||||
lastTenParams.value.splice(index, 1, lastParamsItem);
|
||||
} else {
|
||||
// 如果不存在,添加到第一位
|
||||
const mockParamItem = mockAllParams.find((e) => e.value === val);
|
||||
if (mockParamItem) {
|
||||
lastTenParams.value.unshift(mockParamItem);
|
||||
if (lastTenParams.value.length > 10) {
|
||||
// 如果超过 10 个,删除最后一个
|
||||
lastTenParams.value.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
localStorage.setItem('ms-lastTenParams', JSON.stringify(lastTenParams.value));
|
||||
}
|
||||
|
||||
function selectAutoComplete(val: string) {
|
||||
innerValue.value = val;
|
||||
setLastTenParams(val);
|
||||
}
|
||||
|
||||
const autoCompleteRef = ref<InstanceType<typeof AutoComplete>>();
|
||||
|
||||
onMounted(() => {
|
||||
useEventListener(autoCompleteRef.value?.inputRef, 'dblclick', () => {
|
||||
emit('dblclick');
|
||||
});
|
||||
const autoCompleteInput = (autoCompleteRef.value?.inputRef as any)?.$el.querySelector('.arco-input');
|
||||
useEventListener(autoCompleteInput, 'focus', () => {
|
||||
isFocusAutoComplete.value = true;
|
||||
popoverVisible.value = false;
|
||||
});
|
||||
useEventListener(autoCompleteInput, 'blur', () => {
|
||||
isFocusAutoComplete.value = false;
|
||||
});
|
||||
});
|
||||
|
||||
const disabledPopover = computed(() => {
|
||||
return !innerValue.value || innerValue.value.trim() === '' || isFocusAutoComplete.value;
|
||||
});
|
||||
|
||||
const paramSettingVisible = ref(false);
|
||||
const paramSettingType = ref<'mock' | 'jmeter'>('mock');
|
||||
const defaultParamForm = {
|
||||
type: '',
|
||||
JMeterType: '',
|
||||
param1: '',
|
||||
param2: '',
|
||||
param3: '',
|
||||
param4: '',
|
||||
func: '',
|
||||
funcParam1: '',
|
||||
funcParam2: '',
|
||||
};
|
||||
const paramForm = ref<Record<string, any>>({ ...defaultParamForm });
|
||||
const paramFormRef = ref<FormInstance>();
|
||||
const paramTypeOptions: CascaderOption[] = cloneDeep(mockAllGroup);
|
||||
const paramFuncOptions: MockParamItem[] = cloneDeep(mockFunctions);
|
||||
const paramPreview = ref('xsxsxsxs');
|
||||
const currentParamsInputGroup = ref<MockParamInputGroupItem[]>([]);
|
||||
|
||||
/**
|
||||
* 切换变量类型,设置变量输入框的输入组
|
||||
* @param val 变量类型
|
||||
*/
|
||||
function handleParamTypeChange(val: string) {
|
||||
paramForm.value.type = val;
|
||||
// 匹配@开头的函数名
|
||||
const regex = /@([a-zA-Z]+)(\([^)]*\))?/;
|
||||
const currentParamType = mockAllParams.find((e) => {
|
||||
if (specialStringVars.includes(val)) {
|
||||
return e.value === val;
|
||||
}
|
||||
if (e.value.match(regex)?.[1] === val.match(regex)?.[1]) {
|
||||
paramForm.value.type = e.value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (currentParamType) {
|
||||
currentParamsInputGroup.value = currentParamType.inputGroup || [];
|
||||
} else {
|
||||
currentParamsInputGroup.value = [];
|
||||
}
|
||||
paramForm.value = {
|
||||
...paramForm.value,
|
||||
param1: '',
|
||||
param2: '',
|
||||
param3: '',
|
||||
param4: '',
|
||||
};
|
||||
}
|
||||
|
||||
function handleJMeterTypeChange(val: string) {
|
||||
paramForm.value.JMeterType = val;
|
||||
}
|
||||
|
||||
const currentParamsFuncInputGroup = ref<MockParamInputGroupItem[]>([]);
|
||||
|
||||
/**
|
||||
* 切换函数,设置函数输入框的输入组
|
||||
* @param val 函数
|
||||
*/
|
||||
function handleParamFuncChange(val: string) {
|
||||
paramForm.value.func = val;
|
||||
const currentParamFunc = mockFunctions.find((e) => e.value === val);
|
||||
if (currentParamFunc) {
|
||||
currentParamsFuncInputGroup.value = cloneDeep(currentParamFunc.inputGroup) || [];
|
||||
} else {
|
||||
currentParamsFuncInputGroup.value = [];
|
||||
}
|
||||
paramForm.value = {
|
||||
...paramForm.value,
|
||||
funcParam1: '',
|
||||
funcParam2: '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开变量设置弹窗
|
||||
*/
|
||||
function openParamSetting() {
|
||||
if (/^\$/.test(innerValue.value)) {
|
||||
paramSettingType.value = 'jmeter';
|
||||
if (JMeterAllVars.findIndex((e) => e.value === innerValue.value) !== -1) {
|
||||
paramForm.value.JMeterType = innerValue.value;
|
||||
} else {
|
||||
paramForm.value.JMeterType = '';
|
||||
}
|
||||
} else if (/^@/.test(innerValue.value)) {
|
||||
const valueArr = innerValue.value.split('|'); // 分割 mock变量和函数
|
||||
if (valueArr[0]) {
|
||||
// 匹配@开头的变量名
|
||||
const variableRegex = /@([a-zA-Z]+)(?:\(([^)]*)\))?/;
|
||||
const variableMatch = valueArr[0].match(variableRegex);
|
||||
|
||||
if (variableMatch) {
|
||||
const variableName = variableMatch[1];
|
||||
const variableParams = variableMatch[2]?.split(',').map((param) => param.trim());
|
||||
|
||||
if (variableName === 'character') {
|
||||
handleParamTypeChange(`@${variableName}(${variableParams})`); // character变量特殊处理
|
||||
} else {
|
||||
handleParamTypeChange(`@${variableName}`); // 设置匹配的变量参数输入框组
|
||||
}
|
||||
if (variableName !== 'character' || (variableName === 'character' && variableParams?.[0] !== 'pool')) {
|
||||
// 字符串变量@character(pool)特殊处理,不需要填入 pool
|
||||
(variableParams || []).forEach((e, i) => {
|
||||
// 设置变量入参
|
||||
paramForm.value[`param${i + 1}`] = Number.isNaN(Number(e)) ? e : Number(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (valueArr[1]) {
|
||||
// 匹配函数名和参数
|
||||
const functionRegex = /([a-zA-Z]+)(?:\(([^)]*)\))?/;
|
||||
const functionMatch = valueArr[1].match(functionRegex);
|
||||
|
||||
if (functionMatch) {
|
||||
const functionName = functionMatch[1];
|
||||
const functionParams = functionMatch[2]?.split(',').map((param) => param.trim());
|
||||
|
||||
handleParamFuncChange(functionName); // 设置匹配的函数输入框组
|
||||
(functionParams || []).forEach((e, i) => {
|
||||
// 设置函数入参
|
||||
paramForm.value[`funcParam${i + 1}`] = Number.isNaN(Number(e)) ? e : Number(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
paramSettingVisible.value = true;
|
||||
}
|
||||
|
||||
function handleParamSettingChange() {
|
||||
paramFormRef.value?.clearValidate();
|
||||
}
|
||||
|
||||
const JMeterVarsOptions: CascaderOption[] = cloneDeep(JMeterAllGroup);
|
||||
|
||||
function cancel() {
|
||||
paramFormRef.value?.resetFields();
|
||||
paramSettingType.value = 'mock';
|
||||
paramSettingVisible.value = false;
|
||||
currentParamsInputGroup.value = [];
|
||||
currentParamsFuncInputGroup.value = [];
|
||||
paramForm.value = { ...defaultParamForm };
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用 Mock类型变量和函数
|
||||
*/
|
||||
function applyMock() {
|
||||
let resultStr = '';
|
||||
// 如果选择的变量需要添加入参
|
||||
if (currentParamsInputGroup.value.length > 0) {
|
||||
const testReg = /\(([^)]+)\)/;
|
||||
const paramVal = [paramForm.value.param1, paramForm.value.param2, paramForm.value.param3, paramForm.value.param4]
|
||||
.filter((e) => e !== '')
|
||||
.join(',');
|
||||
// 如果变量名是包含了入参的,则替换()内的入参为用户输入的
|
||||
resultStr = paramVal !== '' ? paramForm.value.type.replace(testReg, `(${paramVal})`) : paramForm.value.type;
|
||||
} else {
|
||||
resultStr = paramForm.value.type;
|
||||
}
|
||||
if (paramForm.value.func !== '') {
|
||||
// 如果选择了添加函数
|
||||
resultStr = `${resultStr}|${paramForm.value.func}`;
|
||||
if (currentParamsFuncInputGroup.value.length > 0 && !Number.isNaN(paramForm.value.funcParam1)) {
|
||||
// 如果添加的函数还有入参
|
||||
resultStr = `${resultStr}(${[paramForm.value.funcParam1, paramForm.value.funcParam2]
|
||||
.filter((e) => !Number.isNaN(e))
|
||||
.join(',')})`;
|
||||
}
|
||||
}
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
function apply() {
|
||||
paramFormRef.value?.validate((errors) => {
|
||||
if (!errors) {
|
||||
let result = '';
|
||||
if (paramSettingType.value === 'mock') {
|
||||
result = applyMock();
|
||||
} else {
|
||||
result = paramForm.value.JMeterType;
|
||||
}
|
||||
setLastTenParams(paramForm.value.type);
|
||||
innerValue.value = result;
|
||||
emit('apply', result);
|
||||
cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ms-params-input-popover {
|
||||
.arco-trigger-popup-wrapper {
|
||||
.arco-popover-popup-content {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ms-params-input-setting-trigger {
|
||||
@apply bg-white;
|
||||
.ms-params-input-setting-trigger-content {
|
||||
padding: 16px;
|
||||
width: 480px;
|
||||
border-radius: var(--border-radius-medium);
|
||||
box-shadow: 0 5px 5px -3px rgb(0 0 0 / 10%), 0 8px 10px 1px rgb(0 0 0 / 6%), 0 3px 14px 2px rgb(0 0 0 / 5%);
|
||||
&::before {
|
||||
@apply absolute left-0 top-0;
|
||||
|
||||
content: '';
|
||||
z-index: -1;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
border: 1px solid var(--color-text-input-border);
|
||||
border-radius: 12px;
|
||||
transform-origin: 0 0;
|
||||
transform: scale(0.5, 0.5);
|
||||
}
|
||||
.ms-params-input-setting-trigger-content-scroll {
|
||||
.ms-scroll-bar();
|
||||
|
||||
overflow-y: auto;
|
||||
margin-right: -6px;
|
||||
max-height: 400px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ms-params-input:not(.arco-input-focus) {
|
||||
border-color: transparent;
|
||||
&:not(:hover) {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
.ms-params-input {
|
||||
.ms-params-input-mock-icon {
|
||||
@apply invisible;
|
||||
}
|
||||
&:hover,
|
||||
&.arco-input-focus {
|
||||
.ms-params-input-mock-icon {
|
||||
@apply visible cursor-pointer;
|
||||
&:hover {
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.arco-select-option) {
|
||||
@apply flex flex-1 p-10;
|
||||
}
|
||||
}
|
||||
.ms-params-input--focus {
|
||||
border-color: rgb(var(--primary-5)) !important;
|
||||
.ms-params-input-mock-icon {
|
||||
@apply visible cursor-pointer;
|
||||
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
}
|
||||
.ms-params-input-trigger {
|
||||
width: 350px;
|
||||
.arco-select-dropdown-list {
|
||||
.arco-select-option {
|
||||
@apply !h-auto;
|
||||
|
||||
padding: 2px 8px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ms-params-popover-title {
|
||||
@apply font-medium;
|
||||
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.ms-params-popover-subtitle {
|
||||
margin-bottom: 2px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
.ms-params-popover-value {
|
||||
min-width: 100px;
|
||||
max-width: 280px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,219 @@
|
|||
export default {
|
||||
'ms.paramsInput.value': 'Parameter value',
|
||||
'ms.paramsInput.placeholder': 'Starting with {at}, double-click to quickly enter',
|
||||
'ms.paramsInput.preview': 'Parameter preview',
|
||||
'ms.paramsInput.natural': 'Natural number',
|
||||
'ms.paramsInput.naturalDesc': 'Returns a random natural number',
|
||||
'ms.paramsInput.naturalRange': 'Natural numbers from 1-100',
|
||||
'ms.paramsInput.naturalRangeDesc':
|
||||
'Returns a random natural number from 1 to 100 (an integer greater than or equal to 1)',
|
||||
'ms.paramsInput.max': 'Maximum',
|
||||
'ms.paramsInput.maxNaturalPlaceholder': 'Enter an integer <=100, such as 100',
|
||||
'ms.paramsInput.min': 'Minimum',
|
||||
'ms.paramsInput.minNaturalPlaceholder': 'Enter an integer >=1, such as 1',
|
||||
'ms.paramsInput.integer': 'Integer',
|
||||
'ms.paramsInput.integerDesc': 'Returns a random integer',
|
||||
'ms.paramsInput.integerRange': 'Integer from 1-100',
|
||||
'ms.paramsInput.integerRangeDesc': 'Returns a random integer between 1 and 100',
|
||||
'ms.paramsInput.maxIntegerPlaceholder': 'Enter an integer, such as 100, the maximum value cannot exceed 100',
|
||||
'ms.paramsInput.minIntegerPlaceholder': 'Enter an integer, such as 1',
|
||||
'ms.paramsInput.float': 'Float',
|
||||
'ms.paramsInput.floatDesc':
|
||||
'Returns a random floating point number, integer 1-10, minimum value 2, maximum value 5 in the decimal part.',
|
||||
'ms.paramsInput.floatMin': 'Minimum decimal part',
|
||||
'ms.paramsInput.floatMax': 'Maximum decimal part',
|
||||
'ms.paramsInput.floatIntegerMin': 'Minimum value of integer part',
|
||||
'ms.paramsInput.floatIntegerMax': 'Maximum value of integer part',
|
||||
'ms.paramsInput.commonPlaceholder': 'Please input',
|
||||
'ms.paramsInput.commonSelectPlaceholder': 'Please select',
|
||||
'ms.paramsInput.bool': 'Boolean',
|
||||
'ms.paramsInput.boolDesc': 'Generate a random boolean value',
|
||||
'ms.paramsInput.character': 'Character pool',
|
||||
'ms.paramsInput.characterDesc': 'Return random characters from the character pool',
|
||||
'ms.paramsInput.characterLower': 'Random lowercase characters',
|
||||
'ms.paramsInput.characterLowerDesc': 'Returns a random lowercase character',
|
||||
'ms.paramsInput.characterUpper': 'Random uppercase characters',
|
||||
'ms.paramsInput.characterUpperDesc': 'Returns a random uppercase character',
|
||||
'ms.paramsInput.characterSymbol': 'Special symbol',
|
||||
'ms.paramsInput.characterSymbolDesc': 'Returns a random special symbol',
|
||||
'ms.paramsInput.characterFormat': 'Format',
|
||||
'ms.paramsInput.string': 'String',
|
||||
'ms.paramsInput.stringDesc': 'Returns a random string from the string pool with 1-10 characters.',
|
||||
'ms.paramsInput.stringMin': 'Minimum character count',
|
||||
'ms.paramsInput.stringMax': 'Maximum character count',
|
||||
'ms.paramsInput.integerArray': 'Integer array',
|
||||
'ms.paramsInput.integerArrayDesc':
|
||||
'Return an integer array, the parameters are: start: starting value, stop: end value, step: step size',
|
||||
'ms.paramsInput.integerArrayStartPlaceholder': 'Start value',
|
||||
'ms.paramsInput.integerArrayEndPlaceholder': 'End value',
|
||||
'ms.paramsInput.integerArrayStepPlaceholder': 'Step size',
|
||||
'ms.paramsInput.date': 'Date',
|
||||
'ms.paramsInput.dateDesc': 'Returns a random date string. Example: 1983-01-29',
|
||||
'ms.paramsInput.time': 'Time',
|
||||
'ms.paramsInput.timeDesc': 'Returns a random time string. Example: 20:47:37',
|
||||
'ms.paramsInput.dateTime': 'Date and time',
|
||||
'ms.paramsInput.dateTimeDesc': 'Returns a random date and time string. Example: 1977-11-17 03:50:15',
|
||||
'ms.paramsInput.nowDateTime': 'Current date and time',
|
||||
'ms.paramsInput.nowDateTimeDesc': 'Returns the current date string. Example: 2014-04-29 20:08:38',
|
||||
'ms.paramsInput.uuidDesc': 'Randomly generate a UUID. Example: eFD616Bd-e149-c98E-a041-5e12ED0C94Fd',
|
||||
'ms.paramsInput.url': 'URL',
|
||||
'ms.paramsInput.urlDesc': 'Randomly generate an http URL',
|
||||
'ms.paramsInput.urlPlaceholder': 'Please enter URL',
|
||||
'ms.paramsInput.protocol': 'Protocol',
|
||||
'ms.paramsInput.protocolDesc': 'Randomly generate a URL protocol. Example: http ftp',
|
||||
'ms.paramsInput.domain': 'Domain',
|
||||
'ms.paramsInput.domainDesc': 'Generate a domain name randomly',
|
||||
'ms.paramsInput.topDomain': 'Top level domain',
|
||||
'ms.paramsInput.topDomainDesc': 'Randomly generate a top-level domain name. Example: net',
|
||||
'ms.paramsInput.email': 'Email address',
|
||||
'ms.paramsInput.emailDesc': 'Randomly generate an email address',
|
||||
'ms.paramsInput.ip': 'IP address',
|
||||
'ms.paramsInput.ipDesc': 'Randomly generate an IP address',
|
||||
'ms.paramsInput.location': 'Area',
|
||||
'ms.paramsInput.locationDesc': 'Randomly generate a (China) region. Example: North China',
|
||||
'ms.paramsInput.province': 'Province',
|
||||
'ms.paramsInput.provinceDesc':
|
||||
'Randomly generate a (China) province (or municipality, autonomous region, special administrative region)',
|
||||
'ms.paramsInput.city': 'City',
|
||||
'ms.paramsInput.cityDesc': 'Randomly generate a (China) city',
|
||||
'ms.paramsInput.county': 'County',
|
||||
'ms.paramsInput.countyDesc': 'Randomly generate a (China) county',
|
||||
'ms.paramsInput.provinceCityCounty': 'Province/City/County',
|
||||
'ms.paramsInput.provinceCityCountyDesc':
|
||||
'Randomly generate a (China) county (with province and city). Example: Huining County, Baiyin City, Gansu Province',
|
||||
'ms.paramsInput.zip': 'Zip code',
|
||||
'ms.paramsInput.zipDesc': 'Randomly generate a zip code',
|
||||
'ms.paramsInput.idCard': 'ID number',
|
||||
'ms.paramsInput.idCardDesc': 'Randomly generate an ID number (the generated ID number may not be true and valid)',
|
||||
'ms.paramsInput.specifyIdCard': 'ID number of specified month',
|
||||
'ms.paramsInput.specifyIdCardDesc':
|
||||
'Randomly generate an ID number (birthday can be specified) (the generated ID number may not be true and valid)',
|
||||
'ms.paramsInput.birthday': 'Birth date',
|
||||
'ms.paramsInput.birthdayPlaceholder': 'Please select a date',
|
||||
'ms.paramsInput.phone': 'Phone number',
|
||||
'ms.paramsInput.phoneDesc':
|
||||
'Randomly generate a mobile phone number (the generated mobile phone number may not be real and valid)',
|
||||
'ms.paramsInput.englishName': 'English name',
|
||||
'ms.paramsInput.englishNameDesc': 'Randomly generate a common English name',
|
||||
'ms.paramsInput.englishSurname': 'English surname',
|
||||
'ms.paramsInput.englishSurnameDesc': 'Randomly generate a common English surname',
|
||||
'ms.paramsInput.englishFullName': 'English name',
|
||||
'ms.paramsInput.englishFullNameDesc': 'Randomly generate a common English name',
|
||||
'ms.paramsInput.chineseName': 'Chinese name',
|
||||
'ms.paramsInput.chineseNameDesc': 'Randomly generate a common Chinese name',
|
||||
'ms.paramsInput.chineseSurname': 'Chinese surname',
|
||||
'ms.paramsInput.chineseSurnameDesc': 'Randomly generate a common Chinese surname',
|
||||
'ms.paramsInput.chineseFullName': 'Full Chinese name',
|
||||
'ms.paramsInput.chineseFullNameDesc': 'Randomly generate a common Chinese name',
|
||||
'ms.paramsInput.color': 'color',
|
||||
'ms.paramsInput.colorDesc': "Randomly generate colors in the format '#RRGGBB'",
|
||||
'ms.paramsInput.RGBDesc': "Randomly generate colors in the format 'rgb(r, g, b)'",
|
||||
'ms.paramsInput.RGBADesc': "Randomly generate colors in the format 'rgba(r, g, b, a)'",
|
||||
'ms.paramsInput.hslDesc': "Randomly generate colors in the format 'hsl(h, s, l)'",
|
||||
'ms.paramsInput.englishText': 'Large text',
|
||||
'ms.paramsInput.englishTextDesc': 'Randomly generate a piece of text',
|
||||
'ms.paramsInput.englishSentence': 'Sentence',
|
||||
'ms.paramsInput.englishSentenceDesc':
|
||||
'Randomly generate a sentence with the first letter of the first word capitalized',
|
||||
'ms.paramsInput.englishWord': 'Word',
|
||||
'ms.paramsInput.englishWordDesc': 'Generate a word randomly',
|
||||
'ms.paramsInput.englishTitle': 'Title',
|
||||
'ms.paramsInput.englishTitleDesc': 'Randomly generate a title',
|
||||
'ms.paramsInput.chineseText': 'Large text',
|
||||
'ms.paramsInput.chineseTextDesc': 'Randomly generate a Chinese text',
|
||||
'ms.paramsInput.chineseSentence': 'Sentence',
|
||||
'ms.paramsInput.chineseSentenceDesc': 'Randomly generate a Chinese sentence',
|
||||
'ms.paramsInput.chineseWord': 'Single word',
|
||||
'ms.paramsInput.chineseWordDesc': 'Randomly generate a Chinese character',
|
||||
'ms.paramsInput.chineseTitle': 'Title',
|
||||
'ms.paramsInput.chineseTitleDesc': 'Randomly generate a Chinese title',
|
||||
'ms.paramsInput.regexp': 'Regular expressions',
|
||||
'ms.paramsInput.regexpDesc': 'Return results based on regular expression',
|
||||
'ms.paramsInput.addFunc': 'Add function',
|
||||
'ms.paramsInput.md5Desc': 'MD5 encrypt',
|
||||
'ms.paramsInput.base64Desc': 'Base64 encryption',
|
||||
'ms.paramsInput.unbase64Desc': 'Base64 decryption',
|
||||
'ms.paramsInput.substrDesc': 'Starting and ending',
|
||||
'ms.paramsInput.substrStartPlaceholder': 'Starting value',
|
||||
'ms.paramsInput.substrEndPlaceholder': 'Ending value',
|
||||
'ms.paramsInput.concatconcat': 'String to end with',
|
||||
'ms.paramsInput.concatconcatDesc': 'Ending string',
|
||||
'ms.paramsInput.lconcatDesc': 'Starting string',
|
||||
'ms.paramsInput.lconcat': 'The string to start with',
|
||||
'ms.paramsInput.sha1Desc': 'SHA1 encryption',
|
||||
'ms.paramsInput.sha224Desc': 'SHA224 encryption',
|
||||
'ms.paramsInput.sha256Desc': 'SHA256 encryption',
|
||||
'ms.paramsInput.sha384Desc': 'SHA384 encryption',
|
||||
'ms.paramsInput.sha512Desc': 'SHA512 encryption',
|
||||
'ms.paramsInput.lowerDesc': 'Convert all letters to lowercase',
|
||||
'ms.paramsInput.upperDesc': 'Convert all letters to uppercase',
|
||||
'ms.paramsInput.lengthDesc': 'Data length',
|
||||
'ms.paramsInput.numberDesc': 'Convert string to number',
|
||||
'ms.paramsInput.paramSetting': 'Parameter settings',
|
||||
'ms.paramsInput.mockType': 'Mock type',
|
||||
'ms.paramsInput.jmeterType': 'JMeter type',
|
||||
'ms.paramsInput.mockTypePlaceholder': 'Please select parameter type',
|
||||
'ms.paramsInput.personal': 'Personal information',
|
||||
'ms.paramsInput.dateOrTime': 'Date/Time',
|
||||
'ms.paramsInput.cText': 'Chinese text',
|
||||
'ms.paramsInput.eText': 'English text',
|
||||
'ms.paramsInput.web': 'Web variables',
|
||||
'ms.paramsInput.number': 'Number',
|
||||
'ms.paramsInput.base': 'Basic variables',
|
||||
'ms.paramsInput.apply': 'Apply',
|
||||
'ms.paramsInput.variable': 'Variable',
|
||||
'ms.paramsInput.randomFromMultipleVars': 'Extract elements from a set of values of variables separated by |',
|
||||
'ms.paramsInput.split': 'Split string into variables',
|
||||
'ms.paramsInput.eval': 'Compute variable expression',
|
||||
'ms.paramsInput.evalVar': 'Evaluate an expression stored in a variable',
|
||||
'ms.paramsInput.V': 'Evaluate variable name',
|
||||
'ms.paramsInput.code': 'Encode',
|
||||
'ms.paramsInput.escapeHtml': 'Encode a string using HTML encoding',
|
||||
'ms.paramsInput.escapeXml': 'Encode a string using XML encoding',
|
||||
'ms.paramsInput.unescape': 'Handle strings containing Java escape characters (such as \\n and \\t)',
|
||||
'ms.paramsInput.unescapeHtml': 'Decode HTML encoded string',
|
||||
'ms.paramsInput.urldecode': 'Decode application/x-www-form-urlencoded string',
|
||||
'ms.paramsInput.urlencode': 'Encode string to application/x-www-form-urlencoded string',
|
||||
'ms.paramsInput.script': 'Script',
|
||||
'ms.paramsInput.groovy': 'Run the Apache Groovy script',
|
||||
'ms.paramsInput.BeanShell': 'Run BeanShell script',
|
||||
'ms.paramsInput.javaScript': 'Processing JavaScript (Nashorn)',
|
||||
'ms.paramsInput.jexl2': 'Evaluate Commons Jexl2 expressions',
|
||||
'ms.paramsInput.jexl3': 'Evaluate Commons Jexl3 expressions',
|
||||
'ms.paramsInput.jmeterTime': 'Returns the current time in various formats',
|
||||
'ms.paramsInput.timeShift':
|
||||
'Returns a date in various formats with a specified amount of seconds/minutes/hours/days added',
|
||||
'ms.paramsInput.dateTimeConvert': 'Convert date or time from source format to target format',
|
||||
'ms.paramsInput.RandomDate': 'Generate random dates within a specific date range',
|
||||
'ms.paramsInput.property': 'Property',
|
||||
'ms.paramsInput.isPropDefined': 'Test whether the attribute exists',
|
||||
'ms.paramsInput.readProperty': 'Read properties',
|
||||
'ms.paramsInput.P': 'Read attributes (shorthand method)',
|
||||
'ms.paramsInput.setProperty': 'Set JMeter properties',
|
||||
'ms.paramsInput.isVarDefined': 'Test whether the variable exists',
|
||||
'ms.paramsInput.counter': 'Generate an increasing number',
|
||||
'ms.paramsInput.intSum': 'Add an int number',
|
||||
'ms.paramsInput.longSum': 'Add long numbers',
|
||||
'ms.paramsInput.Random': 'Generate a random number',
|
||||
'ms.paramsInput.StringFromFile': 'Read a line from a file',
|
||||
'ms.paramsInput.file': 'File',
|
||||
'ms.paramsInput.FileToString': 'Read the entire file',
|
||||
'ms.paramsInput.CSVRead': 'Read from CSV delimited file',
|
||||
'ms.paramsInput.XPath': 'Read from a file using XPath expressions',
|
||||
'ms.paramsInput.StringToFile': 'Write string to file',
|
||||
'ms.paramsInput.info': 'Info',
|
||||
'ms.paramsInput.digest': 'Generate digest (SHA-1, SHA-256, MD5...)',
|
||||
'ms.paramsInput.threadNum': 'Get thread group number',
|
||||
'ms.paramsInput.threadGroupName': 'Get thread group name',
|
||||
'ms.paramsInput.samplerName': 'Get sampler name (label)',
|
||||
'ms.paramsInput.machineIP': 'Get the local IP address',
|
||||
'ms.paramsInput.machineName': 'Get local machine name',
|
||||
'ms.paramsInput.TestPlanName': 'Returns the name of the current test plan',
|
||||
'ms.paramsInput.log': 'Log (or display) a message (and return a value)',
|
||||
'ms.paramsInput.logn': 'Log (or display) a message (empty return value)',
|
||||
'ms.paramsInput.RandomString': 'Generate a random string',
|
||||
'ms.paramsInput.UUID': 'Generate random type 4 UUID',
|
||||
'ms.paramsInput.char': 'Generate Unicode character values from a list of numbers',
|
||||
'ms.paramsInput.changeCase': 'Change case according to different modes',
|
||||
'ms.paramsInput.regexFunction': 'Parse previous responses using regular expressions',
|
||||
};
|
|
@ -0,0 +1,210 @@
|
|||
export default {
|
||||
'ms.paramsInput.value': '参数值',
|
||||
'ms.paramsInput.placeholder': '以{at}开始,双击可快速输入',
|
||||
'ms.paramsInput.preview': '参数预览',
|
||||
'ms.paramsInput.natural': '自然数',
|
||||
'ms.paramsInput.naturalDesc': '返回一个随机的自然数',
|
||||
'ms.paramsInput.naturalRange': '1-100自然数',
|
||||
'ms.paramsInput.naturalRangeDesc': '返回一个随机的1-100的自然数(大于等于 1 的整数)',
|
||||
'ms.paramsInput.max': '最大值',
|
||||
'ms.paramsInput.maxNaturalPlaceholder': '输入 <=100 的整数,如 100',
|
||||
'ms.paramsInput.min': '最小值',
|
||||
'ms.paramsInput.minNaturalPlaceholder': '输入 >=1的整数,如 1',
|
||||
'ms.paramsInput.integer': '整数',
|
||||
'ms.paramsInput.integerDesc': '返回一个随机的整数',
|
||||
'ms.paramsInput.integerRange': '1-100整数',
|
||||
'ms.paramsInput.integerRangeDesc': '返回随机的1-100的整数',
|
||||
'ms.paramsInput.maxIntegerPlaceholder': '输入整数,如 100,最大不超过 100',
|
||||
'ms.paramsInput.minIntegerPlaceholder': '输入整数,如 1',
|
||||
'ms.paramsInput.float': '浮点数',
|
||||
'ms.paramsInput.floatDesc': '返回一个随机的浮点数,整数1-10,小数部分位数的最小值2,最大值5',
|
||||
'ms.paramsInput.floatMin': '小数部分最小值',
|
||||
'ms.paramsInput.floatMax': '小数部分最大值',
|
||||
'ms.paramsInput.floatIntegerMin': '整数部分最小值',
|
||||
'ms.paramsInput.floatIntegerMax': '整数部分最大值',
|
||||
'ms.paramsInput.commonPlaceholder': '请输入',
|
||||
'ms.paramsInput.commonSelectPlaceholder': '请选择',
|
||||
'ms.paramsInput.bool': '布尔值',
|
||||
'ms.paramsInput.boolDesc': '随机生成一个布尔值',
|
||||
'ms.paramsInput.character': '字符池',
|
||||
'ms.paramsInput.characterDesc': '从字符串池返回随机的字符',
|
||||
'ms.paramsInput.characterLower': '随机小写字符',
|
||||
'ms.paramsInput.characterLowerDesc': '返回一个随机的小写字符',
|
||||
'ms.paramsInput.characterUpper': '随机大写字符',
|
||||
'ms.paramsInput.characterUpperDesc': '返回一个随机的大写字符',
|
||||
'ms.paramsInput.characterSymbol': '特殊符号',
|
||||
'ms.paramsInput.characterSymbolDesc': '返回一个随机的特殊符号',
|
||||
'ms.paramsInput.characterFormat': '格式',
|
||||
'ms.paramsInput.string': '字符串',
|
||||
'ms.paramsInput.stringDesc': '从字符串池返回一个随机字符串,字符数1-10',
|
||||
'ms.paramsInput.stringMin': '最小字符数',
|
||||
'ms.paramsInput.stringMax': '最大字符数',
|
||||
'ms.paramsInput.integerArray': '整型数组',
|
||||
'ms.paramsInput.integerArrayDesc': '返回一个整型数组,参数分别:start:起始值,stop:结束值,step:步长',
|
||||
'ms.paramsInput.integerArrayStartPlaceholder': '起始值',
|
||||
'ms.paramsInput.integerArrayEndPlaceholder': '结束值',
|
||||
'ms.paramsInput.integerArrayStepPlaceholder': '步长',
|
||||
'ms.paramsInput.date': '日期',
|
||||
'ms.paramsInput.dateDesc': '返回一个随机的日期字符串。例:1983-01-29',
|
||||
'ms.paramsInput.time': '时间',
|
||||
'ms.paramsInput.timeDesc': '返回一个随机的时间字符串。 例:20:47:37',
|
||||
'ms.paramsInput.dateTime': '日期时间',
|
||||
'ms.paramsInput.dateTimeDesc': '返回一个随机的日期和时间字符串。例:1977-11-17 03:50:15',
|
||||
'ms.paramsInput.nowDateTime': '当前日期时间',
|
||||
'ms.paramsInput.nowDateTimeDesc': '返回当前日期字符串。例:2014-04-29 20:08:38',
|
||||
'ms.paramsInput.uuidDesc': '随机生成一个 UUID。例:eFD616Bd-e149-c98E-a041-5e12ED0C94Fd',
|
||||
'ms.paramsInput.url': '网址',
|
||||
'ms.paramsInput.urlDesc': '随机生成一个http URL',
|
||||
'ms.paramsInput.urlPlaceholder': '请输入内容',
|
||||
'ms.paramsInput.protocol': '协议',
|
||||
'ms.paramsInput.protocolDesc': '随机生成一个 URL 协议。例:http ftp',
|
||||
'ms.paramsInput.domain': '域名',
|
||||
'ms.paramsInput.domainDesc': '随机生成一个域名',
|
||||
'ms.paramsInput.topDomain': '顶级域名',
|
||||
'ms.paramsInput.topDomainDesc': '随机生成一个顶级域名。例:net',
|
||||
'ms.paramsInput.email': '邮件地址',
|
||||
'ms.paramsInput.emailDesc': '随机生成一个邮件地址',
|
||||
'ms.paramsInput.ip': 'IP地址',
|
||||
'ms.paramsInput.ipDesc': '随机生成一个IP地址',
|
||||
'ms.paramsInput.location': '区域',
|
||||
'ms.paramsInput.locationDesc': '随机生成一个(中国)大区。例:华北',
|
||||
'ms.paramsInput.province': '省份',
|
||||
'ms.paramsInput.provinceDesc': '随机生成一个(中国)省(或直辖市、自治区、特别行政区)',
|
||||
'ms.paramsInput.city': '城市',
|
||||
'ms.paramsInput.cityDesc': '随机生成一个(中国)市',
|
||||
'ms.paramsInput.county': '地区',
|
||||
'ms.paramsInput.countyDesc': '随机生成一个(中国)县',
|
||||
'ms.paramsInput.provinceCityCounty': '省份/市/县',
|
||||
'ms.paramsInput.provinceCityCountyDesc': '随机生成一个(中国)县(带省市)。例:甘肃省 白银市 会宁县',
|
||||
'ms.paramsInput.zip': '邮编',
|
||||
'ms.paramsInput.zipDesc': '随机生成一个邮政编码',
|
||||
'ms.paramsInput.idCard': '身份证号',
|
||||
'ms.paramsInput.idCardDesc': '随机生成一个身份证号(生成的身份证号码并不一定是真实有效的)',
|
||||
'ms.paramsInput.specifyIdCard': '指定月份身份证号',
|
||||
'ms.paramsInput.specifyIdCardDesc': '随机生成一个身份证号(可以指定生日)(生成的身份证号码并不一定是真实有效的)',
|
||||
'ms.paramsInput.birthday': '出生日期',
|
||||
'ms.paramsInput.birthdayPlaceholder': '请选择日期',
|
||||
'ms.paramsInput.phone': '手机号',
|
||||
'ms.paramsInput.phoneDesc': '随机生成一个手机号(生成的手机号码并不一定是真实有效的)',
|
||||
'ms.paramsInput.englishName': '英文名',
|
||||
'ms.paramsInput.englishNameDesc': '随机生成一个常见的英文名',
|
||||
'ms.paramsInput.englishSurname': '英文姓',
|
||||
'ms.paramsInput.englishSurnameDesc': '随机生成一个常见的英文姓',
|
||||
'ms.paramsInput.englishFullName': '英文姓名',
|
||||
'ms.paramsInput.englishFullNameDesc': '随机生成一个常见的英文姓名',
|
||||
'ms.paramsInput.chineseName': '中文名',
|
||||
'ms.paramsInput.chineseNameDesc': '随机生成一个常见的中文名',
|
||||
'ms.paramsInput.chineseSurname': '中文姓',
|
||||
'ms.paramsInput.chineseSurnameDesc': '随机生成一个常见的中文姓',
|
||||
'ms.paramsInput.chineseFullName': '中文姓名',
|
||||
'ms.paramsInput.chineseFullNameDesc': '随机生成一个常见的中文姓名',
|
||||
'ms.paramsInput.color': '颜色',
|
||||
'ms.paramsInput.colorDesc': "随机生成颜色,格式为 '#RRGGBB'",
|
||||
'ms.paramsInput.RGBDesc': "随机生成颜色,格式为 'rgb(r, g, b)'",
|
||||
'ms.paramsInput.RGBADesc': "随机生成颜色,格式为 'rgba(r, g, b, a)'",
|
||||
'ms.paramsInput.hslDesc': "随机生成颜色,格式为 'hsl(h, s, l)'",
|
||||
'ms.paramsInput.englishText': '大段文本',
|
||||
'ms.paramsInput.englishTextDesc': '随机生成一段文本',
|
||||
'ms.paramsInput.englishSentence': '句子',
|
||||
'ms.paramsInput.englishSentenceDesc': '随机生成一个句子,第一个单词的首字母大写',
|
||||
'ms.paramsInput.englishWord': '单词',
|
||||
'ms.paramsInput.englishWordDesc': '随机生成一个单词',
|
||||
'ms.paramsInput.englishTitle': '标题',
|
||||
'ms.paramsInput.englishTitleDesc': '随机生成一个标题',
|
||||
'ms.paramsInput.chineseText': '大段文本',
|
||||
'ms.paramsInput.chineseTextDesc': '随机生成一段中文文本',
|
||||
'ms.paramsInput.chineseSentence': '句子',
|
||||
'ms.paramsInput.chineseSentenceDesc': '随机生成一个中文句子',
|
||||
'ms.paramsInput.chineseWord': '单字',
|
||||
'ms.paramsInput.chineseWordDesc': '随机生成一个汉字',
|
||||
'ms.paramsInput.chineseTitle': '标题',
|
||||
'ms.paramsInput.chineseTitleDesc': '随机生成一个中文标题',
|
||||
'ms.paramsInput.regexp': '正则表达式',
|
||||
'ms.paramsInput.regexpDesc': '根据正则表达式返回结果',
|
||||
'ms.paramsInput.addFunc': '添加函数',
|
||||
'ms.paramsInput.md5Desc': 'md5 加密',
|
||||
'ms.paramsInput.base64Desc': 'base64 加密',
|
||||
'ms.paramsInput.unbase64Desc': 'base64 解密',
|
||||
'ms.paramsInput.substrDesc': '起止',
|
||||
'ms.paramsInput.substrStartPlaceholder': '起始值',
|
||||
'ms.paramsInput.substrEndPlaceholder': '结束值',
|
||||
'ms.paramsInput.concatconcat': '要作为结尾的字符串',
|
||||
'ms.paramsInput.concatconcatDesc': '结尾字符串',
|
||||
'ms.paramsInput.lconcatDesc': '开头字符串',
|
||||
'ms.paramsInput.lconcat': '要作为开头的字符串',
|
||||
'ms.paramsInput.sha1Desc': 'sha1 加密',
|
||||
'ms.paramsInput.sha224Desc': 'sha224 加密',
|
||||
'ms.paramsInput.sha256Desc': 'sha256 加密',
|
||||
'ms.paramsInput.sha384Desc': 'sha384 加密',
|
||||
'ms.paramsInput.sha512Desc': 'sha512 加密',
|
||||
'ms.paramsInput.lowerDesc': '所有字母转为小写',
|
||||
'ms.paramsInput.upperDesc': '所有字母转为大写',
|
||||
'ms.paramsInput.lengthDesc': '数据长度',
|
||||
'ms.paramsInput.numberDesc': '字符串转数字',
|
||||
'ms.paramsInput.paramSetting': '参数设置',
|
||||
'ms.paramsInput.mockType': 'mock类型',
|
||||
'ms.paramsInput.jmeterType': 'JMeter类型',
|
||||
'ms.paramsInput.mockTypePlaceholder': '请选择参数类型',
|
||||
'ms.paramsInput.personal': '个人信息',
|
||||
'ms.paramsInput.dateOrTime': '日期/时间',
|
||||
'ms.paramsInput.cText': '中文文本',
|
||||
'ms.paramsInput.eText': '英文文本',
|
||||
'ms.paramsInput.web': 'web变量',
|
||||
'ms.paramsInput.number': '数字',
|
||||
'ms.paramsInput.base': '基础变量',
|
||||
'ms.paramsInput.apply': '应用',
|
||||
'ms.paramsInput.variable': '变量',
|
||||
'ms.paramsInput.randomFromMultipleVars': '从由|分隔的一组变量的值中提取元素',
|
||||
'ms.paramsInput.split': '将字符串拆分为变量',
|
||||
'ms.paramsInput.eval': '计算变量表达式',
|
||||
'ms.paramsInput.evalVar': '计算存储在变量中的表达式',
|
||||
'ms.paramsInput.V': '评估变量名',
|
||||
'ms.paramsInput.code': '编码',
|
||||
'ms.paramsInput.escapeHtml': '使用 HTML 编码对字符串进行编码',
|
||||
'ms.paramsInput.escapeXml': '使用 XML 编码对字符串进行编码',
|
||||
'ms.paramsInput.unescape': '处理包含 Java 转义符的字符串(例如 \\n 和 \\t)',
|
||||
'ms.paramsInput.unescapeHtml': '解码 HTML 编码的字符串',
|
||||
'ms.paramsInput.urldecode': '解码 application/x-www-form-urlencoded 字符串',
|
||||
'ms.paramsInput.urlencode': '将字符串编码为 application/x-www-form-urlencoded 字符串',
|
||||
'ms.paramsInput.script': '脚本',
|
||||
'ms.paramsInput.groovy': '运行 Apache Groovy 脚本',
|
||||
'ms.paramsInput.BeanShell': '运行 BeanShell 脚本',
|
||||
'ms.paramsInput.javaScript': '处理 JavaScript (Nashorn)',
|
||||
'ms.paramsInput.jexl2': '评估 Commons Jexl2 表达式',
|
||||
'ms.paramsInput.jexl3': '评估 Commons Jexl3 表达式',
|
||||
'ms.paramsInput.jmeterTime': '以各种格式返回当前时间',
|
||||
'ms.paramsInput.timeShift': '返回各种格式的日期,并添加指定的秒/分钟/小时/天量',
|
||||
'ms.paramsInput.dateTimeConvert': '将日期或时间从源格式转换为目标格式',
|
||||
'ms.paramsInput.RandomDate': '生成特定日期范围内的随机日期',
|
||||
'ms.paramsInput.property': '属性',
|
||||
'ms.paramsInput.isPropDefined': '测试属性是否存在',
|
||||
'ms.paramsInput.readProperty': '读取属性',
|
||||
'ms.paramsInput.P': '读取属性(简写方法)',
|
||||
'ms.paramsInput.setProperty': '设置 JMeter 属性',
|
||||
'ms.paramsInput.isVarDefined': '测试变量是否存在',
|
||||
'ms.paramsInput.counter': '生成一个递增的数字',
|
||||
'ms.paramsInput.intSum': '添加 int 数字',
|
||||
'ms.paramsInput.longSum': '添加长数字',
|
||||
'ms.paramsInput.Random': '生成一个随机数',
|
||||
'ms.paramsInput.StringFromFile': '从文件中读取一行',
|
||||
'ms.paramsInput.file': '文件',
|
||||
'ms.paramsInput.FileToString': '读取整个文件',
|
||||
'ms.paramsInput.CSVRead': '从 CSV 分隔文件中读取',
|
||||
'ms.paramsInput.XPath': '使用 XPath 表达式从文件中读取',
|
||||
'ms.paramsInput.StringToFile': '将字符串写入文件',
|
||||
'ms.paramsInput.info': '信息',
|
||||
'ms.paramsInput.digest': '生成摘要(SHA-1、SHA-256、MD5...)',
|
||||
'ms.paramsInput.threadNum': '获取线程组号',
|
||||
'ms.paramsInput.threadGroupName': '获取线程组名称',
|
||||
'ms.paramsInput.samplerName': '获取采样器名称(标签)',
|
||||
'ms.paramsInput.machineIP': '获取本机IP地址',
|
||||
'ms.paramsInput.machineName': '获取本地机器名',
|
||||
'ms.paramsInput.TestPlanName': '返回当前测试计划的名称',
|
||||
'ms.paramsInput.log': '记录(或显示)一条消息(并返回值)',
|
||||
'ms.paramsInput.logn': '记录(或显示)一条消息(空返回值)',
|
||||
'ms.paramsInput.RandomString': '生成一个随机字符串',
|
||||
'ms.paramsInput.UUID': '生成随机类型 4 UUID',
|
||||
'ms.paramsInput.char': '从数字列表生成 Unicode 字符值',
|
||||
'ms.paramsInput.changeCase': '根据不同模式更改大小写',
|
||||
'ms.paramsInput.regexFunction': '使用正则表达式解析先前的响应',
|
||||
};
|
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<a-form-item v-for="(group, index) of props.inputGroup" :key="index" :label="t(group.label || '')" class="mb-[16px]">
|
||||
<a-select
|
||||
v-if="group.type === 'select'"
|
||||
v-model:model-value="innerForm[`${paramKey}${index + 1}`]"
|
||||
:options="group.options"
|
||||
:placeholder="t(group.placeholder || '')"
|
||||
/>
|
||||
<a-input
|
||||
v-else-if="group.type === 'input'"
|
||||
v-model:model-value="innerForm[`${paramKey}${index + 1}`]"
|
||||
:placeholder="t(group.placeholder || '')"
|
||||
/>
|
||||
<a-radio-group
|
||||
v-else-if="group.type === 'radio'"
|
||||
v-model:model-value="innerForm[`${paramKey}${index + 1}`]"
|
||||
type="button"
|
||||
>
|
||||
<a-radio v-for="(option, i) of group.options || []" :key="`option${i}`" :value="option.value">
|
||||
{{ t(option.label) }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
<a-date-picker
|
||||
v-else-if="group.type === 'date'"
|
||||
v-model:model-value="innerForm[`${paramKey}${index + 1}`]"
|
||||
:placeholder="t(group.placeholder || '')"
|
||||
/>
|
||||
<a-input-number
|
||||
v-else-if="group.type === 'number'"
|
||||
v-model:model-value="innerForm[`${paramKey}${index + 1}`]"
|
||||
:placeholder="t(group.placeholder || '')"
|
||||
/>
|
||||
<a-input
|
||||
v-else-if="group.type === 'inputAppendSelect'"
|
||||
v-model:model-value="innerForm[`${paramKey}${index + 1}`]"
|
||||
:placeholder="t(group.placeholder || '')"
|
||||
class="ms-params-input-inputAppendSelect"
|
||||
>
|
||||
<template #prepend>
|
||||
<a-select
|
||||
v-model:model-value="innerForm[`${paramKey}${index + 1}`]"
|
||||
:options="group.options"
|
||||
class="select-input-prepend !w-[70px]"
|
||||
/>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import type { MockParamInputGroupItem } from './types';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
paramForm: Record<string, any>;
|
||||
type?: 'var' | 'func';
|
||||
inputGroup: MockParamInputGroupItem[];
|
||||
}>(),
|
||||
{
|
||||
type: 'var',
|
||||
}
|
||||
);
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:paramForm', value: Record<string, any>): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const innerForm = useVModel(props, 'paramForm', emit);
|
||||
|
||||
const paramKey = computed(() => {
|
||||
return props.type === 'var' ? 'param' : 'funcParam';
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.inputGroup,
|
||||
(arr) => {
|
||||
arr.forEach((e, i) => {
|
||||
if (innerForm.value[`${paramKey.value}${i + 1}`] === '') {
|
||||
innerForm.value[`${paramKey.value}${i + 1}`] = e.value;
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ms-input-group--prepend();
|
||||
</style>
|
|
@ -0,0 +1,24 @@
|
|||
// mock 参数配置项输入组选项
|
||||
export interface MockParamInputGroupOptionItem {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
// mock 参数配置项输入组项类型
|
||||
export type MockParamInputGroupItemType = 'select' | 'input' | 'number' | 'date' | 'radio' | 'inputAppendSelect';
|
||||
// mock 参数配置项输入组
|
||||
export interface MockParamInputGroupItem {
|
||||
type: MockParamInputGroupItemType;
|
||||
label?: string;
|
||||
value?: string | number;
|
||||
placeholder?: string;
|
||||
options?: MockParamInputGroupOptionItem[];
|
||||
inputValue?: string;
|
||||
selectValue?: string;
|
||||
}
|
||||
// mock 参数配置项
|
||||
export interface MockParamItem {
|
||||
label: string;
|
||||
value: string;
|
||||
desc?: string;
|
||||
inputGroup?: MockParamInputGroupItem[];
|
||||
}
|
|
@ -374,7 +374,7 @@
|
|||
|
||||
<style lang="less">
|
||||
.ms-tree-container {
|
||||
.ms-container--shadow();
|
||||
.ms-container--shadow-y();
|
||||
.ms-tree {
|
||||
.ms-scroll-bar();
|
||||
.arco-tree-node {
|
||||
|
@ -486,5 +486,8 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.arco-tree-node-disabled-selectable {
|
||||
@apply !cursor-default;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<template>
|
||||
<div
|
||||
:class="`ms-button ms-button-${props.type} ms-button--${props.status} ${
|
||||
props.disabled || props.loading ? `ms-button--${props.status}--disabled` : ''
|
||||
props.disabled || props.loading ? `ms-button--disabled ms-button--${props.status}--disabled` : ''
|
||||
}`"
|
||||
@click="clickHandler"
|
||||
:disabled="props.disabled"
|
||||
@click.stop="clickHandler"
|
||||
>
|
||||
<slot></slot>
|
||||
<icon-loading v-if="props.loading" />
|
||||
|
@ -92,4 +93,10 @@
|
|||
background-color: rgb(var(--danger-1));
|
||||
}
|
||||
}
|
||||
.ms-button--disabled {
|
||||
@apply cursor-not-allowed;
|
||||
}
|
||||
.ms-button--secondary--disabled {
|
||||
color: var(--color-text-brand);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<slot name="title">
|
||||
<span class="font-medium">{{ title }}</span>
|
||||
</slot>
|
||||
<div class="w-[96px] cursor-pointer text-right !text-[var(--color-text-4)]" @click="toggle">
|
||||
<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') }}
|
||||
|
|
|
@ -83,4 +83,8 @@ export const editorProps = {
|
|||
title: {
|
||||
type: String as PropType<string>,
|
||||
},
|
||||
showFullScreen: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
<div class="handle" @mousedown="startResize">
|
||||
<div v-if="!props.disabledWidthDrag" class="handle" @mousedown="startResize">
|
||||
<icon-drag-dot-vertical class="absolute left-[-3px] top-[50%] w-[14px]" size="14" />
|
||||
</div>
|
||||
<a-scrollbar class="h-full overflow-y-auto">
|
||||
|
@ -73,7 +73,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, defineAsyncComponent, ref, watch } from 'vue';
|
||||
import { defineAsyncComponent, ref, watch } from 'vue';
|
||||
|
||||
import type { Description } from '@/components/pure/ms-description/index.vue';
|
||||
|
||||
|
@ -100,6 +100,7 @@
|
|||
width: number;
|
||||
noContentPadding?: boolean; // 是否没有内容内边距
|
||||
popupContainer?: string;
|
||||
disabledWidthDrag?: boolean; // 是否禁止拖拽宽度
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<DrawerProps>(), {
|
||||
|
@ -108,6 +109,7 @@
|
|||
showSkeleton: false,
|
||||
showContinue: false,
|
||||
popupContainer: 'body',
|
||||
disabledWidthDrag: false,
|
||||
});
|
||||
const emit = defineEmits(['update:visible', 'confirm', 'cancel', 'continue']);
|
||||
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
<template>
|
||||
<div class="tab-container">
|
||||
<a-tooltip v-if="!isNotOverflow" :content="t('ms.editableTab.arrivedLeft')" :disabled="!arrivedState.left">
|
||||
<MsButton
|
||||
type="icon"
|
||||
status="secondary"
|
||||
class="tab-button !mr-[4px]"
|
||||
:disabled="arrivedState.left"
|
||||
@click="scrollTabs('left')"
|
||||
>
|
||||
<MsIcon type="icon-icon_left_outlined" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<div ref="tabNav" class="tab-nav">
|
||||
<div
|
||||
v-for="tab in props.tabs"
|
||||
:key="tab.id"
|
||||
class="tab"
|
||||
:class="{ active: innerActiveTab === tab.id }"
|
||||
@click="handleTabClick(tab)"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<slot name="label" :tab="tab">{{ tab.label }}</slot>
|
||||
<MsButton
|
||||
v-if="tab.closable"
|
||||
type="icon"
|
||||
status="secondary"
|
||||
class="tab-close-button"
|
||||
@click="() => close(tab)"
|
||||
>
|
||||
<MsIcon type="icon-icon_close_outlined" size="12" />
|
||||
</MsButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-tooltip v-if="!isNotOverflow" :content="t('ms.editableTab.arrivedRight')" :disabled="!arrivedState.right">
|
||||
<MsButton
|
||||
type="icon"
|
||||
status="secondary"
|
||||
class="tab-button !mr-[8px]"
|
||||
:disabled="arrivedState.right"
|
||||
@click="scrollTabs('right')"
|
||||
>
|
||||
<MsIcon type="icon-icon_right_outlined" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<MsButton type="icon" status="secondary" class="tab-button !mr-[4px]" @click="addTab">
|
||||
<MsIcon type="icon-icon_add_outlined" />
|
||||
</MsButton>
|
||||
<MsMoreAction v-if="props.moreActionList" :list="props.moreActionList">
|
||||
<MsButton type="icon" status="secondary" class="tab-button">
|
||||
<MsIcon type="icon-icon_more_outlined" />
|
||||
</MsButton>
|
||||
</MsMoreAction>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onMounted, ref, watch } from 'vue';
|
||||
import { useScroll, useVModel } from '@vueuse/core';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import type { TabItem } from './types';
|
||||
|
||||
const props = defineProps<{
|
||||
tabs: TabItem[];
|
||||
activeTab: string | number;
|
||||
moreActionList?: ActionsItem[];
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:activeTab', activeTab: string | number): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'close', item: TabItem): void;
|
||||
(e: 'click', item: TabItem): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const innerActiveTab = useVModel(props, 'activeTab', emit);
|
||||
const tabNav = ref<HTMLElement | null>(null);
|
||||
const { arrivedState } = useScroll(tabNav);
|
||||
const isNotOverflow = computed(() => arrivedState.left && arrivedState.right); // 内容是否溢出,用于判断左右滑动按钮是否展示
|
||||
|
||||
const scrollTabs = (direction: 'left' | 'right') => {
|
||||
if (tabNav.value) {
|
||||
const tabNavWidth = tabNav.value?.clientWidth || 0;
|
||||
const tabNavScrollWidth = tabNav.value?.scrollWidth || 0;
|
||||
|
||||
if (direction === 'left') {
|
||||
tabNav.value.scrollTo({
|
||||
left: tabNav.value.scrollLeft - tabNavWidth - 80,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
} else if (tabNavScrollWidth > tabNav.value.scrollLeft + tabNavWidth - 80) {
|
||||
tabNav.value.scrollTo({
|
||||
left: tabNav.value.scrollLeft + tabNavWidth,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const scrollToActiveTab = () => {
|
||||
const activeTabDom = tabNav.value?.querySelector('.tab.active');
|
||||
if (activeTabDom) {
|
||||
const tabRect = activeTabDom.getBoundingClientRect();
|
||||
const navRect = tabNav.value?.getBoundingClientRect();
|
||||
if (tabRect.left < navRect!.left) {
|
||||
scrollTabs('left');
|
||||
} else if (tabRect.right > navRect!.right) {
|
||||
scrollTabs('right');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(props.tabs, () => {
|
||||
nextTick(() => {
|
||||
scrollToActiveTab();
|
||||
});
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
scrollToActiveTab();
|
||||
});
|
||||
resizeObserver.observe(tabNav.value as Element);
|
||||
});
|
||||
|
||||
function addTab() {
|
||||
emit('add');
|
||||
}
|
||||
|
||||
function close(item: TabItem) {
|
||||
emit('close', item);
|
||||
}
|
||||
|
||||
function handleTabClick(item: TabItem) {
|
||||
innerActiveTab.value = item.id;
|
||||
nextTick(() => {
|
||||
tabNav.value?.querySelector('.tab.active')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tab-container {
|
||||
@apply flex items-center;
|
||||
|
||||
height: 32px;
|
||||
.tab-nav {
|
||||
@apply relative flex overflow-x-auto whitespace-nowrap;
|
||||
&::-webkit-scrollbar {
|
||||
width: 0; /* 宽度为0,隐藏垂直滚动条 */
|
||||
height: 0; /* 高度为0,隐藏水平滚动条 */
|
||||
}
|
||||
.tab {
|
||||
@apply flex cursor-pointer items-center;
|
||||
|
||||
margin-right: 4px;
|
||||
padding: 5px 8px;
|
||||
border-radius: var(--border-radius-small);
|
||||
background-color: var(--color-text-n9);
|
||||
gap: 8px;
|
||||
&.active,
|
||||
&:hover {
|
||||
color: rgb(var(--primary-5));
|
||||
background-color: rgb(var(--primary-1));
|
||||
.tab-close-button {
|
||||
@apply visible;
|
||||
}
|
||||
}
|
||||
.tab-close-button {
|
||||
@apply invisible !rounded-full;
|
||||
|
||||
margin-left: 4px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tab-button {
|
||||
padding: 8px;
|
||||
&:not([disabled='true']) {
|
||||
padding: 8px;
|
||||
color: var(--color-text-4);
|
||||
&:hover {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,4 @@
|
|||
export default {
|
||||
'ms.editableTab.arrivedLeft': 'Already reached the far left~',
|
||||
'ms.editableTab.arrivedRight': 'Already reached the far right~',
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
export default {
|
||||
'ms.editableTab.arrivedLeft': '到最左侧啦~',
|
||||
'ms.editableTab.arrivedRight': '到最右侧啦~',
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
export interface TabItem {
|
||||
id: string | number;
|
||||
label: string;
|
||||
closable?: boolean;
|
||||
[key: string]: any;
|
||||
}
|
|
@ -169,7 +169,7 @@
|
|||
|
||||
<style lang="less" scoped>
|
||||
.ms-list {
|
||||
.ms-container--shadow();
|
||||
.ms-container--shadow-y();
|
||||
:deep(.arco-list) {
|
||||
@apply rounded-none;
|
||||
.ms-list-item {
|
||||
|
|
|
@ -3,13 +3,26 @@
|
|||
v-model:size="innerSize"
|
||||
:min="props.min"
|
||||
:max="props.max"
|
||||
:class="['h-full', isExpanded ? '' : 'expanded-panel', isExpandAnimating ? 'animating' : '']"
|
||||
:class="[
|
||||
'h-full',
|
||||
isExpanded ? '' : 'expanded-panel',
|
||||
isExpandAnimating ? 'animating' : '',
|
||||
props.direction === 'vertical' ? 'ms-split-box--vertical' : '',
|
||||
]"
|
||||
:direction="props.direction"
|
||||
:disabled="props.disabled || !isExpanded"
|
||||
>
|
||||
<template #first>
|
||||
<div v-if="props.direction === 'horizontal'" class="ms-split-box ms-split-box--left">
|
||||
<div v-if="props.expandDirection === 'right'" class="absolute right-0 flex h-full w-[16px] items-center">
|
||||
<div class="expand-icon expand-icon--left" @click="changeLeftExpand">
|
||||
<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)]' : ''
|
||||
}`"
|
||||
>
|
||||
<div
|
||||
v-if="props.direction === 'horizontal' && props.expandDirection === 'right' && !props.disabled"
|
||||
class="absolute right-0 h-full w-[16px]"
|
||||
>
|
||||
<div class="expand-icon expand-icon--left" @click="() => changeExpand()">
|
||||
<MsIcon
|
||||
:type="isExpanded ? 'icon-icon_up-left_outlined' : 'icon-icon_down-right_outlined'"
|
||||
class="text-[var(--color-text-brand)]"
|
||||
|
@ -17,41 +30,30 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="left"></slot>
|
||||
<slot name="first"></slot>
|
||||
</div>
|
||||
<div v-else class="ms-split-box ms-split-box--top">
|
||||
<slot name="top"></slot>
|
||||
</template>
|
||||
<template #resize-trigger>
|
||||
<div :class="props.direction === 'horizontal' ? 'horizontal-expand-line' : 'vertical-expand-line'">
|
||||
<div v-if="isExpanded" class="expand-color-line"></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #second>
|
||||
<template v-if="props.direction === 'horizontal'">
|
||||
<div v-if="props.expandDirection === 'left'" class="absolute flex h-full w-[16px] items-center">
|
||||
<div class="expand-icon" @click="changeLeftExpand">
|
||||
<MsIcon
|
||||
:type="isExpanded ? 'icon-icon_up-left_outlined' : 'icon-icon_down-right_outlined'"
|
||||
class="text-[var(--color-text-brand)]"
|
||||
size="12"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.direction === 'horizontal' && props.expandDirection === 'left' && !props.disabled"
|
||||
class="absolute h-full w-[16px]"
|
||||
>
|
||||
<div class="expand-icon" @click="() => changeExpand()">
|
||||
<MsIcon
|
||||
:type="isExpanded ? 'icon-icon_up-left_outlined' : 'icon-icon_down-right_outlined'"
|
||||
class="text-[var(--color-text-brand)]"
|
||||
size="12"
|
||||
/>
|
||||
</div>
|
||||
<div class="ms-split-box ms-split-box--right">
|
||||
<slot name="right"></slot>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="absolute top-0 flex h-[16px] w-full items-center justify-center">
|
||||
<div class="expand-icon expand-icon--vertical" @click="changeLeftExpand">
|
||||
<MsIcon
|
||||
:type="isExpanded ? 'icon-icon_up-left_outlined' : 'icon-icon_down-right_outlined'"
|
||||
class="text-[var(--color-text-brand)]"
|
||||
size="12"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ms-split-box ms-split-box--bottom">
|
||||
<slot name="bottom"></slot>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div :class="`ms-split-box ${props.direction === 'horizontal' ? 'ms-split-box--right' : 'ms-split-box--bottom'}`">
|
||||
<slot name="second"></slot>
|
||||
</div>
|
||||
</template>
|
||||
</a-split>
|
||||
</template>
|
||||
|
@ -68,6 +70,7 @@
|
|||
max?: number | string;
|
||||
direction?: 'horizontal' | 'vertical';
|
||||
expandDirection?: 'left' | 'right' | 'top'; // TODO: 未实现 bottom,有场景再补充。目前默认水平是 left,垂直是 top
|
||||
disabled?: boolean; // 是否禁用
|
||||
}>(),
|
||||
{
|
||||
size: '300px',
|
||||
|
@ -81,6 +84,9 @@
|
|||
const emit = defineEmits(['update:size', 'expandChange']);
|
||||
|
||||
const innerSize = ref(props.size || '300px');
|
||||
const initialSize = props.size || '300px';
|
||||
const isExpanded = ref(true);
|
||||
const isExpandAnimating = ref(false); // 控制动画类
|
||||
|
||||
watch(
|
||||
() => props.size,
|
||||
|
@ -98,32 +104,45 @@
|
|||
}
|
||||
);
|
||||
|
||||
const isExpanded = ref(true);
|
||||
const isExpandAnimating = ref(false); // 控制动画类
|
||||
|
||||
function changeLeftExpand() {
|
||||
function expand(size?: string | number) {
|
||||
isExpandAnimating.value = true;
|
||||
isExpanded.value = !isExpanded.value;
|
||||
if (isExpanded.value) {
|
||||
innerSize.value = props.size || '300px'; // 按初始化的 size 展开,无论是水平还是垂直,都是宽度/高度
|
||||
emit('expandChange', true);
|
||||
} else {
|
||||
innerSize.value = props.expandDirection === 'right' ? 1 : '0px'; // expandDirection为 right 时,收起即为把左侧容器宽度提到 100%
|
||||
emit('expandChange', false);
|
||||
}
|
||||
isExpanded.value = true;
|
||||
innerSize.value = size || initialSize || '300px'; // 按初始化的 size 展开,无论是水平还是垂直,都是宽度/高度
|
||||
emit('expandChange', true);
|
||||
// 动画结束,去掉动画类
|
||||
setTimeout(() => {
|
||||
isExpandAnimating.value = false;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function collapse(size?: string | number) {
|
||||
isExpandAnimating.value = true;
|
||||
isExpanded.value = false;
|
||||
innerSize.value = props.expandDirection === 'right' ? 1 : size || '0px'; // expandDirection为 right 时,收起即为把左侧容器宽度提到 100%
|
||||
emit('expandChange', false);
|
||||
// 动画结束,去掉动画类
|
||||
setTimeout(() => {
|
||||
isExpandAnimating.value = false;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function changeExpand() {
|
||||
if (isExpanded.value) {
|
||||
collapse();
|
||||
} else {
|
||||
expand();
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
expand,
|
||||
collapse,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
/* stylelint-disable value-keyword-case */
|
||||
.expanded-panel {
|
||||
:deep(.arco-split-trigger) {
|
||||
@apply hidden;
|
||||
}
|
||||
:deep(.arco-split-pane) {
|
||||
@apply relative overflow-hidden;
|
||||
}
|
||||
|
@ -146,8 +165,10 @@
|
|||
width: calc(v-bind(innerSize) - 4px);
|
||||
}
|
||||
.expand-icon {
|
||||
@apply z-10 flex cursor-pointer justify-center;
|
||||
@apply relative z-20 flex cursor-pointer justify-center;
|
||||
|
||||
top: 25%;
|
||||
transform: translateY(50%);
|
||||
padding: 12px 2px;
|
||||
border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0;
|
||||
background-color: var(--color-text-n8);
|
||||
|
@ -158,15 +179,50 @@
|
|||
:deep(.arco-split-trigger-icon) {
|
||||
font-size: 14px;
|
||||
}
|
||||
.ms-split-box--bottom {
|
||||
@apply h-full;
|
||||
}
|
||||
.expand-icon--vertical {
|
||||
@apply rotate-90;
|
||||
}
|
||||
.expand-icon--left {
|
||||
@apply rotate-180;
|
||||
|
||||
border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0;
|
||||
}
|
||||
.horizontal-expand-line {
|
||||
padding: 0 1px;
|
||||
height: 100%;
|
||||
.expand-color-line {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background-color: var(--color-text-n8);
|
||||
}
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: rgb(var(--primary-5));
|
||||
.expand-color-line {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ms-split-box--vertical {
|
||||
.ms-split-box--bottom {
|
||||
@apply h-full bg-white;
|
||||
}
|
||||
.vertical-expand-line {
|
||||
@apply relative z-10 flex items-center justify-center bg-transparent;
|
||||
&::before {
|
||||
@apply absolute w-full;
|
||||
|
||||
margin-bottom: -4px;
|
||||
height: 4px;
|
||||
box-shadow: 0 -1px 4px 0 rgb(31 35 41 / 10%), 0 -1px 4px 0 rgb(255 255 255), 0 -1px 4px 0 rgb(255 255 255),
|
||||
0 -1px 4px 0 rgb(255 255 255);
|
||||
content: '';
|
||||
}
|
||||
// .expand-icon--vertical {
|
||||
// width: 20px;
|
||||
// height: 0;
|
||||
// margin-top: 4px;
|
||||
// background-color: transparent;
|
||||
// border-radius: 2px;
|
||||
// background-color: var(--color-text-n8);
|
||||
// }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -357,7 +357,7 @@ export default function useTableProps<T>(
|
|||
resetSelector();
|
||||
} else {
|
||||
resetSelector(false);
|
||||
data.forEach((item) => {
|
||||
data.forEach((item: Record<string, any>) => {
|
||||
if (item[rowKey] && !selectedKeys.has(item[rowKey])) {
|
||||
selectedKeys.add(item[rowKey]);
|
||||
}
|
||||
|
@ -386,22 +386,24 @@ export default function useTableProps<T>(
|
|||
});
|
||||
|
||||
watchEffect(() => {
|
||||
const { heightUsed, showPagination, selectedKeys, msPagination } = propsRes.value;
|
||||
let hasFooterAction = false;
|
||||
if (showPagination) {
|
||||
const { pageSize, total } = msPagination as Pagination;
|
||||
/*
|
||||
* 是否有底部操作栏 包括 批量操作 和 分页器
|
||||
* 1. 有分页器,且总条数大于每页条数
|
||||
* 2. 有选中项
|
||||
*/
|
||||
hasFooterAction = total > pageSize || selectedKeys.size > 0;
|
||||
}
|
||||
if (props?.heightUsed) {
|
||||
const { heightUsed, showPagination, selectedKeys, msPagination } = propsRes.value;
|
||||
let hasFooterAction = false;
|
||||
if (showPagination) {
|
||||
const { pageSize, total } = msPagination as Pagination;
|
||||
/*
|
||||
* 是否有底部操作栏 包括 批量操作 和 分页器
|
||||
* 1. 有分页器,且总条数大于每页条数
|
||||
* 2. 有选中项
|
||||
*/
|
||||
hasFooterAction = total > pageSize || selectedKeys.size > 0;
|
||||
}
|
||||
|
||||
const currentY =
|
||||
appStore.innerHeight - (heightUsed || defaultHeightUsed) + (hasFooterAction ? 0 : footerActionWrapHeight);
|
||||
propsRes.value.showFooterActionWrap = hasFooterAction;
|
||||
propsRes.value.scroll = { ...propsRes.value.scroll, y: currentY };
|
||||
const currentY =
|
||||
appStore.innerHeight - (heightUsed || defaultHeightUsed) + (hasFooterAction ? 0 : footerActionWrapHeight);
|
||||
propsRes.value.showFooterActionWrap = hasFooterAction;
|
||||
propsRes.value.scroll = { ...propsRes.value.scroll, y: currentY };
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
@ -27,6 +27,23 @@ export interface PathMapItem {
|
|||
* children 是子路由/tab集合
|
||||
*/
|
||||
export const pathMap: PathMapItem[] = [
|
||||
{
|
||||
key: 'API_TEST', // 接口测试
|
||||
locale: 'menu.apiTest',
|
||||
route: RouteEnum.API_TEST,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
children: [
|
||||
{
|
||||
key: 'API_TEST_DEBUG', // 接口测试
|
||||
locale: 'menu.apiTest.debug',
|
||||
route: RouteEnum.API_TEST_DEBUG,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'CASE_MANAGEMENT', // 功能测试
|
||||
locale: 'menu.caseManagement',
|
||||
|
@ -41,13 +58,6 @@ export const pathMap: PathMapItem[] = [
|
|||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
// {
|
||||
// key: 'CASE_MANAGEMENT_CASE_DETAIL', // 功能测试-功能用例-创建用例
|
||||
// locale: 'menu.caseManagement.featureCaseDetail',
|
||||
// route: RouteEnum.CASE_MANAGEMENT_CASE_DETAIL,
|
||||
// permission: [],
|
||||
// level: MENU_LEVEL[2],
|
||||
// },
|
||||
{
|
||||
key: 'CASE_MANAGEMENT_REVIEW', // 功能测试-功能用例-用例评审
|
||||
locale: 'menu.caseManagement.caseManagementReview',
|
||||
|
@ -55,6 +65,27 @@ export const pathMap: PathMapItem[] = [
|
|||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
children: [
|
||||
{
|
||||
key: 'CASE_MANAGEMENT_CASE_DETAIL', // 功能测试-功能用例详情
|
||||
locale: 'menu.caseManagement.featureCaseDetail',
|
||||
route: RouteEnum.CASE_MANAGEMENT_CASE_DETAIL,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
{
|
||||
key: 'CASE_MANAGEMENT_CASE_CREATE_SUCCESS', // 功能测试-功能用例创建成功页面
|
||||
locale: 'menu.caseManagement.featureCaseCreateSuccess',
|
||||
route: RouteEnum.CASE_MANAGEMENT_CASE_CREATE_SUCCESS,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
{
|
||||
key: 'CASE_MANAGEMENT_CASE_RECYCLE', // 功能测试-功能用例-回收站
|
||||
locale: 'menu.caseManagement.featureCaseRecycle',
|
||||
route: RouteEnum.CASE_MANAGEMENT_CASE_RECYCLE,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
{
|
||||
key: 'CASE_MANAGEMENT_REVIEW_CREATE', // 功能测试-功能用例-创建评审
|
||||
locale: 'menu.caseManagement.caseManagementReviewCreate',
|
||||
|
@ -392,41 +423,4 @@ export const pathMap: PathMapItem[] = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'CASE_MANAGEMENT', // 功能测试
|
||||
locale: 'menu.caseManagement',
|
||||
route: RouteEnum.CASE_MANAGEMENT,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
children: [
|
||||
{
|
||||
key: 'CASE_MANAGEMENT_CASE', // 功能测试-功能用例
|
||||
locale: 'menu.caseManagement.featureCase',
|
||||
route: RouteEnum.CASE_MANAGEMENT_CASE,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
{
|
||||
key: 'CASE_MANAGEMENT_CASE_DETAIL', // 功能测试-功能用例详情
|
||||
locale: 'menu.caseManagement.featureCaseDetail',
|
||||
route: RouteEnum.CASE_MANAGEMENT_CASE_DETAIL,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
{
|
||||
key: 'CASE_MANAGEMENT_CASE_CREATE_SUCCESS', // 功能测试-功能用例创建成功页面
|
||||
locale: 'menu.caseManagement.featureCaseCreateSuccess',
|
||||
route: RouteEnum.CASE_MANAGEMENT_CASE_CREATE_SUCCESS,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
{
|
||||
key: 'CASE_MANAGEMENT_CASE_RECYCLE', // 功能测试-功能用例-回收站
|
||||
locale: 'menu.caseManagement.featureCaseRecycle',
|
||||
route: RouteEnum.CASE_MANAGEMENT_CASE_RECYCLE,
|
||||
permission: [],
|
||||
level: MENU_LEVEL[2],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
export enum RequestMethods {
|
||||
GET = 'GET',
|
||||
POST = 'POST',
|
||||
PUT = 'PUT',
|
||||
DELETE = 'DELETE',
|
||||
PATCH = 'PATCH',
|
||||
OPTIONS = 'OPTIONS',
|
||||
HEAD = 'HEAD',
|
||||
CONNECT = 'CONNECT',
|
||||
}
|
||||
|
||||
export enum RequestComposition {
|
||||
HEADER = 'HEADER',
|
||||
BODY = 'BODY',
|
||||
QUERY = 'QUERY',
|
||||
REST = 'REST',
|
||||
PREFIX = 'PREFIX',
|
||||
POST_CONDITION = 'POST_CONDITION',
|
||||
ASSERTION = 'ASSERTION',
|
||||
AUTH = 'AUTH',
|
||||
SETTING = 'SETTING',
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
export enum ApiTestRouteEnum {
|
||||
API_TEST = 'apiTest',
|
||||
API_TEST_DEBUG = 'apiTestDebug',
|
||||
}
|
||||
|
||||
export enum BugManagementRouteEnum {
|
||||
|
|
|
@ -16,7 +16,7 @@ export interface ContainerShadowOptions {
|
|||
}
|
||||
|
||||
/**
|
||||
* 监听指定容器的滚动事件,以给容器添加顶部和底部的阴影,需要给指定的容器添加 .ms-container--shadow() 样式类使用阴影
|
||||
* 监听指定容器的滚动事件,以给容器添加顶部和底部的阴影,需要给指定的容器添加 .ms-container--shadow-y() 样式类使用阴影
|
||||
* @param options ContainerShadowOptions
|
||||
*/
|
||||
export default function useContainerShadow(options: ContainerShadowOptions) {
|
||||
|
|
|
@ -64,10 +64,12 @@ export default {
|
|||
'common.resetDefault': 'Reset default',
|
||||
'common.tagPlaceholder': 'Add tag and press Enter to end',
|
||||
'common.batchModify': 'Batch Edit',
|
||||
'common.batchAdd': 'Batch Add',
|
||||
'common.pleaseSelect': 'please choose',
|
||||
'common.quickAddMember': 'Quickly add members',
|
||||
'common.filter': 'Filter',
|
||||
'common.export': 'Export',
|
||||
'common.import': 'Import',
|
||||
'common.collapseAll': 'Collapse all',
|
||||
'common.expandAll': 'Expand all',
|
||||
'common.copy': 'Copy',
|
||||
|
|
|
@ -24,6 +24,7 @@ export default {
|
|||
'menu.bugManagement': 'Bug',
|
||||
'menu.caseManagement': 'Case Management',
|
||||
'menu.apiTest': 'API Test',
|
||||
'menu.apiTest.debug': 'API debug',
|
||||
'menu.uiTest': 'UI Test',
|
||||
'menu.performanceTest': 'Performance Test',
|
||||
'menu.projectManagement': 'Project',
|
||||
|
|
|
@ -67,9 +67,11 @@ export default {
|
|||
'common.resetDefault': '恢复默认',
|
||||
'common.tagPlaceholder': '添加标签回车结束',
|
||||
'common.batchModify': '批量修改',
|
||||
'common.batchAdd': '批量添加',
|
||||
'common.pleaseSelect': '请选择',
|
||||
'common.quickAddMember': '快速添加成员',
|
||||
'common.export': '导出',
|
||||
'common.import': '导入',
|
||||
'common.collapseAll': '展开全部',
|
||||
'common.expandAll': '收起全部',
|
||||
'common.copy': '复制',
|
||||
|
|
|
@ -23,6 +23,7 @@ export default {
|
|||
'menu.bugManagement': '缺陷管理',
|
||||
'menu.caseManagement': '功能测试',
|
||||
'menu.apiTest': '接口测试',
|
||||
'menu.apiTest.debug': '接口调试',
|
||||
'menu.uiTest': 'UI测试',
|
||||
'menu.workstation': '工作台',
|
||||
'menu.loadTest': '性能测试',
|
||||
|
|
|
@ -6,7 +6,7 @@ import type { AppRouteRecordRaw } from '../types';
|
|||
const ApiTest: AppRouteRecordRaw = {
|
||||
path: '/api-test',
|
||||
name: ApiTestRouteEnum.API_TEST,
|
||||
redirect: '/api-test/index',
|
||||
redirect: '/api-test/debug',
|
||||
component: DEFAULT_LAYOUT,
|
||||
meta: {
|
||||
locale: 'menu.apiTest',
|
||||
|
@ -16,11 +16,13 @@ const ApiTest: AppRouteRecordRaw = {
|
|||
},
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
name: 'apiTestIndex',
|
||||
component: () => import('@/views/api-test/index.vue'),
|
||||
path: 'debug',
|
||||
name: ApiTestRouteEnum.API_TEST_DEBUG,
|
||||
component: () => import('@/views/api-test/debug/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.apiTest.debug',
|
||||
roles: ['*'],
|
||||
isTopMenu: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -33,3 +33,31 @@ export function removeEventListen(
|
|||
target.removeEventListener(event, handler, capture);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册 Ctrl + S 快捷键保存事件拦截
|
||||
* @param callback 保存回调
|
||||
*/
|
||||
export function registerCatchSaveShortcut(callback: () => void) {
|
||||
document.addEventListener('keydown', (event) => {
|
||||
// 检查是否按下了 Ctrl 键(Windows/Linux)或 Command 键(Mac)
|
||||
const isCtrlPressed = event.ctrlKey || event.metaKey;
|
||||
// 检查是否按下了 'S' 键
|
||||
const isSPressed = event.key === 's';
|
||||
// 如果同时按下了 Ctrl 键和 'S' 键
|
||||
if (isCtrlPressed && isSPressed) {
|
||||
// 阻止默认行为,防止浏览器保存页面
|
||||
event.preventDefault();
|
||||
// 在这里添加你的保存逻辑
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除 Ctrl + S 快捷键保存事件拦截
|
||||
* @param callback 保存回调
|
||||
*/
|
||||
export function removeCatchSaveShortcut(callback: () => void) {
|
||||
document.removeEventListener('keydown', callback);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<div class="font-medium" :style="{ color: methodColor }">{{ props.method }}</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RequestMethods } from '@/enums/apiEnum';
|
||||
|
||||
const props = defineProps<{
|
||||
method: RequestMethods;
|
||||
}>();
|
||||
|
||||
const colorMaps = [
|
||||
{
|
||||
color: 'rgb(var(--success-6))',
|
||||
includes: [RequestMethods.GET, RequestMethods.HEAD],
|
||||
},
|
||||
{
|
||||
color: 'rgb(var(--warning-7))',
|
||||
includes: [RequestMethods.POST],
|
||||
},
|
||||
{
|
||||
color: 'rgb(var(--link-7))',
|
||||
includes: [RequestMethods.PUT, RequestMethods.OPTIONS],
|
||||
},
|
||||
{
|
||||
color: 'rgb(var(--danger-6))',
|
||||
includes: [RequestMethods.DELETE],
|
||||
},
|
||||
{
|
||||
color: 'rgb(var(--primary-7))',
|
||||
includes: [RequestMethods.PATCH],
|
||||
},
|
||||
{
|
||||
color: 'rgb(var(--primary-4))',
|
||||
includes: [RequestMethods.CONNECT],
|
||||
},
|
||||
];
|
||||
|
||||
const methodColor = computed(() => {
|
||||
const colorMap = colorMaps.find((item) => item.includes.includes(props.method));
|
||||
return colorMap?.color;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,345 @@
|
|||
<template>
|
||||
<div class="mb-[8px] flex items-center justify-between">
|
||||
<div class="font-medium">{{ t('ms.apiTestDebug.header') }}</div>
|
||||
<a-button type="outline" size="mini" @click="showBatchAddParamDrawer = true">
|
||||
{{ t('ms.apiTestDebug.batchAdd') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<MsBaseTable v-bind="propsRes" id="headerTable" v-on="propsEvent">
|
||||
<template #name="{ record }">
|
||||
<a-popover position="tl" :disabled="!record.name || record.name.trim() === ''" class="ms-params-input-popover">
|
||||
<template #content>
|
||||
<div class="param-popover-title">
|
||||
{{ t('ms.apiTestDebug.paramName') }}
|
||||
</div>
|
||||
<div class="param-popover-value">
|
||||
{{ record.name }}
|
||||
</div>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:model-value="record.name"
|
||||
:placeholder="t('ms.apiTestDebug.paramNamePlaceholder')"
|
||||
class="param-input"
|
||||
@input="addTableLine"
|
||||
/>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template #value="{ record }">
|
||||
<MsParamsInput
|
||||
v-model:value="record.value"
|
||||
@change="addTableLine"
|
||||
@dblclick="quickInputParams(record)"
|
||||
@apply="handleParamSettingApply"
|
||||
/>
|
||||
</template>
|
||||
<template #desc="{ record }">
|
||||
<paramDescInput v-model:desc="record.desc" @input="addTableLine" @dblclick="quickInputDesc(record)" />
|
||||
</template>
|
||||
<template #operation="{ rowIndex }">
|
||||
<icon-minus-circle
|
||||
v-if="paramsLength > 1 && rowIndex !== paramsLength - 1"
|
||||
class="cursor-pointer text-[var(--color-text-4)]"
|
||||
size="20"
|
||||
@click="deleteParam(rowIndex)"
|
||||
/>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
</div>
|
||||
<MsDrawer
|
||||
v-model:visible="showBatchAddParamDrawer"
|
||||
:title="t('common.batchAdd')"
|
||||
:width="680"
|
||||
:ok-text="t('ms.apiTestDebug.apply')"
|
||||
disabled-width-drag
|
||||
@confirm="applyBatchParams"
|
||||
>
|
||||
<div class="flex h-full">
|
||||
<MsCodeEditor
|
||||
v-model:model-value="batchParamsCode"
|
||||
class="flex-1"
|
||||
theme="MS-text"
|
||||
height="calc(100% - 48px)"
|
||||
:show-full-screen="false"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex flex-col">
|
||||
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||
{{ t('ms.apiTestDebug.batchAddParamsTip') }}
|
||||
</div>
|
||||
<div class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
|
||||
{{ t('ms.apiTestDebug.batchAddParamsTip2') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsCodeEditor>
|
||||
</div>
|
||||
</MsDrawer>
|
||||
<a-modal
|
||||
v-model:visible="showQuickInputParam"
|
||||
:title="t('ms.paramsInput.value')"
|
||||
:ok-text="t('ms.apiTestDebug.apply')"
|
||||
class="ms-modal-form"
|
||||
body-class="!p-0"
|
||||
:width="680"
|
||||
title-align="start"
|
||||
@ok="applyQuickInputParam"
|
||||
@close="clearQuickInputParam"
|
||||
>
|
||||
<MsCodeEditor v-model:model-value="quickInputParamValue" theme="MS-text" height="300px" :show-full-screen="false">
|
||||
<template #title>
|
||||
<div class="flex justify-between">
|
||||
<div class="text-[var(--color-text-1)]">
|
||||
{{ t('ms.apiTestDebug.quickInputParamsTip') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsCodeEditor>
|
||||
</a-modal>
|
||||
<a-modal
|
||||
v-model:visible="showQuickInputDesc"
|
||||
:title="t('ms.apiTestDebug.desc')"
|
||||
:ok-text="t('common.save')"
|
||||
:ok-button-props="{ disabled: !quickInputDescValue || quickInputDescValue.trim() === '' }"
|
||||
class="ms-modal-form"
|
||||
body-class="!p-0"
|
||||
:width="480"
|
||||
title-align="start"
|
||||
:auto-size="{ minRows: 2 }"
|
||||
@ok="applyQuickInputDesc"
|
||||
@close="clearQuickInputDesc"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:model-value="quickInputDescValue"
|
||||
:placeholder="t('ms.apiTestDebug.descPlaceholder')"
|
||||
:max-length="255"
|
||||
show-word-limit
|
||||
></a-textarea>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
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 MsParamsInput from '@/components/business/ms-params-input/index.vue';
|
||||
import paramDescInput from './paramDescInput.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const props = defineProps<{
|
||||
params: any[];
|
||||
layout: 'horizontal' | 'vertical';
|
||||
secondBoxHeight: number;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'ms.apiTestDebug.paramName',
|
||||
dataIndex: 'name',
|
||||
slotName: 'name',
|
||||
},
|
||||
{
|
||||
title: 'ms.apiTestDebug.paramValue',
|
||||
dataIndex: 'value',
|
||||
slotName: 'value',
|
||||
},
|
||||
{
|
||||
title: 'ms.apiTestDebug.desc',
|
||||
dataIndex: 'desc',
|
||||
slotName: 'desc',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
slotName: 'operation',
|
||||
width: 50,
|
||||
},
|
||||
];
|
||||
const { propsRes, propsEvent } = useTable(() => Promise.resolve([]), {
|
||||
scroll: props.layout === 'horizontal' ? { x: '700px' } : { x: '100%' },
|
||||
heightUsed: props.layout === 'horizontal' ? 422 : 422 + props.secondBoxHeight,
|
||||
columns,
|
||||
selectable: true,
|
||||
draggable: { type: 'handle', width: 24 },
|
||||
});
|
||||
|
||||
propsRes.value.data = props.params.concat({
|
||||
id: new Date().getTime(),
|
||||
name: '',
|
||||
value: '',
|
||||
desc: '',
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.layout,
|
||||
(val) => {
|
||||
propsRes.value.heightUsed = val === 'horizontal' ? 422 : 422 + props.secondBoxHeight;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.secondBoxHeight,
|
||||
(val) => {
|
||||
if (props.layout === 'vertical') {
|
||||
propsRes.value.heightUsed = 422 + val;
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
const paramsLength = computed(() => propsRes.value.data.length);
|
||||
|
||||
function deleteParam(rowIndex: number) {
|
||||
propsRes.value.data.splice(rowIndex, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当表格输入框变化时,给参数表格添加一行数据行
|
||||
* @param val 输入值
|
||||
*/
|
||||
function addTableLine(val: string | number) {
|
||||
const lastData = propsRes.value.data[propsRes.value.data.length - 1];
|
||||
if (val && (lastData.name || lastData.value || lastData.desc)) {
|
||||
propsRes.value.data.push({
|
||||
id: new Date().getTime(),
|
||||
name: '',
|
||||
value: '',
|
||||
desc: '',
|
||||
} as any);
|
||||
}
|
||||
}
|
||||
|
||||
const showBatchAddParamDrawer = ref(false);
|
||||
const batchParamsCode = ref('');
|
||||
|
||||
/**
|
||||
* 批量参数代码转换为参数表格数据
|
||||
*/
|
||||
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(),
|
||||
desc: '',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter((item) => item);
|
||||
propsRes.value.data.splice(propsRes.value.data.length - 1, 0, ...(resultArr as any[]));
|
||||
showBatchAddParamDrawer.value = false;
|
||||
batchParamsCode.value = '';
|
||||
}
|
||||
|
||||
const showQuickInputParam = ref(false);
|
||||
const activeQuickInputRecord = ref<any>({});
|
||||
const quickInputParamValue = ref('');
|
||||
|
||||
function quickInputParams(record: any) {
|
||||
activeQuickInputRecord.value = record;
|
||||
showQuickInputParam.value = true;
|
||||
quickInputParamValue.value = record.value;
|
||||
}
|
||||
|
||||
function clearQuickInputParam() {
|
||||
activeQuickInputRecord.value = {};
|
||||
quickInputParamValue.value = '';
|
||||
}
|
||||
|
||||
function applyQuickInputParam() {
|
||||
activeQuickInputRecord.value.value = quickInputParamValue.value;
|
||||
showQuickInputParam.value = false;
|
||||
clearQuickInputParam();
|
||||
}
|
||||
|
||||
function handleParamSettingApply(val: string | number) {
|
||||
addTableLine(val);
|
||||
}
|
||||
|
||||
const showQuickInputDesc = ref(false);
|
||||
const quickInputDescValue = ref('');
|
||||
|
||||
function quickInputDesc(record: any) {
|
||||
activeQuickInputRecord.value = record;
|
||||
showQuickInputDesc.value = true;
|
||||
quickInputDescValue.value = record.desc;
|
||||
}
|
||||
|
||||
function clearQuickInputDesc() {
|
||||
activeQuickInputRecord.value = {};
|
||||
quickInputDescValue.value = '';
|
||||
}
|
||||
|
||||
function applyQuickInputDesc() {
|
||||
activeQuickInputRecord.value.desc = quickInputDescValue.value;
|
||||
showQuickInputDesc.value = false;
|
||||
clearQuickInputDesc();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-table-th) {
|
||||
background-color: var(--color-text-n9);
|
||||
}
|
||||
:deep(.arco-table-cell-align-left) {
|
||||
padding: 16px 4px;
|
||||
}
|
||||
:deep(.arco-table-cell) {
|
||||
padding: 11px 4px;
|
||||
}
|
||||
.param-input:not(.arco-input-focus) {
|
||||
&:not(:hover) {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
.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;
|
||||
|
||||
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,262 @@
|
|||
<template>
|
||||
<div class="border-b border-[var(--color-text-n8)] p-[24px_24px_16px_24px]">
|
||||
<MsEditableTab
|
||||
v-model:active-tab="activeTab"
|
||||
:tabs="debugTabs"
|
||||
:more-action-list="moreActionList"
|
||||
@add="addDebugTab"
|
||||
@close="closeDebugTab"
|
||||
@click="setActiveDebug"
|
||||
>
|
||||
<template #label="{ tab }">
|
||||
<apiMethodName :method="tab.method" class="mr-[4px]" />
|
||||
{{ tab.label }}
|
||||
<div class="ml-[8px] h-[8px] w-[8px] rounded-full bg-[rgb(var(--primary-5))]"></div>
|
||||
</template>
|
||||
</MsEditableTab>
|
||||
</div>
|
||||
<div class="px-[24px] pt-[16px]">
|
||||
<div class="mb-[4px] flex items-center justify-between">
|
||||
<a-input-group class="flex-1">
|
||||
<a-select v-model:model-value="activeDebug.method" class="w-[140px]">
|
||||
<template #label="{ data }">
|
||||
<apiMethodName :method="data.value" class="inline-block" />
|
||||
</template>
|
||||
<a-option v-for="method of RequestMethods" :key="method" :value="method">
|
||||
<apiMethodName :method="method" />
|
||||
</a-option>
|
||||
</a-select>
|
||||
<a-input v-model:model-value="debugUrl" :placeholder="t('ms.apiTestDebug.urlPlaceholder')" />
|
||||
</a-input-group>
|
||||
<div class="ml-[16px]">
|
||||
<a-dropdown-button class="exec-btn">
|
||||
{{ t('ms.apiTestDebug.serverExec') }}
|
||||
<template #icon>
|
||||
<icon-down />
|
||||
</template>
|
||||
<template #content>
|
||||
<a-doption>{{ t('ms.apiTestDebug.localExec') }}</a-doption>
|
||||
</template>
|
||||
</a-dropdown-button>
|
||||
<a-button type="secondary">
|
||||
<div class="flex items-center">
|
||||
{{ t('common.save') }}
|
||||
<div class="text-[var(--color-text-4)]">(<icon-command size="14" /> + S)</div>
|
||||
</div>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="splitContainerRef" class="flex-1">
|
||||
<MsSplitBox
|
||||
ref="splitBoxRef"
|
||||
v-model:size="splitBoxSize"
|
||||
:max="0.98"
|
||||
min="10px"
|
||||
:direction="activeLayout"
|
||||
@expand-change="handleExpandChange"
|
||||
>
|
||||
<template #first>
|
||||
<div :class="`h-full min-w-[500px] px-[24px] pb-[16px] ${activeLayout === 'horizontal' ? ' pr-[16px]' : ''}`">
|
||||
<a-tabs v-model:active-key="contentTab" class="no-content">
|
||||
<a-tab-pane v-for="item of contentTabList" :key="item.value" :title="item.label" />
|
||||
</a-tabs>
|
||||
<a-divider margin="0" class="!mb-[16px]"></a-divider>
|
||||
<debugHeader :params="activeDebug.params" :layout="activeLayout" :second-box-height="secondBoxHeight" />
|
||||
</div>
|
||||
</template>
|
||||
<template #second>
|
||||
<div class="min-w-[290px] bg-[var(--color-text-n9)] p-[8px_16px]">
|
||||
<div class="flex items-center">
|
||||
<template v-if="activeLayout === 'vertical'">
|
||||
<MsButton
|
||||
v-if="isExpanded"
|
||||
type="icon"
|
||||
class="!mr-0 !rounded-full bg-[rgb(var(--primary-1))]"
|
||||
@click="changeExpand(false)"
|
||||
>
|
||||
<icon-down :size="12" />
|
||||
</MsButton>
|
||||
<MsButton v-else type="icon" status="secondary" class="!mr-0 !rounded-full" @click="changeExpand(true)">
|
||||
<icon-right :size="12" />
|
||||
</MsButton>
|
||||
</template>
|
||||
<div class="ml-[4px] mr-[24px] font-medium">{{ t('ms.apiTestDebug.responseContent') }}</div>
|
||||
<a-radio-group
|
||||
v-model:model-value="activeLayout"
|
||||
type="button"
|
||||
size="small"
|
||||
@change="handleActiveLayoutChange"
|
||||
>
|
||||
<a-radio value="vertical">{{ t('ms.apiTestDebug.vertical') }}</a-radio>
|
||||
<a-radio value="horizontal">{{ t('ms.apiTestDebug.horizontal') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
|
||||
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
import apiMethodName from '../../../components/apiMethodName.vue';
|
||||
import debugHeader from './header.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { RequestComposition, RequestMethods } from '@/enums/apiEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const initDefaultId = `debug-${Date.now()}`;
|
||||
const activeTab = ref<string | number>(initDefaultId);
|
||||
const debugTabs = ref<TabItem[]>([
|
||||
{
|
||||
id: initDefaultId,
|
||||
label: t('ms.apiTestDebug.newApi'),
|
||||
closable: true,
|
||||
method: RequestMethods.GET,
|
||||
unSave: true,
|
||||
params: [],
|
||||
},
|
||||
]);
|
||||
const debugUrl = ref('');
|
||||
const activeDebug = ref<TabItem>(debugTabs.value[0]);
|
||||
|
||||
function setActiveDebug(item: TabItem) {
|
||||
activeDebug.value = item;
|
||||
}
|
||||
|
||||
function addDebugTab() {
|
||||
const id = `debug-${Date.now()}`;
|
||||
debugTabs.value.push({
|
||||
id,
|
||||
label: t('ms.apiTestDebug.newApi'),
|
||||
closable: true,
|
||||
method: RequestMethods.GET,
|
||||
unSave: true,
|
||||
params: [],
|
||||
});
|
||||
activeTab.value = id;
|
||||
}
|
||||
|
||||
function closeDebugTab(tab: TabItem) {
|
||||
const index = debugTabs.value.findIndex((item) => item.id === tab.id);
|
||||
if (activeTab.value === tab.id) {
|
||||
activeTab.value = debugTabs.value[0]?.id || '';
|
||||
}
|
||||
debugTabs.value.splice(index, 1);
|
||||
}
|
||||
|
||||
const moreActionList = [
|
||||
{
|
||||
key: 'add',
|
||||
label: t('common.add'),
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: t('common.delete'),
|
||||
},
|
||||
];
|
||||
|
||||
const contentTab = ref(RequestComposition.HEADER);
|
||||
const contentTabList = [
|
||||
{
|
||||
value: RequestComposition.HEADER,
|
||||
label: t('ms.apiTestDebug.header'),
|
||||
},
|
||||
{
|
||||
value: RequestComposition.BODY,
|
||||
label: t('ms.apiTestDebug.body'),
|
||||
},
|
||||
{
|
||||
value: RequestComposition.QUERY,
|
||||
label: RequestComposition.QUERY,
|
||||
},
|
||||
{
|
||||
value: RequestComposition.REST,
|
||||
label: RequestComposition.REST,
|
||||
},
|
||||
{
|
||||
value: RequestComposition.PREFIX,
|
||||
label: t('ms.apiTestDebug.prefix'),
|
||||
},
|
||||
{
|
||||
value: RequestComposition.POST_CONDITION,
|
||||
label: t('ms.apiTestDebug.postCondition'),
|
||||
},
|
||||
{
|
||||
value: RequestComposition.ASSERTION,
|
||||
label: t('ms.apiTestDebug.assertion'),
|
||||
},
|
||||
{
|
||||
value: RequestComposition.AUTH,
|
||||
label: t('ms.apiTestDebug.auth'),
|
||||
},
|
||||
{
|
||||
value: RequestComposition.SETTING,
|
||||
label: t('ms.apiTestDebug.setting'),
|
||||
},
|
||||
];
|
||||
|
||||
const splitBoxSize = ref<string | number>(0.6);
|
||||
const activeLayout = ref<'horizontal' | 'vertical'>('vertical');
|
||||
const splitContainerRef = ref<HTMLElement>();
|
||||
const secondBoxHeight = ref(0);
|
||||
|
||||
watch(
|
||||
() => splitBoxSize.value,
|
||||
debounce((val) => {
|
||||
if (splitContainerRef.value) {
|
||||
secondBoxHeight.value = splitContainerRef.value.clientHeight * (1 - val);
|
||||
}
|
||||
}, 300),
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
const splitBoxRef = ref<InstanceType<typeof MsSplitBox>>();
|
||||
const isExpanded = ref(true);
|
||||
|
||||
function handleExpandChange(val: boolean) {
|
||||
isExpanded.value = val;
|
||||
}
|
||||
function changeExpand(val: boolean) {
|
||||
isExpanded.value = val;
|
||||
if (val) {
|
||||
splitBoxRef.value?.expand(0.6);
|
||||
} else {
|
||||
splitBoxRef.value?.collapse(splitContainerRef.value ? `${splitContainerRef.value.clientHeight - 42}px` : 0);
|
||||
}
|
||||
}
|
||||
|
||||
function handleActiveLayoutChange() {
|
||||
isExpanded.value = true;
|
||||
splitBoxSize.value = 0.6;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.exec-btn {
|
||||
margin-right: 12px;
|
||||
:deep(.arco-btn) {
|
||||
color: white !important;
|
||||
background-color: rgb(var(--primary-5)) !important;
|
||||
.btn-base-primary-hover();
|
||||
.btn-base-primary-active();
|
||||
.btn-base-primary-disabled();
|
||||
}
|
||||
}
|
||||
:deep(.no-content) {
|
||||
.arco-tabs-content {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<a-popover position="tl" :disabled="!props.desc || props.desc.trim() === ''" class="ms-params-input-popover">
|
||||
<template #content>
|
||||
<div class="param-popover-title">
|
||||
{{ t('ms.apiTestDebug.desc') }}
|
||||
</div>
|
||||
<div class="param-popover-value">
|
||||
{{ props.desc }}
|
||||
</div>
|
||||
</template>
|
||||
<a-input ref="inputRef" v-model:model-value="innerValue" class="param-input" @input="(val) => emit('input', val)" />
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useEventListener, useVModel } from '@vueuse/core';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const props = defineProps<{
|
||||
desc: string;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', val: string): void;
|
||||
(e: 'input', val: string): void;
|
||||
(e: 'dblclick'): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const innerValue = useVModel(props, 'desc', emit);
|
||||
|
||||
const inputRef = ref<HTMLElement>();
|
||||
|
||||
onMounted(() => {
|
||||
useEventListener(inputRef.value, 'dblclick', () => {
|
||||
emit('dblclick');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.param-input:not(.arco-input-focus) {
|
||||
&:not(:hover) {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
.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,356 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-[8px] flex items-center gap-[8px]">
|
||||
<a-select v-model:model-value="moduleProtocol" :options="moduleProtocolOptions" class="w-[90px]"></a-select>
|
||||
<a-input
|
||||
v-model:model-value="moduleKeyword"
|
||||
:placeholder="t('caseManagement.caseReview.folderSearchPlaceholder')"
|
||||
allow-clear
|
||||
/>
|
||||
</div>
|
||||
<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-count">({{ allFileCount }})</div>
|
||||
</div>
|
||||
<div class="ml-auto flex items-center">
|
||||
<a-tooltip :content="isExpandAll ? t('common.collapseAll') : t('common.expandAll')">
|
||||
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" @click="changeExpand">
|
||||
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<popConfirm mode="add" :all-names="rootModulesName" parent-id="NONE" @add-finish="initModules">
|
||||
<MsButton type="icon" class="!mr-0 p-[2px]">
|
||||
<MsIcon
|
||||
type="icon-icon_create_planarity"
|
||||
size="18"
|
||||
class="text-[rgb(var(--primary-5))] hover:text-[rgb(var(--primary-4))]"
|
||||
/>
|
||||
</MsButton>
|
||||
</popConfirm>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider v-if="!props.isModal" class="my-[8px]" />
|
||||
<a-spin class="min-h-[400px] w-full" :loading="loading">
|
||||
<MsTree
|
||||
v-model:focus-node-key="focusNodeKey"
|
||||
:data="folderTree"
|
||||
:keyword="moduleKeyword"
|
||||
:node-more-actions="folderMoreActions"
|
||||
:default-expand-all="isExpandAll"
|
||||
:expand-all="isExpandAll"
|
||||
:empty-text="t('ms.apiTestDebug.noMatchModule')"
|
||||
:draggable="!props.isModal"
|
||||
:virtual-list-props="virtualListProps"
|
||||
:field-names="{
|
||||
title: 'name',
|
||||
key: 'id',
|
||||
children: 'children',
|
||||
count: 'count',
|
||||
}"
|
||||
:selectable="false"
|
||||
block-node
|
||||
title-tooltip-position="left"
|
||||
@more-action-select="handleFolderMoreSelect"
|
||||
@more-actions-close="moreActionsClose"
|
||||
@drop="handleDrop"
|
||||
>
|
||||
<template #title="nodeData">
|
||||
<div class="inline-flex w-full">
|
||||
<div class="one-line-text w-[calc(100%-32px)] text-[var(--color-text-1)]">{{ nodeData.name }}</div>
|
||||
<div v-if="!props.isModal" class="ml-[4px] text-[var(--color-text-4)]">({{ nodeData.count || 0 }})</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!props.isModal" #extra="nodeData">
|
||||
<!-- 默认模块的 id 是root,默认模块不可编辑、不可添加子模块 -->
|
||||
<popConfirm
|
||||
v-if="nodeData.id !== 'root'"
|
||||
mode="add"
|
||||
:all-names="(nodeData.children || []).map((e: ModuleTreeNode) => e.name || '')"
|
||||
:parent-id="nodeData.id"
|
||||
@close="resetFocusNodeKey"
|
||||
@add-finish="() => initModules()"
|
||||
>
|
||||
<MsButton type="icon" size="mini" class="ms-tree-node-extra__btn !mr-0" @click="setFocusNodeKey(nodeData)">
|
||||
<MsIcon type="icon-icon_add_outlined" size="14" class="text-[var(--color-text-4)]" />
|
||||
</MsButton>
|
||||
</popConfirm>
|
||||
<popConfirm
|
||||
v-if="nodeData.id !== 'root'"
|
||||
mode="rename"
|
||||
:parent-id="nodeData.id"
|
||||
:node-id="nodeData.id"
|
||||
:field-config="{ field: renameFolderTitle }"
|
||||
:all-names="(nodeData.children || []).map((e: ModuleTreeNode) => e.name || '')"
|
||||
@close="resetFocusNodeKey"
|
||||
@rename-finish="initModules"
|
||||
>
|
||||
<span :id="`renameSpan${nodeData.id}`" class="relative"></span>
|
||||
</popConfirm>
|
||||
</template>
|
||||
</MsTree>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeMount, ref, watch } from 'vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import MsTree from '@/components/business/ms-tree/index.vue';
|
||||
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||
import popConfirm from './popConfirm.vue';
|
||||
|
||||
import { deleteReviewModule, getReviewModules, moveReviewModule } from '@/api/modules/case-management/caseReview';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { mapTree } from '@/utils';
|
||||
|
||||
import { ModuleTreeNode } from '@/models/projectManagement/file';
|
||||
|
||||
const props = defineProps<{
|
||||
isModal?: boolean; // 是否是弹窗模式
|
||||
modulesCount?: Record<string, number>; // 模块数量统计对象
|
||||
isExpandAll?: boolean; // 是否展开所有节点
|
||||
}>();
|
||||
const emit = defineEmits(['init', 'folderNodeSelect']);
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const { openModal } = useModal();
|
||||
|
||||
const virtualListProps = computed(() => {
|
||||
if (props.isModal) {
|
||||
return {
|
||||
height: 'calc(60vh - 190px)',
|
||||
};
|
||||
}
|
||||
return {
|
||||
height: 'calc(100vh - 325px)',
|
||||
};
|
||||
});
|
||||
|
||||
const activeFolder = ref<string>('all');
|
||||
const allFileCount = ref(0);
|
||||
const isExpandAll = ref(props.isExpandAll);
|
||||
const rootModulesName = ref<string[]>([]); // 根模块名称列表
|
||||
|
||||
watch(
|
||||
() => props.isExpandAll,
|
||||
(val) => {
|
||||
isExpandAll.value = val;
|
||||
}
|
||||
);
|
||||
|
||||
function changeExpand() {
|
||||
isExpandAll.value = !isExpandAll.value;
|
||||
}
|
||||
|
||||
const moduleProtocol = ref('http');
|
||||
const moduleProtocolOptions = ref([
|
||||
{
|
||||
label: 'HTTP',
|
||||
value: 'http',
|
||||
},
|
||||
]);
|
||||
const moduleKeyword = ref('');
|
||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||
const focusNodeKey = ref<string | number>('');
|
||||
const loading = ref(false);
|
||||
|
||||
function setFocusNodeKey(node: MsTreeNodeData) {
|
||||
focusNodeKey.value = node.id || '';
|
||||
}
|
||||
|
||||
const folderMoreActions: ActionsItem[] = [
|
||||
{
|
||||
label: 'common.rename',
|
||||
eventTag: 'rename',
|
||||
},
|
||||
{
|
||||
label: 'common.delete',
|
||||
eventTag: 'delete',
|
||||
danger: true,
|
||||
},
|
||||
];
|
||||
const renamePopVisible = ref(false);
|
||||
|
||||
/**
|
||||
* 初始化模块树
|
||||
* @param isSetDefaultKey 是否设置第一个节点为选中节点
|
||||
*/
|
||||
async function initModules() {
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await getReviewModules(appStore.currentProjectId);
|
||||
folderTree.value = mapTree<ModuleTreeNode>(res, (e) => {
|
||||
return {
|
||||
...e,
|
||||
hideMoreAction: e.id === 'root',
|
||||
draggable: e.id !== 'root' && !props.isModal,
|
||||
disabled: e.id === activeFolder.value && props.isModal,
|
||||
};
|
||||
});
|
||||
emit('init', folderTree.value);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件夹
|
||||
* @param node 节点信息
|
||||
*/
|
||||
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'),
|
||||
okButtonProps: {
|
||||
status: 'danger',
|
||||
},
|
||||
maskClosable: false,
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
await deleteReviewModule(node.id);
|
||||
Message.success(t('caseManagement.caseReview.deleteSuccess'));
|
||||
initModules();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
hideCancel: false,
|
||||
});
|
||||
}
|
||||
|
||||
const renameFolderTitle = ref(''); // 重命名的文件夹名称
|
||||
|
||||
function resetFocusNodeKey() {
|
||||
focusNodeKey.value = '';
|
||||
renamePopVisible.value = false;
|
||||
renameFolderTitle.value = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理树节点更多按钮事件
|
||||
* @param item
|
||||
*/
|
||||
function handleFolderMoreSelect(item: ActionsItem, node: MsTreeNodeData) {
|
||||
switch (item.eventTag) {
|
||||
case 'delete':
|
||||
deleteFolder(node);
|
||||
resetFocusNodeKey();
|
||||
break;
|
||||
case 'rename':
|
||||
renameFolderTitle.value = node.name || '';
|
||||
renamePopVisible.value = true;
|
||||
document.querySelector(`#renameSpan${node.id}`)?.dispatchEvent(new Event('click'));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文件夹树节点拖拽事件
|
||||
* @param tree 树数据
|
||||
* @param dragNode 拖拽节点
|
||||
* @param dropNode 释放节点
|
||||
* @param dropPosition 释放位置
|
||||
*/
|
||||
async function handleDrop(
|
||||
tree: MsTreeNodeData[],
|
||||
dragNode: MsTreeNodeData,
|
||||
dropNode: MsTreeNodeData,
|
||||
dropPosition: number
|
||||
) {
|
||||
try {
|
||||
loading.value = true;
|
||||
await moveReviewModule({
|
||||
dragNodeId: dragNode.id as string,
|
||||
dropNodeId: dropNode.id || '',
|
||||
dropPosition,
|
||||
});
|
||||
Message.success(t('caseManagement.caseReview.moduleMoveSuccess'));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
initModules();
|
||||
}
|
||||
}
|
||||
|
||||
function moreActionsClose() {
|
||||
if (!renamePopVisible.value) {
|
||||
// 当下拉菜单关闭时,若不是触发重命名气泡显示,则清空聚焦节点 key
|
||||
resetFocusNodeKey();
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
initModules();
|
||||
});
|
||||
|
||||
/**
|
||||
* 初始化模块文件数量
|
||||
*/
|
||||
watch(
|
||||
() => props.modulesCount,
|
||||
(obj) => {
|
||||
folderTree.value = mapTree<ModuleTreeNode>(folderTree.value, (node) => {
|
||||
return {
|
||||
...node,
|
||||
count: obj?.[node.id] || 0,
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
initModules,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.folder {
|
||||
@apply flex cursor-pointer items-center justify-between;
|
||||
|
||||
padding: 8px 4px;
|
||||
border-radius: var(--border-radius-small);
|
||||
&:hover {
|
||||
background-color: rgb(var(--primary-1));
|
||||
}
|
||||
.folder-text {
|
||||
@apply flex cursor-pointer items-center;
|
||||
.folder-icon {
|
||||
margin-right: 4px;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
.folder-name {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.folder-count {
|
||||
margin-left: 4px;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
}
|
||||
.folder-text--active {
|
||||
.folder-icon,
|
||||
.folder-name,
|
||||
.folder-count {
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,176 @@
|
|||
<template>
|
||||
<a-popconfirm
|
||||
v-model:popup-visible="innerVisible"
|
||||
class="ms-pop-confirm--hidden-icon"
|
||||
position="bottom"
|
||||
:ok-loading="loading"
|
||||
:cancel-button-props="{ disabled: loading }"
|
||||
:on-before-ok="beforeConfirm"
|
||||
:popup-container="props.popupContainer || 'body'"
|
||||
@popup-visible-change="reset"
|
||||
>
|
||||
<template #content>
|
||||
<div class="mb-[8px] font-medium">
|
||||
{{
|
||||
props.title ||
|
||||
(props.mode === 'add' ? t('project.fileManagement.addSubModule') : t('project.fileManagement.rename'))
|
||||
}}
|
||||
</div>
|
||||
<a-form ref="formRef" :model="form" layout="vertical">
|
||||
<a-form-item
|
||||
class="hidden-item"
|
||||
field="field"
|
||||
:rules="[{ required: true, message: t('project.fileManagement.nameNotNull') }, { validator: validateName }]"
|
||||
>
|
||||
<a-textarea
|
||||
v-if="props.fieldConfig?.isTextArea"
|
||||
v-model:model-value="form.field"
|
||||
:max-length="props.fieldConfig?.maxLength"
|
||||
:auto-size="{ maxRows: 4 }"
|
||||
:placeholder="props.fieldConfig?.placeholder || t('project.fileManagement.namePlaceholder')"
|
||||
class="w-[245px]"
|
||||
@press-enter="beforeConfirm(undefined)"
|
||||
>
|
||||
</a-textarea>
|
||||
<a-input
|
||||
v-else
|
||||
v-model:model-value="form.field"
|
||||
:max-length="props.fieldConfig?.maxLength || 50"
|
||||
:placeholder="props.fieldConfig?.placeholder || t('project.fileManagement.namePlaceholder')"
|
||||
class="w-[245px]"
|
||||
@press-enter="beforeConfirm(undefined)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
<slot></slot>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
import { addReviewModule, updateReviewModule } from '@/api/modules/case-management/caseReview';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import type { FieldRule, FormInstance } from '@arco-design/web-vue';
|
||||
|
||||
interface FieldConfig {
|
||||
field?: string;
|
||||
rules?: FieldRule[];
|
||||
placeholder?: string;
|
||||
maxLength?: number;
|
||||
isTextArea?: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
mode: 'add' | 'rename' | 'fileRename' | 'fileUpdateDesc' | 'repositoryRename';
|
||||
visible?: boolean;
|
||||
title?: string;
|
||||
allNames: string[];
|
||||
popupContainer?: string;
|
||||
fieldConfig?: FieldConfig;
|
||||
parentId?: string; // 父节点 id
|
||||
nodeId?: string; // 节点 id
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:visible', 'close', 'addFinish', 'renameFinish', 'updateDescFinish']);
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const innerVisible = ref(props.visible || false);
|
||||
const form = ref({
|
||||
field: props.fieldConfig?.field || '',
|
||||
});
|
||||
const formRef = ref<FormInstance>();
|
||||
const loading = ref(false);
|
||||
|
||||
watch(
|
||||
() => props.fieldConfig?.field,
|
||||
(val) => {
|
||||
form.value.field = val || '';
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
innerVisible.value = val;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => innerVisible.value,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
emit('close');
|
||||
}
|
||||
emit('update:visible', val);
|
||||
}
|
||||
);
|
||||
|
||||
function beforeConfirm(done?: (closed: boolean) => void) {
|
||||
if (loading.value) return;
|
||||
formRef.value?.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
try {
|
||||
loading.value = true;
|
||||
if (props.mode === 'add') {
|
||||
// 添加根级模块
|
||||
await addReviewModule({
|
||||
projectId: appStore.currentProjectId,
|
||||
parentId: props.parentId || '',
|
||||
name: form.value.field,
|
||||
});
|
||||
Message.success(t('project.fileManagement.addSubModuleSuccess'));
|
||||
emit('addFinish', form.value.field);
|
||||
} else if (props.mode === 'rename') {
|
||||
// 模块重命名
|
||||
await updateReviewModule({
|
||||
id: props.nodeId || '',
|
||||
name: form.value.field,
|
||||
});
|
||||
Message.success(t('project.fileManagement.renameSuccess'));
|
||||
emit('renameFinish', form.value.field);
|
||||
}
|
||||
if (done) {
|
||||
done(true);
|
||||
} else {
|
||||
innerVisible.value = false;
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
if (done) {
|
||||
done(false);
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
} else if (done) {
|
||||
done(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateName(value: any, callback: (error?: string | undefined) => void) {
|
||||
if (props.allNames.includes(value)) {
|
||||
callback(t('project.fileManagement.nameExist'));
|
||||
}
|
||||
}
|
||||
|
||||
function reset(val: boolean) {
|
||||
if (!val) {
|
||||
form.value.field = '';
|
||||
formRef.value?.resetFields();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<MsCard simple no-content-padding>
|
||||
<MsSplitBox :size="0.25" :max="0.5">
|
||||
<template #first>
|
||||
<div class="border-b border-[var(--color-text-n8)] p-[24px_24px_16px_24px]">
|
||||
<a-button type="primary" class="mr-[12px]">{{ t('ms.apiTestDebug.createDebug') }}</a-button>
|
||||
<a-button type="outline">{{ t('common.import') }}</a-button>
|
||||
</div>
|
||||
<div class="px-[24px] py-[16px]">
|
||||
<moduleTree />
|
||||
</div>
|
||||
</template>
|
||||
<template #second>
|
||||
<div class="flex h-full flex-col">
|
||||
<debug />
|
||||
</div>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
</MsCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
import debug from './components/debug/index.vue';
|
||||
import moduleTree from './components/moduleTree.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
function saveDebug() {
|
||||
console.log('save');
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
registerCatchSaveShortcut(saveDebug);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
removeCatchSaveShortcut(saveDebug);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,31 @@
|
|||
export default {
|
||||
'ms.apiTestDebug.createDebug': 'New debug',
|
||||
'ms.apiTestDebug.newApi': 'New request',
|
||||
'ms.apiTestDebug.urlPlaceholder': 'Please enter the full URL including http or https',
|
||||
'ms.apiTestDebug.serverExec': 'Server execution',
|
||||
'ms.apiTestDebug.localExec': 'Local execution',
|
||||
'ms.apiTestDebug.noMatchModule': 'No matching module data yet',
|
||||
'ms.apiTestDebug.header': 'Header',
|
||||
'ms.apiTestDebug.body': 'Body',
|
||||
'ms.apiTestDebug.prefix': 'Prefix',
|
||||
'ms.apiTestDebug.postCondition': 'Post condition',
|
||||
'ms.apiTestDebug.assertion': 'Assertion',
|
||||
'ms.apiTestDebug.auth': 'Auth',
|
||||
'ms.apiTestDebug.setting': 'Setting',
|
||||
'ms.apiTestDebug.batchAdd': 'Batch add',
|
||||
'ms.apiTestDebug.responseContent': 'Response content',
|
||||
'ms.apiTestDebug.vertical': 'Vertical layout',
|
||||
'ms.apiTestDebug.horizontal': 'Horizontal layout',
|
||||
'ms.apiTestDebug.paramName': 'Parameter name',
|
||||
'ms.apiTestDebug.paramNamePlaceholder': 'Please enter parameter name',
|
||||
'ms.apiTestDebug.paramValue': 'Parameter value',
|
||||
'ms.apiTestDebug.paramValuePlaceholder': 'Starting with {at}, double-click to quickly enter',
|
||||
'ms.apiTestDebug.paramValuePreview': 'Parameter preview',
|
||||
'ms.apiTestDebug.desc': 'Description',
|
||||
'ms.apiTestDebug.apply': 'Apply',
|
||||
'ms.apiTestDebug.batchAddParamsTip': 'Writing format: parameter name: parameter value; such as nama: natural',
|
||||
'ms.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.',
|
||||
'ms.apiTestDebug.quickInputParamsTip': 'Support Mock/JMeter/Json/Text/String, etc.',
|
||||
'ms.apiTestDebug.descPlaceholder': 'Please enter content',
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
export default {
|
||||
'ms.apiTestDebug.createDebug': '新建调试',
|
||||
'ms.apiTestDebug.newApi': '新建请求',
|
||||
'ms.apiTestDebug.urlPlaceholder': '请输入包含 http 或 https 的完整URL',
|
||||
'ms.apiTestDebug.serverExec': '服务端执行',
|
||||
'ms.apiTestDebug.localExec': '本地执行',
|
||||
'ms.apiTestDebug.noMatchModule': '暂无匹配的模块数据',
|
||||
'ms.apiTestDebug.header': '请求头',
|
||||
'ms.apiTestDebug.body': '请求体',
|
||||
'ms.apiTestDebug.prefix': '前置',
|
||||
'ms.apiTestDebug.postCondition': '后置',
|
||||
'ms.apiTestDebug.assertion': '断言',
|
||||
'ms.apiTestDebug.auth': '认证',
|
||||
'ms.apiTestDebug.setting': '设置',
|
||||
'ms.apiTestDebug.batchAdd': '批量添加',
|
||||
'ms.apiTestDebug.responseContent': '响应内容',
|
||||
'ms.apiTestDebug.vertical': '上下布局',
|
||||
'ms.apiTestDebug.horizontal': '左右布局',
|
||||
'ms.apiTestDebug.paramName': '参数名称',
|
||||
'ms.apiTestDebug.paramNamePlaceholder': '请输入参数名称',
|
||||
'ms.apiTestDebug.paramValue': '参数值',
|
||||
'ms.apiTestDebug.paramValuePlaceholder': '以{at}开始,双击可快速输入',
|
||||
'ms.apiTestDebug.paramValuePreview': '参数预览',
|
||||
'ms.apiTestDebug.desc': '描述',
|
||||
'ms.apiTestDebug.apply': '应用',
|
||||
'ms.apiTestDebug.batchAddParamsTip': '书写格式:参数名:参数值;如 nama:natural',
|
||||
'ms.apiTestDebug.batchAddParamsTip2': '注: 多条记录以换行分隔,批量添加里的参数名重复,默认以最后一条数据为最新数据',
|
||||
'ms.apiTestDebug.quickInputParamsTip': '支持Mock/JMeter/Json/Text/String等',
|
||||
'ms.apiTestDebug.descPlaceholder': '请输入内容',
|
||||
};
|
|
@ -1,175 +0,0 @@
|
|||
<template>
|
||||
<div class="h-[100vh] bg-white px-[20px] py-[16px] pb-0">
|
||||
<ms-base-table v-bind="propsRes" :action-config="actionConfig" v-on="propsEvent"> </ms-base-table>
|
||||
</div>
|
||||
<a-divider />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import { BatchActionConfig, MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
|
||||
import { getTableList } from '@/api/modules/api-test/index';
|
||||
import { useTableStore } from '@/store';
|
||||
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'num',
|
||||
filterable: {
|
||||
filters: [
|
||||
{
|
||||
text: '> 20000',
|
||||
value: '20000',
|
||||
},
|
||||
{
|
||||
text: '> 30000',
|
||||
value: '30000',
|
||||
},
|
||||
],
|
||||
filter: (value, record) => record.salary > value,
|
||||
multiple: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '接口名称',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '请求类型',
|
||||
dataIndex: 'method',
|
||||
},
|
||||
{
|
||||
title: '责任人',
|
||||
dataIndex: 'username',
|
||||
},
|
||||
{
|
||||
title: '路径',
|
||||
dataIndex: 'path',
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
dataIndex: 'tags',
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
dataIndex: 'updateTime',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '用例数',
|
||||
dataIndex: 'caseTotal',
|
||||
},
|
||||
{
|
||||
title: '用例状态',
|
||||
dataIndex: 'caseStatus',
|
||||
},
|
||||
{
|
||||
title: '用例通过率',
|
||||
dataIndex: 'casePassingRate',
|
||||
},
|
||||
{
|
||||
title: '接口状态',
|
||||
dataIndex: 'status',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
slotName: 'createTime',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'description',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'action',
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
|
||||
const actionConfig: BatchActionConfig = {
|
||||
baseAction: [
|
||||
{
|
||||
label: 'msTable.batch.export',
|
||||
eventTag: 'batchExport',
|
||||
isDivider: false,
|
||||
danger: false,
|
||||
},
|
||||
{
|
||||
label: 'msTable.batch.edit',
|
||||
eventTag: 'batchEdit',
|
||||
isDivider: false,
|
||||
danger: false,
|
||||
},
|
||||
{
|
||||
label: 'msTable.batch.moveTo',
|
||||
eventTag: 'batchMoveTo',
|
||||
isDivider: false,
|
||||
danger: false,
|
||||
},
|
||||
{
|
||||
label: 'msTable.batch.copyTo',
|
||||
eventTag: 'batchCopyTo',
|
||||
isDivider: false,
|
||||
danger: false,
|
||||
},
|
||||
],
|
||||
moreAction: [
|
||||
{
|
||||
label: 'msTable.batch.related',
|
||||
eventTag: 'batchRelated',
|
||||
isDivider: false,
|
||||
danger: false,
|
||||
},
|
||||
{
|
||||
label: 'msTable.batch.generateDep',
|
||||
eventTag: 'batchGenerate',
|
||||
isDivider: false,
|
||||
danger: false,
|
||||
},
|
||||
{
|
||||
label: 'msTable.batch.addPublic',
|
||||
eventTag: 'batchAddTo',
|
||||
isDivider: false,
|
||||
danger: false,
|
||||
},
|
||||
{
|
||||
isDivider: true,
|
||||
},
|
||||
{
|
||||
label: 'msTable.batch.delete',
|
||||
eventTag: 'batchDelete',
|
||||
isDivider: false,
|
||||
danger: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const tableStore = useTableStore();
|
||||
|
||||
await tableStore.initColumn(TableKeyEnum.API_TEST, columns, 'drawer');
|
||||
|
||||
const { propsRes, propsEvent, loadList } = useTable(getTableList, {
|
||||
columns,
|
||||
scroll: { y: 750, x: 2000 },
|
||||
selectable: true,
|
||||
});
|
||||
|
||||
const fetchData = async () => {
|
||||
await loadList();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchData();
|
||||
});
|
||||
</script>
|
|
@ -1,20 +0,0 @@
|
|||
export default {
|
||||
apiTest: {
|
||||
id: 'ID',
|
||||
name: 'Api name',
|
||||
method: 'Requset Method',
|
||||
path: 'Request Path',
|
||||
description: 'Api description',
|
||||
createTime: 'Create time',
|
||||
updateTime: 'Update time',
|
||||
operation: 'Operation',
|
||||
addApiTest: 'Add Api',
|
||||
editApiTest: 'Edit api',
|
||||
deleteApiTest: 'Delete api',
|
||||
deleteApiTestConfirm: 'Are you sure to delete this api?',
|
||||
deleteApiTestSuccess: 'Delete api success!',
|
||||
deleteApiTestError: 'Delete api failed, please try again!',
|
||||
addApiTestSuccess: 'Add api success!',
|
||||
addApiTestError: 'Add api failed, please try again!',
|
||||
},
|
||||
};
|
|
@ -1,20 +0,0 @@
|
|||
export default {
|
||||
apiTest: {
|
||||
id: 'ID',
|
||||
name: '接口名称',
|
||||
method: '请求方式',
|
||||
path: '请求路径',
|
||||
description: '接口描述',
|
||||
createTime: '创建时间',
|
||||
updateTime: '更新时间',
|
||||
operation: '操作',
|
||||
addApiTest: '新增接口',
|
||||
editApiTest: '编辑接口',
|
||||
deleteApiTest: '删除接口',
|
||||
deleteApiTestConfirm: '确定删除该接口吗?',
|
||||
deleteApiTestSuccess: '删除接口成功!',
|
||||
deleteApiTestError: '删除接口失败,请重试!',
|
||||
addApiTestSuccess: '新增接口成功!',
|
||||
addApiTestError: '新增接口失败,请重试!',
|
||||
},
|
||||
};
|
|
@ -75,7 +75,7 @@
|
|||
<template #default>
|
||||
<div ref="wrapperRef" class="h-full bg-white">
|
||||
<MsSplitBox ref="wrapperRef" expand-direction="right" :max="0.7" :min="0.7" :size="900">
|
||||
<template #left>
|
||||
<template #first>
|
||||
<div class="leftWrapper h-full">
|
||||
<div class="header h-[50px]">
|
||||
<a-tabs v-model:active-key="activeTab">
|
||||
|
@ -92,7 +92,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<template #second>
|
||||
<div class="rightWrapper p-[24px]">
|
||||
<div class="mb-4 font-medium">{{ t('caseManagement.featureCase.basicInfo') }}</div>
|
||||
<div class="baseItem">
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
<template #default>
|
||||
<div ref="wrapperRef" class="h-full bg-white">
|
||||
<MsSplitBox ref="wrapperRef" expand-direction="right" :max="0.7" :min="0.7" :size="900">
|
||||
<template #left>
|
||||
<template #first>
|
||||
<div class="leftWrapper h-full">
|
||||
<div class="header h-[50px]">
|
||||
<a-menu mode="horizontal" :default-selected-keys="[activeTab]" @menu-item-click="clickMenu">
|
||||
|
@ -126,7 +126,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<template #second>
|
||||
<div class="rightWrapper p-[24px]">
|
||||
<div class="mb-4 font-medium">{{ t('caseManagement.featureCase.basicInfo') }}</div>
|
||||
<div class="baseItem">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="pageWrap">
|
||||
<MsSplitBox>
|
||||
<template #left>
|
||||
<template #first>
|
||||
<div class="p-[24px]">
|
||||
<a-input-search
|
||||
v-model:model-value="groupKeyword"
|
||||
|
@ -60,7 +60,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<template #second>
|
||||
<div class="p-[24px]">
|
||||
<div class="page-header mb-4 h-[34px]">
|
||||
<div class="text-[var(--color-text-1)]"
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<a-divider class="!my-0" />
|
||||
<div class="pageWrap">
|
||||
<MsSplitBox>
|
||||
<template #left>
|
||||
<template #first>
|
||||
<div class="p-[24px] pb-0">
|
||||
<div class="feature-case h-[100%]">
|
||||
<div class="case h-[38px]">
|
||||
|
@ -80,7 +80,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<template #second>
|
||||
<div class="p-[24px]">
|
||||
<CaseTable
|
||||
:active-folder="activeFolder"
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
>
|
||||
<div class="mb-[4px] flex items-center justify-between">
|
||||
<div>{{ item.id }}</div>
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<div class="flex items-center gap-[4px] leading-[22px]">
|
||||
<MsIcon
|
||||
:type="resultMap[item.result as ResultMap].icon"
|
||||
:style="{color: resultMap[item.result as ResultMap].color}"
|
||||
|
@ -127,10 +127,10 @@
|
|||
<MsDescription v-if="showTab === 'baseInfo'" :descriptions="descriptions" label-width="90px" />
|
||||
<div v-else-if="showTab === 'detail'" class="h-full">
|
||||
<MsSplitBox :size="0.8" direction="vertical" min="0" :max="0.99">
|
||||
<template #top>
|
||||
<template #first>
|
||||
<caseTabDetail :form="detailForm" :allow-edit="false" />
|
||||
</template>
|
||||
<template #bottom>
|
||||
<template #second>
|
||||
<div class="flex h-full flex-col overflow-hidden">
|
||||
<div class="mb-[8px] font-medium text-[var(--color-text-1)]">
|
||||
{{ t('caseManagement.caseReview.reviewHistory') }}
|
||||
|
|
|
@ -115,7 +115,6 @@
|
|||
const props = defineProps<{
|
||||
isModal?: boolean; // 是否是弹窗模式
|
||||
modulesCount?: Record<string, number>; // 模块数量统计对象
|
||||
showType?: string; // 显示类型
|
||||
isExpandAll?: boolean; // 是否展开所有节点
|
||||
}>();
|
||||
const emit = defineEmits(['init', 'folderNodeSelect']);
|
||||
|
|
|
@ -86,12 +86,12 @@
|
|||
</MsCard>
|
||||
<MsCard class="mt-[16px]" :special-height="180" simple has-breadcrumb no-content-padding>
|
||||
<MsSplitBox>
|
||||
<template #left>
|
||||
<template #first>
|
||||
<div class="p-[24px]">
|
||||
<CaseTree ref="folderTreeRef" @folder-node-select="handleFolderNodeSelect" />
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<template #second>
|
||||
<CaseTable :active-folder="activeFolderId"></CaseTable>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
|
|
|
@ -4,18 +4,18 @@
|
|||
<a-button type="primary" @click="goCreateReview">{{ t('caseManagement.caseReview.create') }}</a-button>
|
||||
<a-radio-group v-model:model-value="showType" type="button" class="file-show-type" @change="changeShowType">
|
||||
<a-radio value="all">{{ t('common.all') }}</a-radio>
|
||||
<a-radio value="wait">{{ t('caseManagement.caseReview.waitMyReview') }}</a-radio>
|
||||
<a-radio value="create">{{ t('caseManagement.caseReview.myCreate') }}</a-radio>
|
||||
<a-radio value="reviewByMe">{{ t('caseManagement.caseReview.waitMyReview') }}</a-radio>
|
||||
<a-radio value="createByMe">{{ t('caseManagement.caseReview.myCreate') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<div class="relative h-[calc(100%-73px)]">
|
||||
<MsSplitBox>
|
||||
<template #left>
|
||||
<template #first>
|
||||
<div class="px-[24px] py-[16px]">
|
||||
<ModuleTree ref="folderTreeRef" @folder-node-select="handleFolderNodeSelect" @init="initModuleTree" />
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<template #second>
|
||||
<ReviewTable :active-folder="activeFolderId" :module-tree="moduleTree" @go-create="goCreateReview" />
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
|
@ -42,7 +42,7 @@
|
|||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
|
||||
type ShowType = 'all' | 'wait' | 'create';
|
||||
type ShowType = 'all' | 'reviewByMe' | 'createByMe';
|
||||
|
||||
const showType = ref<ShowType>('all');
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
'caseManagement.caseReview.create': 'Create review',
|
||||
'caseManagement.caseReview.waitMyReview': 'Awaiting my review',
|
||||
'caseManagement.caseReview.waitMyReview': 'I reviewed',
|
||||
'caseManagement.caseReview.myCreate': 'I created',
|
||||
'caseManagement.caseReview.searchPlaceholder': 'Search by ID or name',
|
||||
'caseManagement.caseReview.archive': 'Archive',
|
||||
|
@ -130,4 +130,6 @@ export default {
|
|||
'caseManagement.caseReview.reviewHistory': 'Review history',
|
||||
'caseManagement.caseReview.noMatchReviewer': 'No matching handler, can be set in {menu}',
|
||||
'caseManagement.caseReview.crateCase': 'Create case',
|
||||
'caseManagement.caseReview.demandCases': 'Requirements association list',
|
||||
'caseManagement.caseReview.demandSearchPlaceholder': 'Search by name',
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
'caseManagement.caseReview.create': '创建评审',
|
||||
'caseManagement.caseReview.waitMyReview': '待我评审',
|
||||
'caseManagement.caseReview.waitMyReview': '我评审的',
|
||||
'caseManagement.caseReview.myCreate': '我创建的',
|
||||
'caseManagement.caseReview.searchPlaceholder': '通过 ID 或名称搜索',
|
||||
'caseManagement.caseReview.archive': '归档',
|
||||
|
|
|
@ -1255,7 +1255,7 @@
|
|||
.card-list {
|
||||
@apply grid flex-1 overflow-auto;
|
||||
.ms-scroll-bar();
|
||||
.ms-container--shadow();
|
||||
.ms-container--shadow-y();
|
||||
|
||||
gap: 24px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(102px, 1fr));
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="page">
|
||||
<MsSplitBox>
|
||||
<template #left>
|
||||
<template #first>
|
||||
<div class="p-[24px]">
|
||||
<div class="folder" @click="setActiveFolder('my')">
|
||||
<div :class="getFolderClass('my')">
|
||||
|
@ -74,7 +74,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<template #second>
|
||||
<rightBox
|
||||
:active-folder="activeFolder"
|
||||
:active-folder-type="activeFolderType"
|
||||
|
|
|
@ -514,7 +514,7 @@
|
|||
@apply relative;
|
||||
|
||||
background-color: var(--color-text-n9);
|
||||
.ms-container--shadow();
|
||||
.ms-container--shadow-y();
|
||||
.robot-list {
|
||||
@apply grid max-h-full overflow-y-auto;
|
||||
.ms-scroll-bar();
|
||||
|
|
|
@ -312,7 +312,7 @@
|
|||
<style lang="less" scoped>
|
||||
.field-out-container {
|
||||
@apply h-full;
|
||||
.ms-container--shadow();
|
||||
.ms-container--shadow-y();
|
||||
|
||||
border-radius: var(--border-radius-small);
|
||||
background-color: var(--color-text-n9);
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<div class="card">
|
||||
<MsSplitBox v-model:width="leftWidth" @expand-change="handleCollapse">
|
||||
<template #left>
|
||||
<template #first>
|
||||
<UserGroupLeft ref="ugLeftRef" @handle-select="handleSelect" @add-user-success="handleAddMember" />
|
||||
</template>
|
||||
<template #right>
|
||||
<template #second>
|
||||
<div class="p-[24px]">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<a-tooltip :content="currentUserGroupItem.name">
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<a-select
|
||||
v-model:model-value="activeTime"
|
||||
:options="timeOptions"
|
||||
class="time-input-append"
|
||||
class="select-input-append"
|
||||
:loading="saveLoading"
|
||||
@change="() => saveConfig()"
|
||||
/>
|
||||
|
@ -124,18 +124,5 @@
|
|||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-input-append) {
|
||||
@apply border-none;
|
||||
}
|
||||
:deep(.time-input-append) {
|
||||
@apply z-10;
|
||||
|
||||
margin-left: -16px !important;
|
||||
border-radius: 0 4px 4px 0 !important;
|
||||
background-color: var(--color-text-n8) !important;
|
||||
&:hover {
|
||||
border-color: rgb(var(--primary-5)) !important;
|
||||
background-color: var(--color-text-n8) !important;
|
||||
}
|
||||
}
|
||||
.ms-input-group--append();
|
||||
</style>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<div class="card">
|
||||
<MsSplitBox v-model:width="leftWidth" @expand-change="handleCollapse">
|
||||
<template #left>
|
||||
<template #first>
|
||||
<UserGroupLeft ref="ugLeftRef" @handle-select="handleSelect" @add-user-success="handleAddMember" />
|
||||
</template>
|
||||
<template #right>
|
||||
<template #second>
|
||||
<div class="p-[24px]">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<a-tooltip :content="currentUserGroupItem.name">
|
||||
|
|
Loading…
Reference in New Issue