feat(全局): 接口调试-前置条件&用例评审部分接口联调&部分组件调整&axios、vue-draggable-plus 升级

This commit is contained in:
baiqi 2024-01-17 11:51:30 +08:00 committed by Craftsman
parent cbe6c19c45
commit 057b286c19
65 changed files with 1149 additions and 926 deletions

View File

@ -96,6 +96,7 @@ module.exports = {
'^echarts$',
'^color$',
'^localforage$',
'vue-draggable-plus',
], // node依赖
['.*/assets/.*', '^@/assets$'], // 项目静态资源
['^@/components/pure/.*', '^@/components/business/.*', '.*\\.vue$'], // 组件

View File

@ -48,7 +48,7 @@
"@vueuse/core": "^10.4.1",
"ace-builds": "^1.24.2",
"ahooks-vue": "^0.15.1",
"axios": "^0.24.0",
"axios": "^1.6.5",
"dayjs": "^1.11.9",
"echarts": "^5.4.3",
"fastq": "^1.15.0",
@ -67,7 +67,7 @@
"tippy.js": "^6.3.7",
"vue": "^3.3.4",
"vue-dompurify-html": "^4.1.4",
"vue-draggable-plus": "^0.2.7",
"vue-draggable-plus": "^0.3.5",
"vue-echarts": "^6.6.1",
"vue-i18n": "^9.3.0",
"vue-router": "^4.2.4",

View File

@ -8,7 +8,7 @@ import { ContentTypeEnum } from '@/enums/httpEnum';
import { AxiosCanceler } from './axiosCancel';
import type { CreateAxiosOptions } from './axiosTransform';
import type { RequestOptions, Result, UploadFileParams } from '#/axios';
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
export * from './axiosTransform';
@ -56,7 +56,7 @@ export class MSAxios {
if (requestInterceptors && isFunction(requestInterceptors)) {
config = requestInterceptors(config, this.options);
}
return config;
return config as InternalAxiosRequestConfig; // TODO: 拦截配置升级了,暂时 as 处理
}, undefined);
// 响应拦截器

View File

@ -1,5 +1,5 @@
import MSR from '@/api/http/index';
import { ProjectListUrl } from '@/api/requrls/project-management/project';
import { ProjectListUrl, ProjectSwitchUrl } from '@/api/requrls/project-management/project';
import type { ProjectListItem } from '@/models/setting/project';
@ -7,4 +7,6 @@ export function getProjectList(organizationId: string) {
return MSR.get<ProjectListItem[]>({ url: ProjectListUrl, params: organizationId });
}
export default {};
export function switchProject(data: { projectId: string; userId: string }) {
return MSR.post({ url: ProjectSwitchUrl, data });
}

View File

@ -1,3 +1,2 @@
export const ProjectListUrl = '/project/list/options';
export default {};
export const ProjectListUrl = '/project/list/options'; // 项目列表
export const ProjectSwitchUrl = '/project/switch'; // 切换项目

View File

@ -604,15 +604,24 @@
}
/** 开关 **/
.arco-switch:not(.arco-switch-disabled) {
background: var(--color-fill-4) !important;
}
.arco-switch-checked:not(.arco-switch-disabled) {
background: rgb(var(--primary-6)) !important;
}
.arco-switch[disabled] {
background: var(--color-text-n8);
}
.arco-switch-type-line.arco-switch-small {
height: 14px;
line-height: 14px;
}
.arco-switch-type-line.arco-switch-small {
.arco-switch-handle {
width: 14px;
height: 14px;
}
}
.arco-switch-type-line.arco-switch-small.arco-switch-checked {
.arco-switch-handle {
left: calc(100% - 14px - 0px);
}
}
/** 分页 **/
.ms-pagination {

View File

@ -208,6 +208,7 @@
&::before {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 10px;
@ -222,6 +223,7 @@
&::after {
position: absolute;
bottom: 0;
left: 0;
z-index: 1;
width: 100%;
height: 10px;

View File

@ -122,6 +122,7 @@
class="mt-[8px]"
:style="{ 'margin-top': index === 0 && !props.isShowDrag ? '36px' : '' }"
size="small"
type="line"
/>
</div>
<div
@ -159,6 +160,7 @@
<script setup lang="ts">
import { ref, unref, watchEffect } from 'vue';
import { VueDraggable } from 'vue-draggable-plus';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
@ -167,7 +169,6 @@
import type { FormItemModel, FormMode } from './types';
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
import { VueDraggable } from 'vue-draggable-plus';
const { t } = useI18n();

View File

@ -129,6 +129,7 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { useVModel } from '@vueuse/core';
import { CustomTypeMaps, MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
import { FilterFormItem, FilterType } from '@/components/pure/ms-advance-filter/type';
@ -206,8 +207,8 @@
selectedModuleKeys.value = [];
}
const innerVisible = ref(props.visible);
const innerProject = ref(props.projectId);
const innerVisible = useVModel(props, 'visible', emit);
const innerProject = useVModel(props, 'projectId', emit);
const protocolType = ref('HTTP'); //
const protocolOptions = ref(['HTTP']);
@ -559,7 +560,6 @@
watch(
() => props.visible,
(val) => {
innerVisible.value = val;
if (val) {
resetSelector();
initModules();
@ -569,16 +569,6 @@
}
);
watch(
() => innerVisible.value,
(val) => {
emit('update:visible', val);
if (val) {
initModules();
}
}
);
//
watch(
() => caseType.value,
@ -591,19 +581,9 @@
}
);
watch(
() => props.projectId,
(val) => {
if (val) {
innerProject.value = val;
}
}
);
watch(
() => innerProject.value,
(val) => {
emit('update:project', val);
() => {
if (innerVisible.value) {
searchCase();
resetSelector();

View File

@ -71,6 +71,7 @@
v-model:model-value="item.enable"
size="small"
:before-change="() => handleBeforeEnableChange(item)"
type="line"
/>
</div>
</div>

View File

@ -43,6 +43,7 @@
size="small"
:disabled="apiConfig.id === '' || testApiLoading"
:before-change="(val) => handleApiPriorityBeforeChange(val)"
type="line"
/>
</div>
</div>
@ -86,6 +87,7 @@
size="small"
:disabled="uiConfig.id === '' || testUiLoading"
:before-change="(val) => handleUiPriorityBeforeChange(val)"
type="line"
/>
</div>
</div>

View File

@ -416,6 +416,8 @@
width: calc(100% - 8px);
}
.arco-tree-node-drag-icon {
@apply cursor-move;
right: 4px;
.arco-icon {
font-size: 14px;

View File

@ -6,7 +6,7 @@
:default-value="(defaultValue as number)"
@change="handleChange"
/>
<a-switch v-else :default-checked="(defaultValue as boolean)" size="small" @change="handleChange" />
<a-switch v-else :default-checked="(defaultValue as boolean)" size="small" type="line" @change="handleChange" />
</template>
<script lang="ts" setup>

View File

@ -1,6 +1,6 @@
<template>
<div ref="fullRef" class="rounded-[4px] bg-[var(--color-fill-1)] p-[12px]">
<div class="mb-[12px] flex justify-between pr-[12px]">
<div ref="fullRef" class="h-full rounded-[4px] bg-[var(--color-fill-1)] p-[12px]">
<div v-if="showTitleLine" class="mb-[12px] flex justify-between pr-[12px]">
<slot name="title">
<span class="font-medium">{{ title }}</span>
</slot>
@ -19,7 +19,8 @@
{{ t('msCodeEditor.fullScreen') }}
</div>
</div>
<div class="flex w-full flex-row">
<!-- 这里的 32px 是顶部标题的 32px -->
<div :class="`flex ${showTitleLine ? 'h-[calc(100%-32px)]' : 'h-full'} w-full flex-row`">
<div ref="codeEditBox" :class="['ms-code-editor', isFullscreen ? 'ms-code-editor-full-screen' : '']"></div>
<slot name="rightBox"> </slot>
</div>
@ -27,7 +28,7 @@
</template>
<script lang="ts">
import { defineComponent, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { computed, defineComponent, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { useFullscreen } from '@vueuse/core';
import { useI18n } from '@/hooks/useI18n';
@ -36,8 +37,6 @@
import MsCodeEditorTheme from './themes';
import { CustomTheme, editorProps, Theme } from './types';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import prettier from 'prettier';
import parserBabel from 'prettier/parser-babel';
export default defineComponent({
name: 'MonacoEditor',
@ -60,6 +59,7 @@
value: item,
}))
);
const showTitleLine = computed(() => props.title || props.showThemeChange || props.showFullScreen);
watch(
() => props.theme,
@ -167,6 +167,7 @@
isFullscreen,
currentTheme,
themeOptions,
showTitleLine,
toggle,
t,
handleThemeChange,

View File

@ -18,7 +18,7 @@
<slot name="footer">
<div class="flex" :class="[props.switchProps?.showSwitch ? 'justify-between' : 'justify-end']">
<div v-if="props.switchProps?.showSwitch" class="flex flex-row items-center justify-center">
<a-switch v-model="switchEnable" class="mr-1" size="small" />
<a-switch v-model="switchEnable" class="mr-1" size="small" type="line" />
<a-tooltip v-if="props.switchProps?.switchTooltip" :content="t(props.switchProps?.switchTooltip)">
<span class="flex items-center">
<span class="mr-1">{{ props.switchProps?.switchName }}</span>

View File

@ -117,6 +117,7 @@
* @description 系统管理-模版-模版管理-创建模板-添加字段到模板抽屉
*/
import { computed, ref } from 'vue';
import { VueDraggable } from 'vue-draggable-plus';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
@ -125,7 +126,6 @@
import { useI18n } from '@/hooks/useI18n';
import { MsExportDrawerMap, MsExportDrawerOption } from './types';
import { VueDraggable } from 'vue-draggable-plus';
const { t } = useI18n();

View File

@ -3,22 +3,27 @@
v-bind="props"
ref="listRef"
:data="data"
:class="['ms-list', containerStatusClass]"
:class="['ms-list', containerStatusClass, props.class]"
@reach-bottom="handleReachBottom"
>
<template #item="{ item, index }">
<slot name="item" :item="item" :index="index">
<div
:key="index"
:key="item.id"
:class="[
'ms-list-item',
props.noHover ? 'ms-list-item--no-hover' : '',
props.itemBorder ? 'ms-list-item--bordered' : '',
props.itemClass,
innerFocusItemKey === item[itemKeyField] ? 'ms-list-item--focus' : '',
innerActiveItemKey === item[itemKeyField] ? props.activeItemClass : '',
]"
@click="emit('itemClick', item, index)"
>
<div class="flex-1" @click="emit('itemClick', item, index)">
<slot name="title" :item="item" :index="index"></slot>
</div>
<div class="flex items-center gap-[4px]">
<icon-drag-dot-vertical v-if="props.draggable" class="ms-list-drag-icon" />
<div
v-if="$slots['itemAction'] || (props.itemMoreActions && props.itemMoreActions.length > 0)"
class="ms-list-item-actions"
@ -36,6 +41,8 @@
</MsButton>
</MsTableMoreAction>
</div>
<slot name="itemRight" :item="item" :index="index"></slot>
</div>
<div
v-if="props.mode === 'remote' && index === props.data.length - 1"
class="flex h-[32px] items-center justify-center"
@ -60,6 +67,8 @@
<script setup lang="ts">
import { nextTick, Ref, ref, watch } from 'vue';
import { useVModel } from '@vueuse/core';
import { useDraggable } from 'vue-draggable-plus';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
@ -69,10 +78,14 @@
import useContainerShadow from '@/hooks/useContainerShadow';
import { useI18n } from '@/hooks/useI18n';
import type { VirtualListProps } from '@arco-design/web-vue/es/_components/virtual-list-v2/interface';
const props = withDefaults(
defineProps<{
mode?: 'static' | 'remote'; //
data: Record<string, any>[];
bordered?: boolean; //
activeItemKey?: string | number; // key
focusItemKey?: string | number; // key
itemMoreActions?: ActionsItem[]; //
itemKeyField?: string; // key key
@ -81,16 +94,25 @@
noMoreData?: boolean; //
noHover?: boolean; // hover
itemBorder?: boolean; //
class?: string;
itemClass?: string;
activeItemClass?: string;
draggable?: boolean;
virtualListProps?: VirtualListProps;
}>(),
{
mode: 'static',
itemKeyField: 'key',
itemHeight: 20,
bordered: false,
draggable: false,
}
);
const emit = defineEmits([
'update:data',
'update:focusItemKey',
'update:activeItemKey',
'itemClick',
'close',
'moreActionSelect',
@ -101,21 +123,9 @@
const { t } = useI18n();
const innerFocusItemKey = ref(props.focusItemKey || ''); //
watch(
() => props.focusItemKey,
(val) => {
innerFocusItemKey.value = val || '';
}
);
watch(
() => innerFocusItemKey.value,
(val) => {
emit('update:focusItemKey', val);
}
);
const innerFocusItemKey = useVModel(props, 'focusItemKey', emit); //
const innerActiveItemKey = useVModel(props, 'activeItemKey', emit); // activeItemClass使
const innerData = useVModel(props, 'data', emit);
const popVisible = ref(false);
@ -148,6 +158,21 @@
props.data,
() => {
if (props.data.length > 0 && !isInitListener.value) {
//
if (props.draggable) {
//
if (props.virtualListProps) {
//
useDraggable('.arco-list-content div div', innerData, {
ghostClass: 'ms-list-ghost',
});
} else {
//
useDraggable('.arco-list-content', innerData, {
ghostClass: 'ms-list-ghost',
});
}
}
nextTick(() => {
const listContent = listRef.value?.$el.querySelector('.arco-list-content');
setContainer(listContent);
@ -179,10 +204,16 @@
border-radius: var(--border-radius-small);
&:hover {
background-color: rgb(var(--primary-1));
.ms-list-drag-icon {
@apply visible;
}
.ms-list-item-actions {
@apply visible;
}
}
.ms-list-drag-icon {
@apply invisible cursor-move;
}
.ms-list-item-actions {
@apply invisible flex items-center justify-end;
.ms-list-item-actions-btn {
@ -221,5 +252,16 @@
margin-right: 4px;
}
}
:deep(.arco-scrollbar),
:deep(.arco-list),
:deep(.arco-list-content-wrapper) {
@apply h-full;
}
:deep(.arco-list-content) {
.ms-scroll-bar();
}
}
.ms-list-ghost {
opacity: 0.5;
}
</style>

View File

@ -55,7 +55,7 @@
>
<template #title>
<div class="flex w-full flex-row flex-nowrap items-center">
<slot :name="item.titleSlotName">
<slot :name="item.titleSlotName" :column-config="item">
<div class="text-[var(--color-text-3)]">{{ t(item.title as string) }}</div>
</slot>
<icon-settings
@ -96,7 +96,7 @@
<template
v-if="!record[item.dataIndex as string] || (Array.isArray(record[item.dataIndex as string]) && record[item.dataIndex as string].length === 0)"
>
<slot :name="item.slotName" v-bind="{ record, rowIndex, column }"> - </slot>
<slot :name="item.slotName" v-bind="{ record, rowIndex, column, columnConfig: item }"> - </slot>
</template>
<MsTagGroup
v-else
@ -107,7 +107,7 @@
/>
</template>
<template v-else-if="item.slotName === SpecialColumnEnum.OPERATION">
<slot name="operation" v-bind="{ record, rowIndex }" />
<slot name="operation" v-bind="{ record, rowIndex, columnConfig: item }" />
</template>
<template v-else-if="item.slotName === SpecialColumnEnum.ACTION">
<slot name="action" v-bind="{ record, rowIndex }" />
@ -126,7 +126,7 @@
/>
<a-tooltip v-else placement="top" :content="String(record[item.dataIndex as string])">
<div class="one-line-text max-w-[300px]">
<slot :name="item.slotName" v-bind="{ record, rowIndex, column }">
<slot :name="item.slotName" v-bind="{ record, rowIndex, column, columnConfig: item }">
{{ record[item.dataIndex as string] || (attrs.emptyDataShowLine ? '-' : '') }}
</slot>
</div>
@ -146,11 +146,14 @@
/>
</div>
<div>
<slot :name="item.revokeDeletedSlot" v-bind="{ record, rowIndex, column }"></slot>
<slot :name="item.revokeDeletedSlot" v-bind="{ record, rowIndex, column, columnConfig: item }"></slot>
</div>
</template>
<template v-else>
<slot :name="item.slotName" v-bind="{ record, rowIndex, column, dataIndex: item.dataIndex }">
<slot
:name="item.slotName"
v-bind="{ record, rowIndex, column, dataIndex: item.dataIndex, columnConfig: item }"
>
{{ record[item.dataIndex as string] || (attrs.emptyDataShowLine ? '-' : '') }}
</slot>
</template>

View File

@ -58,6 +58,7 @@
v-model="item.showInTable"
size="small"
:disabled="item.dataIndex === 'name' || item.dataIndex === 'operation'"
type="line"
@change="handleSwitchChange"
/>
</div>
@ -73,7 +74,7 @@
<MsIcon type="icon-icon_drag" class="text-[16px] text-[var(--color-text-4)]" />
<span class="ml-[8px]">{{ t((element.title || element.columnTitle) as string) }}</span>
</div>
<a-switch v-model="element.showInTable" size="small" @update="handleSwitchChange" />
<a-switch v-model="element.showInTable" size="small" type="line" @update="handleSwitchChange" />
</div>
</VueDraggable>
</div>
@ -82,6 +83,7 @@
<script lang="ts" setup>
import { onBeforeMount, ref } from 'vue';
import { VueDraggable } from 'vue-draggable-plus';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
@ -93,7 +95,6 @@
import { TableOpenDetailMode } from '@/store/modules/components/ms-table/types';
import { MsTableColumn } from './type';
import { VueDraggable } from 'vue-draggable-plus';
const tableStore = useTableStore();
const { t } = useI18n();

View File

@ -51,6 +51,8 @@ export interface MsTableColumnData extends TableColumnData {
columnTitle?: string;
// 是否是自定义字段
isCustomParam?: boolean;
// 自定义属性
[key: string]: any;
}
export type MsTableErrorStatus = boolean | 'error' | 'empty';

View File

@ -48,7 +48,7 @@
allowClear: true,
}
);
const emit = defineEmits(['update:modelValue', 'update:inputValue']);
const emit = defineEmits(['update:modelValue', 'update:inputValue', 'change']);
const { t } = useI18n();
@ -72,6 +72,7 @@
tagsLength.value = val.length;
}
emit('update:modelValue', val);
emit('change', val);
}
);

View File

@ -113,66 +113,6 @@
</template>
</a-dropdown>
</li>
<!-- <li>
<a-tooltip :content="isFullscreen ? t('settings.navbar.screen.toExit') : t('settings.navbar.screen.toFull')">
<a-button class="nav-btn" type="outline" :shape="'circle'" @click="toggleFullScreen">
<template #icon>
<icon-fullscreen-exit v-if="isFullscreen" />
<icon-fullscreen v-else />
</template>
</a-button>
</a-tooltip>
</li> -->
<!-- <li>
<a-tooltip :content="t('settings.title')">
<a-button class="nav-btn" type="outline" :shape="'circle'" @click="setVisible">
<template #icon>
<icon-settings />
</template>
</a-button>
</a-tooltip>
</li> -->
<!-- <li>
<a-dropdown trigger="click">
<a-avatar :size="32" :style="{ marginRight: '8px', cursor: 'pointer' }">
<img alt="avatar" :src="avatar" />
</a-avatar>
<template #content>
<a-doption>
<a-space @click="switchRoles">
<icon-tag />
<span>
{{ t('messageBox.switchRoles') }}
</span>
</a-space>
</a-doption>
<a-doption>
<a-space @click="$router.push({ name: 'Info' })">
<icon-user />
<span>
{{ t('messageBox.userCenter') }}
</span>
</a-space>
</a-doption>
<a-doption>
<a-space @click="$router.push({ name: 'Setting' })">
<icon-settings />
<span>
{{ t('messageBox.userSettings') }}
</span>
</a-space>
</a-doption>
<a-doption>
<a-space @click="handleLogout">
<icon-export />
<span>
{{ t('messageBox.logout') }}
</span>
</a-space>
</a-doption>
</template>
</a-dropdown>
</li> -->
</ul>
</div>
</template>
@ -181,19 +121,17 @@
import { computed, onBeforeMount, Ref, ref, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
// import useUser from '@/hooks/useUser';
import TopMenu from '@/components/business/ms-top-menu/index.vue';
import MessageBox from '../message-box/index.vue';
import { getProjectList } from '@/api/modules/project-management/project';
import { getProjectList, switchProject } from '@/api/modules/project-management/project';
import { MENU_LEVEL, type PathMapRoute } from '@/config/pathMap';
import { useI18n } from '@/hooks/useI18n';
import usePathMap from '@/hooks/usePathMap';
import { LOCALE_OPTIONS } from '@/locale';
import useLocale from '@/locale/useLocale';
// import { Message } from '@arco-design/web-vue';
// import { useFullscreen } from '@vueuse/core';
import { useAppStore } from '@/store';
import useAppStore from '@/store/modules/app';
import useUserStore from '@/store/modules/user';
import type { ProjectListItem } from '@/models/setting/project';
@ -206,7 +144,7 @@
}>();
const appStore = useAppStore();
// const { logout } = useUser();
const userStore = useUserStore();
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
@ -240,10 +178,19 @@
return getRouteLevelByKey(route.name as PathMapRoute) === MENU_LEVEL[2];
});
function selectProject(
async function selectProject(
value: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
) {
appStore.setCurrentProjectId(value as string);
try {
await switchProject({
projectId: value as string,
userId: userStore.id || '',
});
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
router.replace({
path: route.path,
query: {
@ -253,6 +200,7 @@
},
});
}
}
const helpCenterList = [
{
@ -278,14 +226,8 @@
];
const { changeLocale, currentLocale } = useLocale();
// const { isFullscreen, toggle: toggleFullScreen } = useFullscreen();
const locales = [...LOCALE_OPTIONS];
// const avatar = computed(() => {
// return userStore.avatar;
// });
// const setVisible = () => {
// appStore.updateSettings({ globalSettings: true });
// };
const refBtn = ref();
const setPopoverVisible = () => {
const event = new MouseEvent('click', {
@ -295,13 +237,6 @@
});
refBtn.value.dispatchEvent(event);
};
// const handleLogout = () => {
// logout();
// };
// const switchRoles = async () => {
// const res = await userStore.switchRoles();
// Message.success(res as string);
// };
</script>
<style scoped lang="less">

View File

@ -92,4 +92,7 @@ export default {
'common.file': 'File',
'common.desc': 'Description',
'common.root': 'Default Module',
'common.revoke': 'Revoke',
'common.clear': 'Clear',
'common.tag': 'Tag',
};

View File

@ -93,4 +93,7 @@ export default {
'common.file': '文件',
'common.desc': '描述',
'common.root': '默认模块',
'common.revoke': '撤销',
'common.clear': '清空',
'common.tag': '标签',
};

View File

@ -1,9 +1,10 @@
<template>
<MsBaseTable v-bind="propsRes" id="headerTable" :hoverable="false" v-on="propsEvent">
<MsBaseTable v-bind="propsRes" id="headerTable" :hoverable="false" no-disable v-on="propsEvent">
<!-- 表格头 slot -->
<template #encodeTitle>
<div class="flex items-center text-[var(--color-text-3)]">
{{ t('apiTestDebug.encode') }}
<a-tooltip>
<a-tooltip position="right">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
size="16"
@ -15,6 +16,18 @@
</a-tooltip>
</div>
</template>
<template #typeTitle="{ columnConfig }">
<div class="flex items-center text-[var(--color-text-3)]">
{{ t('apiTestDebug.paramType') }}
<a-tooltip :content="columnConfig.typeTitleTooltip" position="right">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
</a-tooltip>
</div>
</template>
<!-- 表格列 slot -->
<template #name="{ record }">
<a-popover position="tl" :disabled="!record.name || record.name.trim() === ''" class="ms-params-input-popover">
<template #content>
@ -33,8 +46,11 @@
/>
</a-popover>
</template>
<template #type="{ record }">
<a-tooltip :content="t(record.required ? 'apiTestDebug.paramRequired' : 'apiTestDebug.paramNotRequired')">
<template #type="{ record, columnConfig }">
<a-tooltip
v-if="columnConfig.hasRequired"
:content="t(record.required ? 'apiTestDebug.paramRequired' : 'apiTestDebug.paramNotRequired')"
>
<MsButton
type="icon"
:class="[
@ -48,13 +64,35 @@
</a-tooltip>
<a-select
v-model:model-value="record.type"
:options="typeOptions"
:options="columnConfig.typeOptions || []"
class="param-input"
@change="(val) => handleTypeChange(val, record)"
></a-select>
/>
</template>
<template #value="{ record }">
<template #value="{ record, columnConfig }">
<a-popover
v-if="columnConfig.isNormal"
position="tl"
:disabled="!record.value || record.value.trim() === ''"
class="ms-params-input-popover"
>
<template #content>
<div class="param-popover-title">
{{ t('apiTestDebug.paramValue') }}
</div>
<div class="param-popover-value">
{{ record.value }}
</div>
</template>
<a-input
v-model:model-value="record.value"
class="param-input"
:placeholder="t('apiTestDebug.commonPlaceholder')"
@input="(val) => addTableLine(val)"
/>
</a-popover>
<MsParamsInput
v-else
v-model:value="record.value"
@change="addTableLine"
@dblclick="quickInputParams(record)"
@ -78,6 +116,19 @@
></a-input-number>
</div>
</template>
<template #tag="{ record }">
<a-popover position="tl" :disabled="record.tag.length === 0" class="ms-params-input-popover">
<template #content>
<div class="param-popover-title">
{{ t('common.tag') }}
</div>
<div class="param-popover-value">
<MsTagsGroup is-string-tag :tag-list="record.tag" />
</div>
</template>
<MsTagsInput v-model:model-value="record.tag" :max-tag-count="1" class="param-input" @change="addTableLine" />
</a-popover>
</template>
<template #desc="{ record }">
<paramDescInput
v-model:desc="record.desc"
@ -91,12 +142,16 @@
v-model:model-value="record.encode"
size="small"
class="param-input-switch"
type="line"
@change="(val) => addTableLine(val.toString())"
></a-switch>
/>
</template>
<template #operation="{ record, rowIndex }">
<template #mustContain="{ record }">
<a-checkbox v-model:model-value="record.mustContain" @change="(val) => addTableLine(val)" />
</template>
<template #operation="{ record, rowIndex, columnConfig }">
<a-trigger
v-if="props.format && props.format !== RequestBodyFormat.X_WWW_FORM_URLENCODED"
v-if="columnConfig.format && columnConfig.format !== RequestBodyFormat.X_WWW_FORM_URLENCODED"
trigger="click"
position="br"
>
@ -109,10 +164,17 @@
:options="Object.values(RequestContentTypeEnum).map((e) => ({ label: e, value: e }))"
allow-create
@change="(val) => addTableLine(val as string)"
></a-select>
/>
</div>
</template>
</a-trigger>
<a-switch
v-if="columnConfig.hasEnable"
v-model:model-value="record.enable"
size="small"
type="line"
@change="(val) => addTableLine(val)"
/>
<icon-minus-circle
v-if="paramsLength > 1 && rowIndex !== paramsLength - 1"
class="cursor-pointer text-[var(--color-text-4)]"
@ -120,14 +182,12 @@
@click="deleteParam(rowIndex)"
/>
</template>
<template #mustContain="{ record }">
<a-checkbox v-model:model-value="record.mustContain"></a-checkbox>
</template>
</MsBaseTable>
<a-modal
v-model:visible="showQuickInputParam"
:title="t('ms.paramsInput.value')"
:ok-text="t('apiTestDebug.apply')"
:ok-button-props="{ disabled: !quickInputParamValue || quickInputParamValue.trim() === '' }"
class="ms-modal-form"
body-class="!p-0"
:width="680"
@ -177,14 +237,18 @@
import MsButton from '@/components/pure/ms-button/index.vue';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import type { MsTableColumnData } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsTagsGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import MsParamsInput from '@/components/business/ms-params-input/index.vue';
import paramDescInput from './paramDescInput.vue';
import { useI18n } from '@/hooks/useI18n';
import useTableStore from '@/hooks/useTableStore';
import { RequestBodyFormat, RequestContentTypeEnum } from '@/enums/apiEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
interface Param {
id: number;
@ -197,13 +261,26 @@
contentType: RequestContentTypeEnum;
desc: string;
encode: boolean;
tag: string[];
enable: boolean;
mustContain: boolean;
[key: string]: any;
}
const props = defineProps<{
export type ParamTableColumn = MsTableColumnData & {
isNormal?: boolean; // value MsParamsInput
hasRequired?: boolean; // type required
typeOptions?: { label: string; value: string }[]; // type
typeTitleTooltip?: string; // type tooltip
hasEnable?: boolean; // operation enable
format?: RequestBodyFormat | 'query' | 'rest'; // operation
};
const props = withDefaults(
defineProps<{
params: any[];
columns: MsTableColumn;
format?: RequestBodyFormat | 'query' | 'rest';
defaultParamItem?: Partial<Param>; //
columns: ParamTableColumn[];
scroll?: {
x?: number | string;
y?: number | string;
@ -211,15 +288,18 @@
minWidth?: number | string;
};
heightUsed?: number;
}>();
const emit = defineEmits<{
(e: 'update:params', value: any[]): void;
(e: 'change', data: any[], isInit?: boolean): void;
}>();
const { t } = useI18n();
const defaultParams: Omit<Param, 'id'> = {
draggable?: boolean;
selectable?: boolean;
showSetting?: boolean; //
tableKey?: TableKeyEnum; // key showSettingtrue
disabled?: boolean; //
showSelectorAll?: boolean; //
}>(),
{
selectable: true,
showSetting: false,
tableKey: undefined,
defaultParamItem: () => ({
required: false,
name: '',
type: 'string',
@ -227,52 +307,36 @@
min: undefined,
max: undefined,
contentType: RequestContentTypeEnum.TEXT,
tag: [],
desc: '',
encode: false,
};
const allType = [
{
label: 'string',
value: 'string',
},
{
label: 'integer',
value: 'integer',
},
{
label: 'number',
value: 'number',
},
{
label: 'array',
value: 'array',
},
{
label: 'json',
value: 'json',
},
{
label: 'file',
value: 'file',
},
];
const typeOptions = computed(() => {
if (
props.format === RequestBodyFormat.X_WWW_FORM_URLENCODED ||
props.format === 'query' ||
props.format === 'rest'
) {
return allType.filter((e) => e.value !== 'file' && e.value !== 'json');
enable: false,
mustContain: false,
}),
}
);
const emit = defineEmits<{
(e: 'update:params', value: any[]): void;
(e: 'change', data: any[], isInit?: boolean): void;
}>();
const { t } = useI18n();
const tableStore = useTableStore();
if (props.showSetting && props.tableKey) {
await tableStore.initColumn(props.tableKey, props.columns);
}
return allType;
});
const { propsRes, propsEvent } = useTable(() => Promise.resolve([]), {
tableKey: props.showSetting ? props.tableKey : undefined,
scroll: props.scroll,
heightUsed: props.heightUsed,
columns: props.columns,
selectable: true,
draggable: { type: 'handle', width: 24 },
selectable: props.selectable,
draggable: props.draggable ? { type: 'handle', width: 24 } : undefined,
showSetting: props.showSetting,
disabled: props.disabled,
showSelectorAll: props.showSelectorAll,
});
watch(
@ -282,16 +346,8 @@
propsRes.value.data = val;
} else {
propsRes.value.data = props.params.concat({
id: new Date().getTime(),
required: false,
name: '',
type: 'string',
value: '',
min: undefined,
max: undefined,
contentType: RequestContentTypeEnum.TEXT,
desc: '',
encode: false,
id: new Date().getTime(), // id props.defaultParamItem id
...props.defaultParamItem,
});
emit('change', propsRes.value.data, true);
}
@ -320,13 +376,15 @@
* @param val 输入值
* @param isForce 是否强制添加
*/
function addTableLine(val?: string | number, isForce?: boolean) {
function addTableLine(val?: string | number | boolean | (string | number | boolean)[], isForce?: boolean) {
const lastData = propsRes.value.data[propsRes.value.data.length - 1];
const isNotChange = Object.keys(defaultParams).every((key) => lastData[key] === defaultParams[key as any]);
const isNotChange = Object.keys(props.defaultParamItem).every(
(key) => JSON.stringify(lastData[key]) === JSON.stringify(props.defaultParamItem[key])
);
if (isForce || (val !== '' && val !== undefined && !isNotChange)) {
propsRes.value.data.push({
id: new Date().getTime(),
...defaultParams,
...props.defaultParamItem,
} as any);
emit('change', propsRes.value.data);
}
@ -387,9 +445,11 @@
function handleTypeChange(
val: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[],
record: Param
record: Partial<Param>
) {
addTableLine(val as string);
// Content-Type
if (record.contentType) {
if (val === 'file') {
record.contentType = RequestContentTypeEnum.OCTET_STREAM;
} else if (val === 'json') {
@ -398,6 +458,7 @@
record.contentType = RequestContentTypeEnum.TEXT;
}
}
}
</script>
<style lang="less" scoped>
@ -424,9 +485,6 @@
}
}
}
.param-input-switch:not(:hover).arco-switch-checked {
background-color: rgb(var(--primary-3)) !important;
}
.content-type-trigger-content {
@apply bg-white;

View File

@ -8,20 +8,14 @@
<a-radio value="digest">Digest Auth</a-radio>
</a-radio-group>
<a-form v-if="authForm.authType !== 'none'" ref="authFormRef" :model="authForm" layout="vertical">
<a-form-item
:label="t('apiTestDebug.account')"
:rules="[{ required: true, message: t('apiTestDebug.accountRequired') }]"
>
<a-form-item :label="t('apiTestDebug.account')">
<a-input
v-model:model-value="authForm.account"
:placeholder="t('apiTestDebug.commonPlaceholder')"
class="w-[450px]"
/>
</a-form-item>
<a-form-item
:label="t('apiTestDebug.password')"
:rules="[{ required: true, message: t('apiTestDebug.passwordRequired') }]"
>
<a-form-item :label="t('apiTestDebug.password')">
<a-input-password
v-model:model-value="authForm.password"
autocomplete="new-password"

View File

@ -16,7 +16,7 @@
v-model:model-value="batchParamsCode"
class="flex-1"
theme="MS-text"
height="calc(100% - 48px)"
height="calc(100% - 12px)"
:show-full-screen="false"
>
<template #title>

View File

@ -18,7 +18,6 @@
v-else-if="showParamTable"
v-model:params="currentTableParams"
:scroll="{ minWidth: 1160 }"
:format="format"
:columns="columns"
:height-used="heightUsed"
@change="handleParamTableChange"
@ -33,7 +32,7 @@
/>
</div>
<div class="flex items-center">
<a-switch v-model:model-value="innerParams.binarySend" class="mr-[8px]" size="small"></a-switch>
<a-switch v-model:model-value="innerParams.binarySend" class="mr-[8px]" size="small" type="line"></a-switch>
<span>{{ t('apiTestDebug.sendAsMainText') }}</span>
<a-tooltip position="right">
<template #content>
@ -52,7 +51,7 @@
v-model:model-value="currentBodyCode"
class="flex-1"
theme="vs-dark"
height="calc(100% - 48px)"
height="calc(100% - 12px)"
:show-full-screen="false"
:language="currentCodeLanguage"
>
@ -74,8 +73,7 @@
import { useVModel } from '@vueuse/core';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import paramTable from '../../../components/paramTable.vue';
import paramTable, { type ParamTableColumn } from '../../../components/paramTable.vue';
import batchAddKeyVal from './batchAddKeyVal.vue';
import { useI18n } from '@/hooks/useI18n';
@ -107,7 +105,7 @@
const innerParams = useVModel(props, 'params', emit);
const columns: MsTableColumn = [
const columns: ParamTableColumn[] = [
{
title: 'apiTestDebug.paramName',
dataIndex: 'name',
@ -117,6 +115,33 @@
title: 'apiTestDebug.paramType',
dataIndex: 'type',
slotName: 'type',
hasRequired: true,
typeOptions: [
{
label: 'string',
value: 'string',
},
{
label: 'integer',
value: 'integer',
},
{
label: 'number',
value: 'number',
},
{
label: 'array',
value: 'array',
},
{
label: 'json',
value: 'json',
},
{
label: 'file',
value: 'file',
},
],
width: 120,
},
{
@ -146,7 +171,7 @@
title: '',
slotName: 'operation',
fixed: 'right',
width: 80,
width: 50,
},
];

View File

@ -15,8 +15,7 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import paramTable from '../../../components/paramTable.vue';
import paramTable, { ParamTableColumn } from '../../../components/paramTable.vue';
import batchAddKeyVal from './batchAddKeyVal.vue';
import { useI18n } from '@/hooks/useI18n';
@ -35,7 +34,7 @@
const innerParams = useVModel(props, 'params', emit);
const columns: MsTableColumn = [
const columns: ParamTableColumn[] = [
{
title: 'apiTestDebug.paramName',
dataIndex: 'name',

View File

@ -59,7 +59,7 @@
</div>
</div>
</div>
<div ref="splitContainerRef" class="flex-1">
<div ref="splitContainerRef" class="h-[calc(100%-125px)]">
<MsSplitBox
ref="splitBoxRef"
v-model:size="splitBoxSize"
@ -69,7 +69,7 @@
@expand-change="handleExpandChange"
>
<template #first>
<div :class="`h-full min-w-[700px] px-[24px] pb-[16px] ${activeLayout === 'horizontal' ? ' pr-[16px]' : ''}`">
<div :class="`h-full min-w-[800px] px-[24px] pb-[16px] ${activeLayout === 'horizontal' ? ' pr-[16px]' : ''}`">
<a-tabs v-model:active-key="activeDebug.activeTab" class="no-content">
<a-tab-pane v-for="item of contentTabList" :key="item.value" :title="item.label" />
</a-tabs>
@ -102,6 +102,13 @@
:second-box-height="secondBoxHeight"
@change="handleActiveDebugChange"
/>
<precondition
v-else-if="activeDebug.activeTab === RequestComposition.PREFIX"
v-model:params="activeDebug.preconditions"
:layout="activeLayout"
:second-box-height="secondBoxHeight"
@change="handleActiveDebugChange"
/>
<debugAuth
v-else-if="activeDebug.activeTab === RequestComposition.AUTH"
v-model:params="activeDebug.authParams"
@ -159,6 +166,7 @@
import debugAuth from './auth.vue';
import debugBody, { BodyParams } from './body.vue';
import debugHeader from './header.vue';
import precondition from './precondition.vue';
import debugQuery from './query.vue';
import debugRest from './rest.vue';
import debugSetting from './setting.vue';
@ -201,6 +209,7 @@
account: '',
password: '',
},
preconditions: [],
setting: {
connectTimeout: 60000,
responseTimeout: 60000,

View File

@ -0,0 +1,597 @@
<template>
<div class="mb-[8px] flex items-center justify-between">
<a-dropdown @select="addPrecondition">
<a-button type="outline">
<template #icon>
<icon-plus :size="14" />
</template>
{{ t('apiTestDebug.precondition') }}
</a-button>
<template #content>
<a-doption value="script">{{ t('apiTestDebug.script') }}</a-doption>
<a-doption value="sql">{{ t('apiTestDebug.sql') }}</a-doption>
<a-doption value="waitTime">{{ t('apiTestDebug.waitTime') }}</a-doption>
</template>
</a-dropdown>
<div class="flex items-center">
<a-switch v-model:model-value="openGlobalPrecondition" size="small" type="line"></a-switch>
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.openGlobalPrecondition') }}</div>
<a-tooltip :content="t('apiTestDebug.openGlobalPreconditionTip')" position="top">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
</a-tooltip>
</div>
</div>
<div v-show="preconditions.length > 0" class="flex h-[calc(100%-110px)] gap-[8px]">
<div class="h-full w-[20%] min-w-[220px]">
<MsList
v-model:active-item-key="activeItem.id"
v-model:focus-item-key="focusItemKey"
v-model:data="preconditions"
mode="static"
item-key-field="id"
:item-border="false"
class="h-full rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[12px]"
item-class="mb-[4px] bg-white !p-[4px_8px]"
:item-more-actions="itemMoreActions"
active-item-class="!bg-[rgb(var(--primary-1))] text-[rgb(var(--primary-5))]"
:virtual-list-props="{ height: '100%', fixedSize: true }"
draggable
@item-click="handlePreconditionItemClick"
@more-action-select="handlePreconditionMoreActionSelect"
@more-actions-close="focusItemKey = ''"
>
<template #title="{ item, index }">
<div class="flex items-center gap-[4px]">
<div
:class="`flex h-[16px] w-[16px] items-center justify-center rounded-full ${
activeItem.id === item.id ? ' bg-white' : 'bg-[var(--color-text-n8)]'
}`"
>
{{ index + 1 }}
</div>
<div>{{ typeMap[item.type] }}</div>
</div>
</template>
<template #itemRight="{ item }">
<a-switch v-model:model-value="item.enable" size="small" type="line"></a-switch>
</template>
</MsList>
</div>
<div class="precondition-content">
<!-- 前置条件-脚本操作 -->
<template v-if="activeItem.type === 'script'">
<a-radio-group v-model:model-value="activeItem.scriptType" size="small" class="mb-[16px]">
<a-radio value="manual">{{ t('apiTestDebug.manual') }}</a-radio>
<a-radio value="quote">{{ t('apiTestDebug.quote') }}</a-radio>
</a-radio-group>
<div
v-if="activeItem.scriptType === 'manual'"
class="relative rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[12px]"
>
<div v-if="isShowEditScriptNameInput" class="absolute left-[12px] z-10 w-[calc(100%-24px)]">
<a-input
ref="scriptNameInputRef"
v-model:model-value="activeItem.name"
:placeholder="t('apiTestDebug.preconditionScriptNamePlaceholder')"
:max-length="255"
show-word-limit
size="small"
@press-enter="isShowEditScriptNameInput = false"
@blur="isShowEditScriptNameInput = false"
/>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">
<a-tooltip :content="activeItem.name">
<div class="script-name-container">
<div class="one-line-text mr-[4px] max-w-[110px] font-medium text-[var(--color-text-1)]">
{{ activeItem.name }}
</div>
<MsIcon
type="icon-icon_edit_outlined"
class="edit-script-name-icon"
@click="showEditScriptNameInput"
/>
</div>
</a-tooltip>
<a-popover class="h-auto" position="top">
<div class="text-[rgb(var(--primary-5))]">{{ t('apiTestDebug.scriptEx') }}</div>
<template #content>
<div class="mb-[8px] flex items-center justify-between">
<div class="text-[14px] font-medium text-[var(--color-text-1)]">
{{ t('apiTestDebug.scriptEx') }}
</div>
<a-button
type="outline"
class="arco-btn-outline--secondary p-[0_8px]"
size="mini"
@click="copyScriptEx"
>
{{ t('common.copy') }}
</a-button>
</div>
<div class="flex h-[412px]">
<MsCodeEditor
v-model:model-value="scriptEx"
class="flex-1"
theme="MS-text"
width="500px"
height="388px"
:show-full-screen="false"
:show-theme-change="false"
read-only
>
</MsCodeEditor>
</div>
</template>
</a-popover>
</div>
<div class="flex items-center gap-[8px]">
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini">
<template #icon>
<MsIcon type="icon-icon_undo_outlined" class="text-var(--color-text-4)" size="12" />
</template>
{{ t('common.revoke') }}
</a-button>
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini" @click="clearScript">
<template #icon>
<MsIcon type="icon-icon_clear" class="text-var(--color-text-4)" size="12" />
</template>
{{ t('common.clear') }}
</a-button>
<a-button
type="outline"
class="arco-btn-outline--secondary p-[0_8px]"
size="mini"
@click="() => copyPrecondition(activeItem)"
>
{{ t('common.copy') }}
</a-button>
<a-button
type="outline"
class="arco-btn-outline--secondary p-[0_8px]"
size="mini"
@click="() => deletePrecondition(activeItem)"
>
{{ t('common.delete') }}
</a-button>
</div>
</div>
</div>
<div v-else class="flex h-[calc(100%-47px)] flex-col">
<div class="mb-[16px] flex w-full items-center bg-[var(--color-text-n9)] p-[12px]">
<div class="text-[var(--color-text-2)]">
{{ activeItem.quoteScript.name || '-' }}
</div>
<a-divider margin="8px" direction="vertical" />
<MsButton type="text" class="font-medium">
{{ t('apiTestDebug.quote') }}
</MsButton>
</div>
<a-radio-group v-model:model-value="commonScriptShowType" size="small" type="button" class="mb-[8px] w-fit">
<a-radio value="parameters">{{ t('apiTestDebug.parameters') }}</a-radio>
<a-radio value="scriptContent">{{ t('apiTestDebug.scriptContent') }}</a-radio>
</a-radio-group>
<MsBaseTable v-show="commonScriptShowType === 'parameters'" v-bind="propsRes" v-on="propsEvent">
<template #value="{ record }">
<a-tooltip :content="t(record.required ? 'apiTestDebug.paramRequired' : 'apiTestDebug.paramNotRequired')">
<div
:class="[
record.required ? '!text-[rgb(var(--danger-5))]' : '!text-[var(--color-text-brand)]',
'!mr-[4px] !p-[4px]',
]"
>
<div>*</div>
</div>
</a-tooltip>
{{ record.type }}
</template>
</MsBaseTable>
<div v-show="commonScriptShowType === 'scriptContent'" class="h-[calc(100%-76px)]">
<MsCodeEditor
v-model:model-value="activeItem.quoteScript.script"
theme="MS-text"
height="100%"
:show-full-screen="false"
:show-theme-change="false"
read-only
>
</MsCodeEditor>
</div>
</div>
</template>
<!-- 前置条件-SQL操作 -->
<template v-else-if="activeItem.type === 'sql'">
<div class="mb-[16px]">
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('common.desc') }}</div>
<a-input
v-model:model-value="activeItem.desc"
:placeholder="t('apiTestDebug.commonPlaceholder')"
:max-length="255"
show-word-limit
/>
</div>
<div class="mb-[16px] flex w-full items-center bg-[var(--color-text-n9)] p-[12px]">
<div class="text-[var(--color-text-2)]">
{{ activeItem.sqlSource.name || '-' }}
</div>
<a-divider margin="8px" direction="vertical" />
<MsButton type="text" class="font-medium">
{{ t('apiTestDebug.quoteSource') }}
</MsButton>
</div>
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.sqlScript') }}</div>
<div class="mb-[16px] h-[400px]">
<MsCodeEditor
v-model:model-value="activeItem.sqlSource.script"
theme="MS-text"
height="376px"
:show-full-screen="false"
:show-theme-change="false"
read-only
>
</MsCodeEditor>
</div>
<div class="mb-[16px]">
<div class="mb-[8px] flex items-center text-[var(--color-text-1)]">
{{ t('apiTestDebug.storageType') }}
<a-tooltip position="right">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
<template #content>
<div>{{ t('apiTestDebug.storageTypeTip1') }}</div>
<div>{{ t('apiTestDebug.storageTypeTip2') }}</div>
</template>
</a-tooltip>
</div>
<a-radio-group
v-model:model-value="activeItem.sqlSource.storageType"
size="small"
type="button"
class="w-fit"
>
<a-radio value="column">{{ t('apiTestDebug.storageByCol') }}</a-radio>
<a-radio value="result">{{ t('apiTestDebug.storageByResult') }}</a-radio>
</a-radio-group>
</div>
<div v-if="activeItem.sqlSource.storageType === 'column'" class="mb-[16px]">
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.storageByCol') }}</div>
<a-input
v-model:model-value="activeItem.sqlSource.storageByCol"
:placeholder="t('apiTestDebug.storageByColPlaceholder', { a: '{id_1}', b: '{username_1}' })"
/>
</div>
<div v-else class="mb-[16px]">
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.storageByResult') }}</div>
<a-input
v-model:model-value="activeItem.sqlSource.storageByResult"
:placeholder="t('apiTestDebug.storageByResultPlaceholder', { a: '${result}' })"
/>
</div>
<div class="mb-[16px]">
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('apiTestDebug.extractParameter') }}</div>
<paramTable
v-model:params="activeItem.sqlSource.params"
:columns="sqlSourceColumns"
:selectable="false"
@change="handleParamTableChange"
/>
</div>
</template>
<!-- 前置条件-等待时间 -->
<div v-else>
<div class="mb-[8px] flex items-center">
{{ t('apiTestDebug.waitTime') }}
<div class="text-[var(--color-text-4)]">(ms)</div>
</div>
<a-input-number v-model:model-value="activeItem.time" mode="button" :step="100" :min="0" class="w-[160px]" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useClipboard, useVModel } from '@vueuse/core';
import { InputInstance, Message } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsList from '@/components/pure/ms-list/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import paramTable, { type ParamTableColumn } from '../../../components/paramTable.vue';
import { useI18n } from '@/hooks/useI18n';
const props = defineProps<{
params: any[];
layout: 'horizontal' | 'vertical';
secondBoxHeight: number;
}>();
const emit = defineEmits<{
(e: 'update:params', params: any[]): void;
(e: 'change'): void;
}>();
const { t } = useI18n();
//
const openGlobalPrecondition = ref(false);
const preconditions = useVModel(props, 'params', emit);
//
const focusItemKey = ref<any>('');
//
const activeItem = ref(preconditions.value[0] || {});
const typeMap = {
script: t('apiTestDebug.script'),
sql: t('apiTestDebug.sql'),
waitTime: t('apiTestDebug.waitTime'),
};
const itemMoreActions: ActionsItem[] = [
{
label: 'common.copy',
eventTag: 'copy',
},
{
label: 'project.fileManagement.delete',
eventTag: 'delete',
},
];
const scriptEx = ref(`2023-12-04 11:19:28 INFO 9026fd6a 1-1 Thread started: 9026fd6a 1-1
2023-12-04 11:19:28 ERROR 9026fd6a 1-1 Problem in JSR223 script JSR223Sampler, message: {}
In file: inline evaluation of: prev.getResponseCode() import java.net.URI; import org.apache.http.client.method . . . '' Encountered "import" at line 2, column 1.
in inline evaluation of: prev.getResponseCode() import java.net.URI; import org.apache.http.client.method . . . '' at line number 2
javax.script.ScriptException '' at line number 2
javax.script.ScriptException '' at line number 2
javax.script.ScriptException '' at line number 2
javax.script.ScriptException '' at line number 2
javax.script.ScriptException '' at line number 2
javax.script.ScriptException
org.apache.http.client.method . . . '' at line number 2
`);
const { copy, isSupported } = useClipboard();
function copyScriptEx() {
if (isSupported) {
copy(scriptEx.value);
Message.success(t('apiTestDebug.scriptExCopySuccess'));
} else {
Message.warning(t('apiTestDebug.copyNotSupport'));
}
}
/**
* 添加前置条件
* @param value script | sql | waitTime
*/
function addPrecondition(value: string | number | Record<string, any> | undefined) {
const id = new Date().getTime();
switch (value) {
case 'script':
preconditions.value.push({
id,
type: 'script',
name: t('apiTestDebug.preconditionScriptName'),
scriptType: 'manual',
enable: true,
script: '',
quoteScript: {
name: '',
script: scriptEx,
},
});
break;
case 'sql':
preconditions.value.push({
id,
type: 'sql',
desc: '',
enable: true,
sqlSource: {
name: '',
script: scriptEx,
storageType: 'column',
params: [],
},
});
break;
case 'waitTime':
preconditions.value.push({
id,
type: 'waitTime',
enable: true,
time: 1000,
});
break;
default:
break;
}
activeItem.value = preconditions.value[preconditions.value.length - 1];
emit('change');
}
function handlePreconditionItemClick(item: any) {
activeItem.value = item;
}
/**
* 复制前置条件
* @param item 前置条件项
*/
function copyPrecondition(item: Record<string, any>) {
const copyItem = {
...item,
id: new Date().getTime(),
};
preconditions.value.push(copyItem);
activeItem.value = copyItem;
emit('change');
}
function deletePrecondition(item: Record<string, any>) {
preconditions.value = preconditions.value.filter((precondition) => precondition.id !== item.id);
if (activeItem.value.id === item.id) {
[activeItem.value] = preconditions.value;
}
emit('change');
}
/**
* 前置条件列表项-选择更多操作项
* @param event
* @param item
*/
function handlePreconditionMoreActionSelect(event: ActionsItem, item: Record<string, any>) {
if (event.eventTag === 'copy') {
copyPrecondition(item);
} else if (event.eventTag === 'delete') {
deletePrecondition(item);
}
}
function clearScript() {
activeItem.value.script = '';
}
//
const isShowEditScriptNameInput = ref(false);
const scriptNameInputRef = ref<InputInstance>();
function showEditScriptNameInput() {
isShowEditScriptNameInput.value = true;
nextTick(() => {
scriptNameInputRef.value?.focus();
});
}
const commonScriptShowType = ref<'parameters' | 'scriptContent'>('parameters');
const heightUsed = ref<number | undefined>(undefined);
const scroll = computed(() => (props.layout === 'horizontal' ? { x: '700px' } : { x: '100%' }));
const columns: MsTableColumn = [
{
title: 'apiTestDebug.paramName',
dataIndex: 'name',
showTooltip: true,
},
{
title: 'apiTestDebug.paramValue',
dataIndex: 'value',
slotName: 'value',
},
{
title: 'apiTestDebug.desc',
dataIndex: 'desc',
showTooltip: true,
},
];
const { propsRes, propsEvent } = useTable(() => Promise.resolve([]), {
scroll: scroll.value,
heightUsed: heightUsed.value,
columns,
});
propsRes.value.data = [
{
id: new Date().getTime(),
required: false,
name: 'asdasd',
type: 'string',
value: '',
desc: '',
},
{
id: new Date().getTime(),
required: true,
name: '23d23d',
type: 'string',
value: '',
desc: '',
},
] as any;
watch(
() => props.layout,
(val) => {
heightUsed.value = val === 'horizontal' ? 422 : 422 + props.secondBoxHeight;
},
{
immediate: true,
}
);
watch(
() => props.secondBoxHeight,
(val) => {
if (props.layout === 'vertical') {
heightUsed.value = 422 + val;
}
},
{
immediate: true,
}
);
const sqlSourceColumns: ParamTableColumn[] = [
{
title: 'apiTestDebug.paramName',
dataIndex: 'name',
slotName: 'name',
},
{
title: 'apiTestDebug.paramValue',
dataIndex: 'value',
slotName: 'value',
isNormal: true,
},
{
title: '',
slotName: 'operation',
width: 50,
},
];
function handleParamTableChange(resultArr: any[], isInit?: boolean) {
activeItem.value.sqlSource.params = [...resultArr];
if (!isInit) {
emit('change');
}
}
</script>
<style lang="less" scoped>
.precondition-content {
@apply flex-1 overflow-y-auto;
.ms-scroll-bar();
padding: 16px;
border: 1px solid rgb(var(--color-text-n8));
border-radius: var(--border-radius-small);
}
.script-name-container {
@apply flex items-center;
margin-right: 16px;
&:hover {
.edit-script-name-icon {
@apply visible;
}
}
.edit-script-name-icon {
@apply invisible cursor-pointer;
color: rgb(var(--primary-5));
}
}
:deep(.arco-table-th) {
background-color: var(--color-text-n9);
}
:deep(.arco-table-cell) {
padding: 16px 12px;
}
</style>

View File

@ -16,7 +16,6 @@
:columns="columns"
:height-used="heightUsed"
:scroll="{ minWidth: 1160 }"
format="query"
@change="handleParamTableChange"
/>
</template>
@ -24,8 +23,7 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import paramTable from '../../../components/paramTable.vue';
import paramTable, { type ParamTableColumn } from '../../../components/paramTable.vue';
import batchAddKeyVal from './batchAddKeyVal.vue';
import { useI18n } from '@/hooks/useI18n';
@ -44,7 +42,7 @@
const innerParams = useVModel(props, 'params', emit);
const columns: MsTableColumn = [
const columns: ParamTableColumn[] = [
{
title: 'apiTestDebug.paramName',
dataIndex: 'name',
@ -54,6 +52,25 @@
title: 'apiTestDebug.paramType',
dataIndex: 'type',
slotName: 'type',
hasRequired: true,
typeOptions: [
{
label: 'string',
value: 'string',
},
{
label: 'integer',
value: 'integer',
},
{
label: 'number',
value: 'number',
},
{
label: 'array',
value: 'array',
},
],
width: 120,
},
{
@ -82,7 +99,8 @@
title: '',
slotName: 'operation',
fixed: 'right',
width: 50,
format: 'query',
width: 80,
},
];

View File

@ -16,7 +16,6 @@
:columns="columns"
:height-used="heightUsed"
:scroll="{ minWidth: 1160 }"
format="query"
@change="handleParamTableChange"
/>
</template>
@ -24,8 +23,7 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import paramTable from '../../../components/paramTable.vue';
import paramTable, { type ParamTableColumn } from '../../../components/paramTable.vue';
import batchAddKeyVal from './batchAddKeyVal.vue';
import { useI18n } from '@/hooks/useI18n';
@ -44,7 +42,7 @@
const innerParams = useVModel(props, 'params', emit);
const columns: MsTableColumn = [
const columns: ParamTableColumn[] = [
{
title: 'apiTestDebug.paramName',
dataIndex: 'name',
@ -54,6 +52,25 @@
title: 'apiTestDebug.paramType',
dataIndex: 'type',
slotName: 'type',
hasRequired: true,
typeOptions: [
{
label: 'string',
value: 'string',
},
{
label: 'integer',
value: 'integer',
},
{
label: 'number',
value: 'number',
},
{
label: 'array',
value: 'array',
},
],
width: 120,
},
{
@ -82,7 +99,8 @@
title: '',
slotName: 'operation',
fixed: 'right',
width: 50,
format: 'query',
width: 80,
},
];

View File

@ -11,7 +11,13 @@
<div class="text-[var(--color-text-brand)]">(ms)</div>
</div>
</template>
<a-input-number v-model:model-value="settingForm.connectTimeout" mode="button" class="w-[160px]" />
<a-input-number
v-model:model-value="settingForm.connectTimeout"
mode="button"
:step="100"
:min="0"
class="w-[160px]"
/>
</a-form-item>
<a-form-item>
<template #label>
@ -20,7 +26,13 @@
<div class="text-[var(--color-text-brand)]">(ms)</div>
</div>
</template>
<a-input-number v-model:model-value="settingForm.responseTimeout" mode="button" class="w-[160px]" />
<a-input-number
v-model:model-value="settingForm.responseTimeout"
mode="button"
:step="100"
:min="0"
class="w-[160px]"
/>
</a-form-item>
<a-form-item :label="t('apiTestDebug.certificateAlias')">
<a-input

View File

@ -54,4 +54,31 @@ export default {
'apiTestDebug.redirect': '重定向',
'apiTestDebug.follow': '跟随',
'apiTestDebug.auto': '自动',
'apiTestDebug.precondition': '前置条件',
'apiTestDebug.openGlobalPrecondition': '启用全局前置',
'apiTestDebug.openGlobalPreconditionTip': '默认开启,关闭则运行该接口时不执行全局前置',
'apiTestDebug.sql': 'SQL 操作',
'apiTestDebug.sqlScript': 'SQL 脚本',
'apiTestDebug.waitTime': '等待时间',
'apiTestDebug.script': '脚本操作',
'apiTestDebug.preconditionScriptName': '前置脚本名称',
'apiTestDebug.preconditionScriptNamePlaceholder': '前置脚本名称',
'apiTestDebug.manual': '手动录入',
'apiTestDebug.quote': '引用公共脚本',
'apiTestDebug.commonScriptList': '公共脚本列表',
'apiTestDebug.scriptEx': '脚本案例',
'apiTestDebug.copyNotSupport': '您的浏览器不支持自动复制,请您手动复制脚本案例',
'apiTestDebug.scriptExCopySuccess': '脚本案例已复制',
'apiTestDebug.parameters': '传递参数',
'apiTestDebug.scriptContent': '脚本内容',
'apiTestDebug.quoteSource': '引入数据源',
'apiTestDebug.quoteSourcePlaceholder': '请选择数据源',
'apiTestDebug.storageType': '存储方式',
'apiTestDebug.storageTypeTip1': '按列存储:指定从数据库结果集中提取的列的名称;多个列可以使用“,”分隔',
'apiTestDebug.storageTypeTip2': '按结果存储:把整个结果集保存为一个变量,而不是将每个列的值保存为单独的变量',
'apiTestDebug.storageByCol': '按列存储',
'apiTestDebug.storageByColPlaceholder': '如 {a} 改成 {b}',
'apiTestDebug.storageByResult': '按结果存储',
'apiTestDebug.storageByResultPlaceholder': '如 {a}',
'apiTestDebug.extractParameter': '提取参数',
};

View File

@ -92,7 +92,7 @@
<div class="flex flex-row items-center justify-between">
<div>
<div v-if="showAppend" class="flex flex-row items-center gap-[4px]">
<a-switch v-model:model-value="form.append" size="small" />
<a-switch v-model:model-value="form.append" size="small" type="line" />
<span class="text-[var(--color-text-1)]">{{ t('bugManagement.batchUpdate.update') }}</span>
<a-tooltip position="top">
<template #content>

View File

@ -65,7 +65,7 @@
<template #content>
<a-doption>
<a-switch class="mr-1" size="small" />{{ t('caseManagement.featureCase.addToPublic') }}
<a-switch class="mr-1" size="small" type="line" />{{ t('caseManagement.featureCase.addToPublic') }}
</a-doption>
<a-doption @click="updateHandler('copy')">
<MsIcon type="icon-icon_copy_filled" class="font-[16px]" />{{ t('common.copy') }}</a-doption

View File

@ -29,7 +29,7 @@
<div>
<div class="itemTab">
<span>{{ t('caseManagement.featureCase.detail') }}</span>
<a-switch v-model="detailEnable" size="small" :disabled="true" />
<a-switch v-model="detailEnable" size="small" :disabled="true" type="line" />
</div>
<a-divider orientation="center" class="non-sort"
><span class="one-line-text text-xs text-[var(--color-text-4)]">{{
@ -38,7 +38,7 @@
>
<div v-for="item of tabSettingList" :key="item.key" class="itemTab">
<span>{{ t(item.title) }}</span>
<a-switch v-model="item.enable" size="small" />
<a-switch v-model="item.enable" size="small" type="line" />
</div>
</div>
</MsDrawer>

View File

@ -17,7 +17,7 @@
}}
</div>
<div class="ml-[16px] flex items-center">
<a-switch v-model:model-value="onlyMine" size="small" class="mr-[8px]" />
<a-switch v-model:model-value="onlyMine" size="small" class="mr-[8px]" type="line" />
{{ t('caseManagement.caseReview.myReview') }}
</div>
</template>
@ -228,7 +228,7 @@
<div class="font-medium text-[var(--color-text-1)]">
{{ t('caseManagement.caseReview.startReview') }}
</div>
<a-switch v-model:model-value="autoNext" class="mx-[8px]" size="small" />
<a-switch v-model:model-value="autoNext" class="mx-[8px]" size="small" type="line" />
<div class="text-[var(--color-text-4)]">{{ t('caseManagement.caseReview.autoNext') }}</div>
<a-tooltip position="right">
<template #content>

View File

@ -187,7 +187,7 @@
<template #footer>
<div class="flex items-center justify-end">
<div v-if="dialogShowType === 'changeReviewer'" class="mr-auto flex items-center">
<a-switch v-model:model-value="dialogForm.isAppend" size="small" class="mr-[4px]"></a-switch>
<a-switch v-model:model-value="dialogForm.isAppend" size="small" class="mr-[4px]" type="line"></a-switch>
{{ t('caseManagement.caseReview.append') }}
<a-tooltip :content="t('caseManagement.caseReview.reviewResultTip')" position="right">
<template #content>

View File

@ -376,7 +376,7 @@
}
const caseAssociateVisible = ref<boolean>(false);
const caseAssociateProject = ref('');
const caseAssociateProject = ref(appStore.currentProjectId);
const loading = ref(false);
async function initReviewDetail() {
try {

View File

@ -20,7 +20,7 @@
</template>
<template #headerRight>
<div class="mr-[16px] flex items-center">
<a-switch v-model:model-value="onlyMine" size="small" class="mr-[8px]" />
<a-switch v-model:model-value="onlyMine" size="small" class="mr-[8px]" type="line" />
{{ t('caseManagement.caseReview.onlyMine') }}
</div>
<MsButton type="button" status="default" @click="associateDrawerVisible = true">
@ -221,7 +221,7 @@
const caseTableRef = ref<InstanceType<typeof CaseTable>>();
const associateDrawerVisible = ref(false);
const associateDrawerProject = ref('');
const associateDrawerProject = ref(appStore.currentProjectId);
//
async function writeAssociateCases(params: BaseAssociateCaseRequest & { reviewers: string[] }) {

View File

@ -1,417 +0,0 @@
<template>
<MsBaseTable v-bind="propsRes" :hoverable="false" 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('project.environmental.paramNamePlaceholder')"
class="param-input"
@input="(val) => addTableLine(val)"
/>
</a-popover>
</template>
<template #type="{ record }">
<a-select v-model:model-value="record.type" class="param-input" @change="(val) => handleTypeChange(val)">
<a-option v-for="element in typeOptions" :key="element.value" :value="element.value">{{
t(element.label)
}}</a-option>
</a-select>
</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)"
@change="handleDescChange"
/>
</template>
<template #operation="{ record, rowIndex }">
<div class="flex flex-row items-center gap-[16px]">
<a-switch v-if="rowIndex" v-model:model-value="record.enable" size="small" />
<icon-minus-circle
v-if="paramsLength > 1 && rowIndex !== paramsLength - 1"
class="cursor-pointer text-[var(--color-text-4)]"
size="20"
@click="deleteParam(rowIndex)"
/>
</div>
</template>
<template #tag="{ record }">
<ParamTagInput
v-model:model-value="record.tag"
@input="(val) => addTableLine(val)"
@dblclick="quickInputDesc(record)"
@change="handleDescChange"
/>
</template>
</MsBaseTable>
<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-if="showQuickInputParam"
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 async setup lang="ts">
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { MsTableColumnData } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsParamsInput from '@/components/business/ms-params-input/index.vue';
import ParamDescInput from './ParamDescInput.vue';
import ParamTagInput from './ParamTagInput.vue';
import { useI18n } from '@/hooks/useI18n';
import { useTableStore } from '@/store';
import { TableKeyEnum } from '@/enums/tableEnum';
interface Param {
id: number;
name: string;
type: string;
value: string;
desc: string;
tag: string[];
enable: boolean;
}
const props = withDefaults(
defineProps<{
params: Param[];
scroll?: {
x?: number | string;
y?: number | string;
maxHeight?: number | string;
minWidth?: number | string;
};
disabled?: boolean; //
showSetting?: boolean; //
tableKey?: TableKeyEnum; // key showSettingtrue
columns: MsTableColumnData[]; // showSettingfalse
showSelectorAll?: boolean; //
heightUsed?: number;
}>(),
{
disabled: false,
showSetting: false,
tableKey: undefined,
showSelectorAll: false,
heightUsed: 0,
}
);
const emit = defineEmits<{
(e: 'update:params', value: Param[]): void;
(e: 'change', data: Param[], isInit?: boolean): void;
}>();
const { t } = useI18n();
const defaultParams: Omit<Param, 'id'> = {
name: '',
type: 'string',
value: '',
desc: '',
tag: [],
enable: true,
};
const allType = [
{
label: 'common.string',
value: 'string',
},
{
label: 'common.integer',
value: 'integer',
},
{
label: 'common.number',
value: 'number',
},
{
label: 'common.array',
value: 'array',
},
{
label: 'common.json',
value: 'json',
},
{
label: 'common.file',
value: 'file',
},
];
const tableStore = useTableStore();
const typeOptions = computed(() => {
return allType;
});
if (props.showSetting && props.tableKey) {
await tableStore.initColumn(props.tableKey, props.columns);
}
const { propsRes, propsEvent } = useTable<Param>(undefined, {
tableKey: props.showSetting ? props.tableKey : undefined,
columns: props.columns,
scroll: props.scroll,
heightUsed: props.heightUsed,
selectable: true,
draggable: { type: 'handle', width: 24 },
showSetting: props.showSetting,
disabled: props.disabled,
showSelectorAll: props.showSelectorAll,
});
watch(
() => props.params,
(val) => {
if (val.length > 0) {
propsRes.value.data = val;
} else {
propsRes.value.data = props.params.concat({
id: new Date().getTime(),
name: '',
type: 'string',
value: '',
desc: '',
tag: [],
enable: true,
});
emit('change', propsRes.value.data, true);
}
},
{
immediate: true,
}
);
watch(
() => props.heightUsed,
(val) => {
propsRes.value.heightUsed = val;
}
);
const paramsLength = computed(() => propsRes.value.data.length);
function deleteParam(rowIndex: number) {
propsRes.value.data.splice(rowIndex, 1);
emit('change', propsRes.value.data);
}
/**
* 当表格输入框变化时给参数表格添加一行数据行
* @param val 输入值
* @param isForce 是否强制添加
*/
function addTableLine(val?: string | number, isForce?: boolean) {
const lastData = propsRes.value.data[propsRes.value.data.length - 1];
const isNotChange = Object.keys(defaultParams).every((key) => {
if (key === 'id') {
return true;
}
if (key === 'tag') {
return lastData[key].length === 0;
}
return lastData[key] === defaultParams[key as any];
});
if (isForce || (val !== '' && val !== undefined && !isNotChange)) {
propsRes.value.data = [...propsRes.value.data, { id: new Date().getTime(), ...defaultParams }];
emit('change', propsRes.value.data);
}
}
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();
addTableLine(quickInputParamValue.value, true);
emit('change', propsRes.value.data);
}
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();
addTableLine(quickInputDescValue.value, true);
emit('change', propsRes.value.data);
}
function handleDescChange() {
emit('change', propsRes.value.data);
}
function handleTypeChange(
val: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
) {
addTableLine(val as string);
}
</script>
<style lang="less" scoped>
:deep(.setting-icon) {
margin-left: 0 !important;
}
: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;
}
:deep(.param-input:not(.arco-input-focus, .arco-select-view-focus)) {
&:not(:hover) {
border-color: transparent !important;
.arco-input::placeholder {
@apply invisible;
}
.arco-select-view-icon {
@apply invisible;
}
.arco-select-view-value {
color: var(--color-text-brand);
}
}
}
.param-input-switch:not(:hover).arco-switch-checked {
background-color: rgb(var(--primary-3)) !important;
}
.content-type-trigger-content {
@apply bg-white;
padding: 8px;
border-radius: var(--border-radius-small);
box-shadow: 0 4px 10px -1px rgb(100 100 102 / 15%);
}
.param-input {
.param-input-mock-icon {
@apply invisible;
}
&:hover,
&.arco-input-focus {
.param-input-mock-icon {
@apply visible cursor-pointer;
&:hover {
color: rgb(var(--primary-5));
}
}
}
}
.param-popover-title {
@apply font-medium;
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>

View File

@ -1,77 +0,0 @@
<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)"
@change="(val) => emit('change', 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:desc', val: string): void;
(e: 'input', val: string): void;
(e: 'change', 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>

View File

@ -1,74 +0,0 @@
<template>
<a-popover position="tl" class="ms-params-input-popover">
<template #content>
<div class="param-popover-title">
{{ t('project.environmental.tag') }}
</div>
<div class="param-popover-value">
<MsTagsGroup is-string-tag :tag-list="props.modelValue" :show-num="1" class="param-input" />
</div>
</template>
<MsTagsInput ref="inputRef" v-model:model-value="innerValue" :max-tag-count="1" class="param-input" />
</a-popover>
</template>
<script setup lang="ts">
import { useEventListener, useVModel } from '@vueuse/core';
import MsTagsGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import { useI18n } from '@/hooks/useI18n';
const props = defineProps<{
modelValue: string[];
}>();
const emit = defineEmits<{
(e: 'update:modelValue', val: string[]): void;
(e: 'input', val: string): void;
(e: 'change', val: string): void;
(e: 'dblclick'): void;
}>();
const { t } = useI18n();
const innerValue = useVModel(props, 'modelValue', 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>

View File

@ -16,7 +16,7 @@
</a-input>
<batchAddKeyVal :params="innerParams" @apply="handleBatchParamApply" />
</div>
<AllParamsTable
<paramsTable
v-model:params="innerParams"
:table-key="props.tableKey"
:columns="columns"
@ -28,8 +28,7 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import AllParamsTable from './AllParamsTable.vue';
import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import batchAddKeyVal from '@/views/api-test/debug/components/debug/batchAddKeyVal.vue';
import { useI18n } from '@/hooks/useI18n';
@ -56,7 +55,7 @@
const innerParams = useVModel(props, 'params', emit);
const columns: MsTableColumn = [
const columns: ParamTableColumn[] = [
{
title: 'project.environmental.paramName',
dataIndex: 'name',
@ -70,6 +69,34 @@
slotName: 'type',
showInTable: true,
showDrag: true,
typeOptions: [
{
label: t('common.string'),
value: 'string',
},
{
label: t('common.integer'),
value: 'integer',
},
{
label: t('common.number'),
value: 'number',
},
{
label: t('common.array'),
value: 'array',
},
{
label: t('common.json'),
value: 'json',
},
{
label: t('common.file'),
value: 'file',
},
],
titleSlotName: 'typeTitle',
typeTitleTooltip: t('project.environmental.paramTypeTooltip'),
},
{
title: 'project.environmental.paramValue',

View File

@ -1,21 +1,15 @@
<template>
<div class="mb-[8px] flex items-center justify-between">
<div class="font-medium">{{ t('ms.apiTestDebug.header') }}</div>
<div class="font-medium">{{ t('apiTestDebug.header') }}</div>
<batchAddKeyVal :params="innerParams" @apply="handleBatchParamApply" />
</div>
<AllParamsTable
v-model:params="innerParams"
:show-setting="false"
:columns="columns"
@change="handleParamTableChange"
/>
<paramsTable v-model:params="innerParams" :show-setting="false" :columns="columns" @change="handleParamTableChange" />
</template>
<script setup lang="ts">
import { useVModel } from '@vueuse/core';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import AllParamsTable from '../allParams/AllParamsTable.vue';
import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import batchAddKeyVal from '@/views/api-test/debug/components/debug/batchAddKeyVal.vue';
import { useI18n } from '@/hooks/useI18n';
@ -32,14 +26,14 @@
const innerParams = useVModel(props, 'params', emit);
const columns: MsTableColumn = [
const columns: ParamTableColumn[] = [
{
title: 'ms.apiTestDebug.paramName',
title: 'apiTestDebug.paramName',
dataIndex: 'name',
slotName: 'name',
},
{
title: 'ms.apiTestDebug.desc',
title: 'apiTestDebug.desc',
dataIndex: 'desc',
slotName: 'desc',
},

View File

@ -182,6 +182,8 @@
</template>
<script lang="ts" setup>
import { VueDraggable } from 'vue-draggable-plus';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
@ -199,8 +201,6 @@
import { PopVisible } from '@/models/setting/usergroup';
import { EnvAuthScopeEnum, EnvAuthTypeEnum } from '@/enums/envEnum';
import { VueDraggable } from 'vue-draggable-plus';
const { t } = useI18n();
const store = useProjectEnvStore();

View File

@ -21,6 +21,7 @@
:disabled="loading"
size="small"
class="mr-[4px]"
type="line"
/>
<a-tooltip :content="t('project.fileManagement.uploadTipSingle')">
<MsIcon type="icon-icon-maybe_outlined" class="mr-[8px] cursor-pointer hover:text-[rgb(var(--primary-5))]" />

View File

@ -174,7 +174,7 @@
>
<template #tabExtra>
<div v-if="acceptType === 'jar'" class="flex items-center gap-[4px]">
<a-switch size="small" :disabled="fileList.length === 0" @change="enableAllJar"></a-switch>
<a-switch size="small" :disabled="fileList.length === 0" type="line" @change="enableAllJar"></a-switch>
{{ t('project.fileManagement.enableAll') }}
<a-tooltip :content="t('project.fileManagement.uploadTip')">
<MsIcon type="icon-icon-maybe_outlined" class="cursor-pointer hover:text-[rgb(var(--primary-5))]" />
@ -182,7 +182,7 @@
</div>
</template>
<template #actions="{ item }">
<a-switch v-if="acceptType === 'jar'" v-model:model-value="item.enable" size="small"></a-switch>
<a-switch v-if="acceptType === 'jar'" v-model:model-value="item.enable" size="small" type="line"></a-switch>
</template>
</MsFileList>
<template #footer>

View File

@ -88,6 +88,7 @@
v-model:model-value="record.projectRobotConfigMap[dataIndex as string].enable"
:before-change="(val) => handleChangeIntercept(!!val, record, dataIndex as string)"
size="small"
type="line"
/>
<a-popover position="right" :popup-container="isFullscreen ? '#mscard' : undefined">
<div

View File

@ -67,6 +67,7 @@
v-model:model-value="robot.enable"
size="small"
class="ml-auto"
type="line"
@change="handleEnableIntercept(robot)"
/>
</div>
@ -240,7 +241,7 @@
</a-form-item>
</a-form>
<template #footerLeft>
<a-switch v-model:model-value="robotForm.enable" size="small" class="mr-[4px]"></a-switch>
<a-switch v-model:model-value="robotForm.enable" size="small" class="mr-[4px]" type="line"></a-switch>
{{ t('project.messageManagement.status') }}
<a-tooltip position="tl" mini>
<template #content>

View File

@ -80,9 +80,9 @@
<template v-if="platformOption.length" #footerLeft>
<div class="flex flex-row items-center gap-[4px]">
<a-tooltip v-if="okDisabled" :content="t('project.menu.defect.enableAfterConfig')">
<a-switch size="small" disabled />
<a-switch size="small" type="line" disabled />
</a-tooltip>
<a-switch v-else v-model="form.SYNC_ENABLE" size="small" />
<a-switch v-else v-model="form.SYNC_ENABLE" size="small" type="line" />
<span class="text-[var(--color-text-1)]">
{{ t('project.menu.status') }}
</span>

View File

@ -37,9 +37,9 @@
<template v-if="platformOption.length" #footerLeft>
<div class="flex flex-row items-center gap-[4px]">
<a-tooltip v-if="okDisabled" :content="t('project.menu.defect.enableAfterConfig')">
<a-switch size="small" disabled />
<a-switch size="small" type="line" disabled />
</a-tooltip>
<a-switch v-else v-model="form.SYNC_ENABLE" size="small" />
<a-switch v-else v-model="form.SYNC_ENABLE" size="small" type="line" />
<span class="text-[var(--color-text-1)]">
{{ t('project.menu.status') }}
</span>

View File

@ -226,6 +226,7 @@
unchecked-value="false"
:value="allValueMap['BUG_SYNC_SYNC_ENABLE']"
size="small"
type="line"
@change="(v: boolean | string| number) => handleMenuStatusChange('BUG_SYNC_SYNC_ENABLE',v as boolean, MenuEnum.bugManagement)"
/>
</a-tooltip>
@ -234,6 +235,7 @@
checked-value="true"
unchecked-value="false"
size="small"
type="line"
@change="(v: boolean | string| number) => handleMenuStatusChange('BUG_SYNC_SYNC_ENABLE',v as boolean, MenuEnum.bugManagement)"
/>
<!-- 关联需求状态 -->
@ -252,6 +254,7 @@
unchecked-value="false"
:value="allValueMap['CASE_RELATED_CASE_ENABLE']"
size="small"
type="line"
@change="(v: boolean | string| number) => handleMenuStatusChange('CASE_RELATED_CASE_ENABLE',v as boolean, MenuEnum.caseManagement)"
/>
</a-tooltip>
@ -260,6 +263,7 @@
checked-value="true"
unchecked-value="false"
size="small"
type="line"
@change="(v: boolean | string| number) => handleMenuStatusChange('CASE_RELATED_CASE_ENABLE',v as boolean, MenuEnum.caseManagement)"
/>
<!-- 其他配置项 -->
@ -270,6 +274,7 @@
checked-value="true"
unchecked-value="false"
size="small"
type="line"
@change="(v: boolean | string| number) => handleMenuStatusChange(record.type,v as boolean,MenuEnum.workstation)"
/>
<!-- 用例 公共用例 Switch-->
@ -279,6 +284,7 @@
checked-value="true"
unchecked-value="false"
size="small"
type="line"
@change="(v: boolean | string| number) => handleMenuStatusChange(record.type,v as boolean,MenuEnum.caseManagement)"
/>
<!-- 用例 重新提审 Switch-->
@ -288,6 +294,7 @@
checked-value="true"
unchecked-value="false"
size="small"
type="line"
@change="(v: boolean | string| number) => handleMenuStatusChange(record.type,v as boolean,MenuEnum.caseManagement)"
/>
<!-- 接口测试 接口定义URL可重复 Switch-->
@ -297,6 +304,7 @@
checked-value="true"
unchecked-value="false"
size="small"
type="line"
@change="(v: boolean | string| number) => handleMenuStatusChange(record.type,v as boolean,MenuEnum.apiTest)"
/>
<!-- 接口测试 用例同步 Switch-->
@ -306,6 +314,7 @@
checked-value="true"
unchecked-value="false"
size="small"
type="line"
@change="(v: boolean | string| number) => handleMenuStatusChange(record.type,v as boolean,MenuEnum.apiTest)"
/>
<!-- 性能测试 脚本审核 Switch-->
@ -315,6 +324,7 @@
checked-value="true"
unchecked-value="false"
size="small"
type="line"
@change="(v: boolean | string| number) => handleMenuStatusChange('PERFORMANCE_TEST_SCRIPT_REVIEWER_ENABLE',v as boolean,MenuEnum.loadTest)"
/>
</template>

View File

@ -44,6 +44,7 @@
v-model:model-value="projectVersionStatus"
size="small"
:before-change="(val) => openProjectVersion(val)"
type="line"
>
</a-switch>
<span class="ml-[4px] font-medium">{{ t('project.projectVersion.version') }}</span>
@ -144,6 +145,7 @@
v-model:model-value="record.status"
size="small"
:before-change="(val) => handleStatusChange(val, record)"
type="line"
></a-switch>
</template>
<template #latest="{ record }">
@ -152,6 +154,7 @@
:disabled="record.latest"
:before-change="() => handleUseLatestVersionChange(record)"
size="small"
type="line"
/>
</template>
<template #action="{ record }">

View File

@ -20,6 +20,7 @@
v-model="record.enableDefault"
:disabled="record.enableDefault || isEnableOrdTemplate"
size="small"
type="line"
@change="(value) => changeDefault(value, record)"
/>
</template>

View File

@ -72,7 +72,7 @@
<template #footer>
<div class="flex flex-row justify-between">
<div class="flex flex-row items-center gap-[4px]">
<a-switch v-model="form.enable" size="small" />
<a-switch v-model="form.enable" size="small" type="line" />
<span>{{ t('system.organization.status') }}</span>
<a-tooltip :content="t('system.project.createTip')" position="top">
<MsIcon type="icon-icon-maybe_outlined" class="text-[var(--color-text-4)]" />

View File

@ -15,7 +15,7 @@
<template #footer>
<div class="flex justify-between">
<div class="flex flex-row items-center justify-center">
<a-switch v-model="isEnable" :disabled="isDisabled" size="small" />
<a-switch v-model="isEnable" :disabled="isDisabled" size="small" type="line" />
<a-tooltip>
<template #content>
<div class="text-sm">{{ t('organization.service.statusEnableTip') }}</div>

View File

@ -86,10 +86,17 @@
v-model="item.enable"
size="small"
:disabled="true"
type="line"
@change="(v) => changeStatus(v, item.id)"
/></span>
</a-tooltip>
<a-switch v-else v-model="item.enable" size="small" @change="(v) => changeStatus(v, item.id)" />
<a-switch
v-else
v-model="item.enable"
size="small"
type="line"
@change="(v) => changeStatus(v, item.id)"
/>
</span>
</div>
</div>

View File

@ -97,6 +97,7 @@
* @description 系统管理-模版-模版管理-创建模板-添加字段到模板抽屉
*/
import { ref } from 'vue';
import { VueDraggable } from 'vue-draggable-plus';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
@ -106,8 +107,6 @@
import type { DefinedFieldItem } from '@/models/setting/template';
import { VueDraggable } from 'vue-draggable-plus';
const { t } = useI18n();
const showAddDrawer = ref<boolean>(false);

View File

@ -58,7 +58,7 @@
:label="t('system.orgTemplate.allowMultiMember')"
asterisk-position="end"
>
<a-switch v-model="isMultipleSelectMember" size="small" :disabled="isEdit" />
<a-switch v-model="isMultipleSelectMember" size="small" :disabled="isEdit" type="line" />
</a-form-item>
<!-- 选项选择器 -->
<a-form-item

View File

@ -180,11 +180,11 @@
></a-input>
</a-form-item>
<a-form-item :label="t('system.config.email.ssl')" field="ssl" asterisk-position="end">
<a-switch v-model:model-value="emailConfigForm.ssl" />
<a-switch v-model:model-value="emailConfigForm.ssl" type="line" />
<MsFormItemSub :text="t('system.config.email.sslTip')" :show-fill-icon="false" />
</a-form-item>
<a-form-item :label="t('system.config.email.tsl')" field="tsl" asterisk-position="end">
<a-switch v-model:model-value="emailConfigForm.tsl" />
<a-switch v-model:model-value="emailConfigForm.tsl" type="line" />
<MsFormItemSub :text="t('system.config.email.tslTip')" :show-fill-icon="false" />
</a-form-item>
</a-form>

View File

@ -74,7 +74,7 @@
<template #footer>
<div class="flex flex-row justify-between">
<div class="flex flex-row items-center gap-[4px]">
<a-switch v-model="form.enable" size="small" />
<a-switch v-model="form.enable" size="small" type="line" />
<span>{{ t('system.organization.status') }}</span>
<a-tooltip :content="t('system.project.createTip')" position="top">
<MsIcon type="icon-icon-maybe_outlined" class="text-[var(--color-text-4)]" />

View File

@ -73,7 +73,7 @@
<template #footer>
<div class="flex justify-between">
<div class="flex flex-row items-center justify-center">
<a-switch v-model="form.enable" size="small" />
<a-switch v-model="form.enable" size="small" type="line" />
<a-tooltip>
<template #content>
<div class="text-sm">{{ t('system.plugin.statusEnableTip') }}</div>