fix(全局): bug修复&devLocalEnv

This commit is contained in:
baiqi 2024-06-14 17:55:37 +08:00 committed by Craftsman
parent 8965678a2c
commit 95eb0aa49e
31 changed files with 375 additions and 276 deletions

View File

@ -1 +1,2 @@
VITE_API_BASE_URL= 'front'
VITE_API_BASE_URL= 'front'
VITE_DEV_DOMAIN='http://172.16.200.18:8081/'

View File

@ -1,8 +1,12 @@
/// <reference types="vitest" />
import baseConfig from './vite.config.base';
import dotenv from 'dotenv';
import { mergeConfig } from 'vite';
import eslint from 'vite-plugin-eslint';
// 注入本地/开发配置环境变量(先导入的配置优先级高)
dotenv.config({ path: ['.env.development.local', '.env.development'] });
export default mergeConfig(
{
mode: 'development',
@ -13,38 +17,38 @@ export default mergeConfig(
},
proxy: {
'/ws': {
target: 'http://172.16.200.18:8081/',
target: process.env.VITE_DEV_DOMAIN,
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/ws/, ''),
ws: true,
},
'/front': {
target: 'http://172.16.200.18:8081/',
target: process.env.VITE_DEV_DOMAIN,
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front/, ''),
},
'/file': {
target: 'http://172.16.200.18:8081/',
target: process.env.VITE_DEV_DOMAIN,
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/file/, ''),
},
'/attachment': {
target: 'http://172.16.200.18:8081/',
target: process.env.VITE_DEV_DOMAIN,
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/attachment/, ''),
},
'/bug/attachment': {
target: 'http://172.16.200.18:8081/',
target: process.env.VITE_DEV_DOMAIN,
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/bug\/attachment/, ''),
},
'/plugin/image': {
target: 'http://172.16.200.18:8081/',
target: process.env.VITE_DEV_DOMAIN,
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/plugin\/image/, ''),
},
'/base-display': {
target: 'http://172.16.200.18:8081/',
target: process.env.VITE_DEV_DOMAIN,
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/front\/base-display/, ''),
},

View File

@ -56,6 +56,7 @@
"ahooks-vue": "^0.15.1",
"axios": "^1.6.5",
"dayjs": "^1.11.9",
"dotenv": "^16.4.5",
"echarts": "^5.4.3",
"fastq": "^1.15.0",
"github-markdown-css": "^5.5.1",

View File

@ -32,8 +32,6 @@
</template>
<script setup lang="ts">
import { ref } from 'vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type';

View File

@ -69,6 +69,7 @@
import { FormItem } from '@/components/pure/ms-form-create/types';
import MsMinderEditor from '@/components/pure/ms-minder-editor/minderEditor.vue';
import type { MinderJson, MinderJsonNode, MinderJsonNodeData } from '@/components/pure/ms-minder-editor/props';
import { setPriorityView } from '@/components/pure/ms-minder-editor/script/tool/utils';
import { MsFileItem } from '@/components/pure/ms-upload/types';
import attachment from './attachment.vue';
import baseInfo from './basInfo.vue';
@ -87,7 +88,7 @@
import useFeatureCaseStore from '@/store/modules/case/featureCase';
import useMinderStore from '@/store/modules/components/minder-editor/index';
import { MinderCustomEvent } from '@/store/modules/components/minder-editor/types';
import { filterTree, getGenerateId, mapTree } from '@/utils';
import { filterTree, getGenerateId, mapTree, replaceNodeInTree } from '@/utils';
import {
FeatureCaseMinderEditType,
@ -158,7 +159,7 @@
loading.value = true;
const res = await getCaseMinderTree({
projectId: appStore.currentProjectId,
moduleId: props.moduleId === 'all' ? '' : props.moduleId,
moduleId: '', //
});
caseTree.value = mapTree<MinderJsonNode>(res, (e) => ({
...e,
@ -195,8 +196,11 @@
disabled: true,
},
};
importJson.value.treePath = [];
window.minder.importJson(importJson.value);
window.minder.execCommand('camera', window.minder.getRoot(), 100);
if (props.moduleId !== 'all') {
// ID
nextTick(() => {
minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, undefined, [
window.minder.getNodeById(props.moduleId),
@ -485,7 +489,15 @@
node.data.isLoaded = true;
}
// importJson
importJson.value = window.minder.exportJson();
const currentFullJson: MinderJson = window.minder.exportJson();
const { root } = currentFullJson;
if (root.data?.id === 'NONE') {
//
importJson.value = currentFullJson;
} else {
//
replaceNodeInTree([importJson.value.root], node.data?.id || '', root, 'data', 'id');
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
@ -503,6 +515,7 @@
window.minder.layout();
}
}
setPriorityView(true, 'P');
}
/**

View File

@ -34,6 +34,7 @@
const { t } = useI18n();
const props = defineProps<{
associationType: CaseLinkEnum;
hasNotAssociatedIds?: string[];
saveApi?: (params: AssociateCaseRequestType) => Promise<any>;
testPlanId?: string;
@ -73,7 +74,4 @@
}
innerVisible.value = false;
}
// TODO
const associationType = ref<keyof typeof CaseLinkEnum>('FUNCTIONAL');
</script>

View File

@ -224,7 +224,7 @@
</MsMinderEditor>
<caseAssociate
v-model:visible="caseAssociateVisible"
v-model:currentSelectCase="currentSelectCase"
:association-type="currentSelectCase"
:has-not-associated-ids="selectedAssociateCasesParams.selectIds"
:test-plan-id="props.planId"
@success="writeAssociateCases"
@ -269,6 +269,9 @@
const props = defineProps<{
planId: string;
}>();
const emit = defineEmits<{
(e: 'save'): void;
}>();
const appStore = useAppStore();
const { t } = useI18n();
@ -373,6 +376,7 @@
text: t('ms.minders.item', { count: 0 }),
resource: [caseCountTag],
level: 3,
disabled: true, //
isNew: true,
};
//
@ -381,6 +385,7 @@
text: t('case.execute.defaultEnv'),
resource: [envTag],
level: 3,
disabled: true, //
isNew: true,
};
//
@ -389,6 +394,7 @@
resource: [resourcePoolTag],
text: t('ms.minders.defaultResourcePool'),
level: 3,
disabled: true, //
isNew: true,
};
if (node.data?.level === 1) {
@ -398,6 +404,7 @@
id: getGenerateId(),
text: t('ms.minders.defaultTestSet'),
level: 2,
disabled: false, //
isNew: true,
};
} else if (node.parent?.data) {
@ -407,6 +414,7 @@
id: getGenerateId(),
text: t('ms.minders.defaultTestSet'),
level: 2,
disabled: false, //
isNew: true,
};
}
@ -545,7 +553,7 @@
});
}
const currentSelectCase = ref<keyof typeof CaseLinkEnum>('FUNCTIONAL');
const currentSelectCase = ref<CaseLinkEnum>(CaseLinkEnum.FUNCTIONAL);
const caseAssociateVisible = ref<boolean>(false);
//
@ -592,7 +600,7 @@
switchingConfigFormData.value = true;
configForm.value = cloneDeep(activePlanSet.value.data);
extraVisible.value = true;
currentSelectCase.value = node.data?.type || 'FUNCTIONAL';
currentSelectCase.value = (node.data?.type as unknown as CaseLinkEnum) || CaseLinkEnum.FUNCTIONAL;
caseAssociateVisible.value = true;
nextTick(() => {
switchingConfigFormData.value = false;
@ -671,7 +679,7 @@
level,
isNew: false,
changed: false,
disabled: level < 2,
disabled: level !== 2, //
};
return node;
});
@ -727,6 +735,7 @@
handleConfigCancel();
initMinder();
callback();
emit('save');
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);

View File

@ -49,7 +49,7 @@
<slot name="title" v-bind="_props"></slot>
</template>
<template v-if="$slots['extra'] || props.nodeMoreActions" #extra="_props">
<div class="sticky right-[8px] flex items-center justify-between">
<div class="sticky right-0 flex items-center justify-between">
<div v-if="_props.hideMoreAction !== true" class="ms-tree-node-extra">
<slot name="extra" v-bind="_props"></slot>
<MsTableMoreAction
@ -497,6 +497,7 @@
@apply invisible flex w-0 items-center;
margin-left: -4px;
padding-right: 8px;
height: 32px;
border-radius: var(--border-radius-small);
background-color: rgb(var(--primary-1));

View File

@ -3,11 +3,11 @@
<minderHeader :icon-buttons="props.iconButtons" @save="save" />
<Navigator />
<div
v-if="innerImportJson.treePath?.length > 1"
v-if="currentTreePath?.length > 0"
class="absolute left-[50%] top-[24px] z-50 translate-x-[-50%] bg-white p-[8px]"
>
<a-breadcrumb>
<a-breadcrumb-item v-for="crumb of innerImportJson.treePath" :key="crumb.name" @click="switchNode(crumb)">
<a-breadcrumb-item v-for="crumb of currentTreePath" :key="crumb.name" @click="switchNode(crumb)">
{{ crumb.text }}
</a-breadcrumb-item>
</a-breadcrumb>
@ -80,6 +80,7 @@
template: 'default',
treePath: [],
});
const currentTreePath = ref<MinderJsonNodeData[]>([]);
async function init() {
window.editor = new Editor(mec.value, {
@ -144,6 +145,14 @@
const menuVisible = ref(false);
const menuPopupOffset = ref([0, 0]);
function getCurrentTreePath() {
if (innerImportJson.value.root.id === 'NONE' || innerImportJson.value.treePath?.length <= 1) {
return [];
}
const index = innerImportJson.value.treePath?.findIndex((e) => e.id === innerImportJson.value.root.data?.id);
return innerImportJson.value.treePath?.filter((e, i) => i <= index) || [];
}
/**
* 切换脑图展示的节点层级
* @param node 切换的节点
@ -159,15 +168,20 @@
'id'
);
}
if (node.data) {
if (node.id === 'NONE') {
innerImportJson.value = importJson.value;
} else if (node.data) {
innerImportJson.value = findNodePathByKey([importJson.value.root], node.data.id, 'data', 'id') as MinderJson;
} else {
innerImportJson.value = findNodePathByKey([importJson.value.root], node.id, 'data', 'id') as MinderJson;
}
window.minder.importJson(innerImportJson.value);
const root: MinderJsonNode = window.minder.getRoot();
window.minder.toggleSelect(root); //
window.minder.select(root); //
currentTreePath.value = getCurrentTreePath();
setTimeout(() => {
window.minder.select(window.minder.getRoot());
window.minder.execCommand('camera', window.minder.getRoot());
window.minder.execCommand('camera', root);
}, 100); // TODO:
}
@ -220,6 +234,13 @@
}
);
watch(
() => importJson.value.treePath,
(arr) => {
currentTreePath.value = arr;
}
);
onBeforeUnmount(() => {
minderStore.setMinderUnsaved(false);
});

View File

@ -97,7 +97,9 @@
(val) => {
const node: MinderJsonNode = window.minder.getSelectedNode();
if (val && node) {
window.minder.execCommand('camera', node, 100);
nextTick(() => {
window.minder.execCommand('camera', node, 100);
});
}
}
);

View File

@ -36,7 +36,7 @@ export interface MinderJsonNode {
export interface MinderJson {
root: MinderJsonNode;
template: string;
treePath: MinderJsonNode[];
treePath: MinderJsonNodeData[];
}
// 脑图类
export interface MinderClass {

View File

@ -62,6 +62,13 @@
//
const tempActiveKey = ref(activeKey.value);
watch(
() => activeKey.value,
(val) => {
tempActiveKey.value = val;
}
);
function handleTabClick(value: string) {
if (value === activeKey.value) {
return;

View File

@ -32,11 +32,11 @@ export default {
'asyncTask.uploadFileSuccessTitle': 'Upload completed',
// 通用业务提示
'user.openSourceCreateUsersLimit':
'The maximum number of system users has reached 30 (Community Edition). If you need to add more users, you can apply',
'The maximum number of system users has reached 30 (Community Edition). If you need to add/enable more users, you can apply',
'user.businessTry': 'Enterprise Edition Trial',
'user.businessCreateUsersLimitThirty':
'The maximum number of system users has reached 30 (Community Edition). If you need to add more users, you can apply',
'The maximum number of system users has reached 30 (Community Edition). If you need to add/enable more users, you can apply',
'user.businessCreateUsersLimitMax':
'The number of system users has reached the maximum number of user subscriptions {count}. If you want to add more users, you can apply',
'The number of system users has reached the maximum number of user subscriptions {count}. If you want to add/enable more users, you can apply',
'user.businessScaling': 'Enterprise Edition Capacity Expansion',
};

View File

@ -31,9 +31,9 @@ export default {
'asyncTask.uploadFileSuccess': '文件上传完成:成功 {done} 个,失败 {fail} 个',
'asyncTask.uploadFileSuccessTitle': '上传完成',
// 通用业务提示
'user.openSourceCreateUsersLimit': '系统用户数已达到最大用户数限制30人(社区版),如需添加更多用户,可申请',
'user.openSourceCreateUsersLimit': '系统用户数已达到最大用户数限制30人(社区版),如需添加/启用更多用户,可申请',
'user.businessTry': '企业版试用',
'user.businessCreateUsersLimitThirty': '系统用户数已达到最大用户数限制30人 (社区版),如需添加更多用户,可申请',
'user.businessCreateUsersLimitMax': '系统用户数已达到最大用户订阅数 {count} 人,如需添加更多用户,可申请',
'user.businessCreateUsersLimitThirty': '系统用户数已达到最大用户数限制30人 (社区版),如需添加/启用更多用户,可申请',
'user.businessCreateUsersLimitMax': '系统用户数已达到最大用户订阅数 {count} 人,如需添加/启用更多用户,可申请',
'user.businessScaling': '企业版扩容',
};

View File

@ -255,7 +255,6 @@
</template>
<!-- SQL操作 -->
<template v-else-if="condition.processorType === RequestConditionProcessor.SQL">
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('ms.paramsInput.sqlOperationNameDesc') }}</div>
<div class="mb-[8px]">
<a-input
v-model:model-value="condition.name"

View File

@ -1164,6 +1164,20 @@
requestVModel.value.executeLoading = false;
}
function setDefaultActiveTab() {
if (requestVModel.value.body.bodyType !== RequestBodyFormat.NONE) {
requestVModel.value.activeTab = RequestComposition.BODY;
} else if (requestVModel.value.query.length > 0) {
requestVModel.value.activeTab = RequestComposition.QUERY;
} else if (requestVModel.value.rest.length > 0) {
requestVModel.value.activeTab = RequestComposition.REST;
} else if (requestVModel.value.headers.length > 0) {
requestVModel.value.activeTab = RequestComposition.HEADER;
} else {
requestVModel.value.activeTab = RequestComposition.BODY;
}
}
watch(
() => requestVModel.value.id,
async () => {
@ -1189,6 +1203,7 @@
// BODY/QUERY/RESTtabtab
requestVModel.value.activeTab = contentTabList.value[1].value;
}
setDefaultActiveTab();
if (!props.isCase) {
responseRef.value?.setActiveResponse(requestVModel.value.mode === 'debug' ? 'result' : 'content');
}

View File

@ -233,6 +233,7 @@
...cloneDeep(defaultDebugParams),
id,
isNew: !defaultProps?.id, // tabidid
protocol: activeDebug.value.protocol, // tab使tab
...defaultProps,
});
activeDebug.value = debugTabs.value[debugTabs.value.length - 1];

View File

@ -202,7 +202,7 @@ export default {
'apiTestDebug.searchByDataBaseName': 'Search by data source name',
'apiTestDebug.regexMatchRules': 'Expression matching rules',
'apiTestDebug.extractValueTitleTip':
'Enter the column name and corresponding value in column storage. If you want to extract the first value of the name column, enter name_1',
'Enter the column name and corresponding value in column storage. If you want to extract the first value of the id column, enter id_1',
'apiTestDebug.responseRepeatMessage': 'The name is duplicated, please re-enter it.',
'apiTestDebug.saveAsApi': 'Save as Api',
'apiTestDebug.assertionItem': 'Assertion item',

View File

@ -188,7 +188,7 @@ export default {
'apiTestDebug.testSuccess': '测试成功',
'apiTestDebug.searchByDataBaseName': '按数据源名称搜索',
'apiTestDebug.regexMatchRules': '表达式匹配规则',
'apiTestDebug.extractValueTitleTip': '输入按列存储中的列名和对应的数值,如提取name列的第一个值则输入name_1',
'apiTestDebug.extractValueTitleTip': '输入按列存储中的列名和对应的数值,如提取 id 列的第一个值则输入 id_1',
'apiTestDebug.responseRepeatMessage': '名称重复,请重新输入',
'apiTestDebug.saveAsApi': '另存为接口',
'apiTestDebug.assertionItem': '断言项',

View File

@ -306,6 +306,7 @@
id,
isNew: !defaultProps?.id, // tabidid
definitionActiveKey: !defaultProps ? 'definition' : 'preview',
protocol: activeApiTab.value.protocol, // tab使tab
...defaultProps,
});
activeApiTab.value = apiTabs.value[apiTabs.value.length - 1];

View File

@ -14,7 +14,7 @@
@close="handleClose"
>
<template #title>
<div class="flex max-w-[60%] items-center gap-[8px]">
<div class="flex flex-1 items-center gap-[8px] overflow-hidden">
<div
v-if="props.step"
class="flex h-[16px] min-w-[16px] items-center justify-center rounded-full bg-[var(--color-text-brand)] pr-[2px] !text-white"
@ -26,30 +26,30 @@
:step="props.step"
/>
<a-tooltip v-if="!isShowEditStepNameInput" :content="title" position="bottom">
<div class="flex items-center gap-[4px]">
<div class="one-line-text max-w-[300px]">
<div class="flex flex-1 items-center gap-[4px] overflow-hidden">
<div class="one-line-text">
{{ title }}
</div>
<MsIcon
v-if="!props.step || !props.step.isQuoteScenarioStep"
type="icon-icon_edit_outlined"
class="cursor-pointer hover:text-[rgb(var(--primary-5))]"
class="min-w-[16px] cursor-pointer hover:text-[rgb(var(--primary-5))]"
@click="isShowEditStepNameInput = true"
/>
</div>
</a-tooltip>
<a-input
v-if="isShowEditStepNameInput"
ref="stepNameInputRef"
v-model:model-value="requestVModel.stepName"
class="flex-1"
:placeholder="t('apiScenario.pleaseInputStepName')"
:max-length="255"
show-word-limit
@press-enter="updateStepName"
@blur="updateStepName"
/>
</div>
<a-input
v-if="isShowEditStepNameInput"
ref="stepNameInputRef"
v-model:model-value="requestVModel.stepName"
class="flex-1"
:placeholder="t('apiScenario.pleaseInputStepName')"
:max-length="255"
show-word-limit
@press-enter="updateStepName"
@blur="updateStepName"
/>
<div v-show="!isShowEditStepNameInput" class="ml-auto flex items-center gap-[16px]">
<div
v-if="!props.step || props.step?.stepType === ScenarioStepType.CUSTOM_REQUEST"
@ -1156,6 +1156,20 @@
// const showAddDependencyDrawer = ref(false);
// const addDependencyMode = ref<'pre' | 'post'>('pre');
function setDefaultActiveTab() {
if (requestVModel.value.body.bodyType !== RequestBodyFormat.NONE) {
requestVModel.value.activeTab = RequestComposition.BODY;
} else if (requestVModel.value.query.length > 0) {
requestVModel.value.activeTab = RequestComposition.QUERY;
} else if (requestVModel.value.rest.length > 0) {
requestVModel.value.activeTab = RequestComposition.REST;
} else if (requestVModel.value.headers.length > 0) {
requestVModel.value.activeTab = RequestComposition.HEADER;
} else {
requestVModel.value.activeTab = RequestComposition.BODY;
}
}
async function initQuoteApiDetail() {
try {
loading.value = true;
@ -1287,6 +1301,7 @@
});
}
requestVModel.value.activeTab = contentTabList.value[0].value;
setDefaultActiveTab();
nextTick(() => {
isSwitchingContent.value = false;
});

View File

@ -28,17 +28,15 @@
@press-enter="updateStepName"
@blur="updateStepName"
/>
<div v-show="!isShowEditStepNameInput" class="flex flex-1 items-center justify-between">
<div class="flex items-center gap-[8px]">
<div v-show="!isShowEditStepNameInput" class="flex flex-1 items-center justify-between overflow-hidden">
<div class="flex flex-1 items-center gap-[8px] overflow-hidden">
<a-tooltip :content="requestVModel.stepName || activeStep?.name">
<div class="one-line-text max-w-[300px]">
{{ requestVModel.stepName || characterLimit(activeStep?.name) }}</div
>
<div class="one-line-text"> {{ requestVModel.stepName || characterLimit(activeStep?.name) }}</div>
</a-tooltip>
<MsIcon
v-if="!activeStep || !activeStep.isQuoteScenarioStep"
type="icon-icon_edit_outlined"
class="cursor-pointer hover:text-[rgb(var(--primary-5))]"
class="min-w-[16px] cursor-pointer hover:text-[rgb(var(--primary-5))]"
@click="showEditScriptNameInput"
/>
</div>
@ -1020,6 +1018,20 @@
}
}
function setDefaultActiveTab() {
if (requestVModel.value.body.bodyType !== RequestBodyFormat.NONE) {
requestVModel.value.activeTab = RequestComposition.BODY;
} else if (requestVModel.value.query.length > 0) {
requestVModel.value.activeTab = RequestComposition.QUERY;
} else if (requestVModel.value.rest.length > 0) {
requestVModel.value.activeTab = RequestComposition.REST;
} else if (requestVModel.value.headers.length > 0) {
requestVModel.value.activeTab = RequestComposition.HEADER;
} else {
requestVModel.value.activeTab = RequestComposition.BODY;
}
}
/**
* 替换步骤
* @param newStep 替换的新步骤
@ -1063,6 +1075,7 @@
await initQuoteCaseDetail();
}
handleActiveDebugProtocolChange(requestVModel.value.protocol);
setDefaultActiveTab();
nextTick(() => {
isSwitchingContent.value = false;
});

View File

@ -30,24 +30,25 @@
<pre class="response-header-pre">{{ currentResponse?.console }}</pre>
</div>
</div>
<responseResult
v-else
:active-tab="ResponseComposition.BODY"
:request-result="currentResponse"
:console="currentResponse?.console"
:show-empty="false"
:is-edit="false"
is-definition
>
<template #titleLeft>
<div class="flex items-center text-[14px]">
<div class="font-medium text-[var(--color-text-1)]">{{ t('apiScenario.response') }}</div>
<a-tooltip :content="props.step.name">
<div class="one-line-text">({{ props.step.name }})</div>
</a-tooltip>
</div>
</template>
</responseResult>
<div v-else class="response-result">
<responseResult
:active-tab="ResponseComposition.BODY"
:request-result="currentResponse"
:console="currentResponse?.console"
:show-empty="false"
:is-edit="false"
is-definition
>
<template #titleLeft>
<div class="flex items-center text-[14px]">
<div class="font-medium text-[var(--color-text-1)]">{{ t('apiScenario.response') }}</div>
<a-tooltip :content="props.step.name">
<div class="one-line-text">({{ props.step.name }})</div>
</a-tooltip>
</div>
</template>
</responseResult>
</div>
</div>
</div>
</template>
@ -129,17 +130,21 @@
padding: 8px 12px;
border-radius: var(--border-radius-small);
}
.response {
.response-head {
background-color: var(--color-text-n9);
}
.response-result {
@apply h-full overflow-auto bg-white;
.ms-scroll-bar();
.response {
.response-head {
background-color: var(--color-text-n9);
}
border: 1px solid var(--color-text-n8);
border-radius: var(--border-radius-small);
.arco-spin {
padding: 0;
.response-container {
padding: 0 16px 14px;
border: 1px solid var(--color-text-n8);
border-radius: var(--border-radius-small);
.arco-spin {
padding: 0;
.response-container {
padding: 0 16px 14px;
}
}
}
}

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="h-full">
<div class="mb-[8px] flex items-center gap-[8px]">
<a-input
v-model:model-value="moduleKeyword"
@ -51,7 +51,7 @@
</div>
<a-divider class="my-[8px]" />
<a-spin class="w-full" :style="{ height: `calc(100vh - 300px)` }" :loading="loading">
<a-spin class="w-full" :style="{ height: `calc(100vh - 248px)` }" :loading="loading">
<MsTree
v-model:focus-node-key="focusNodeKey"
v-model:selected-keys="selectedKeys"
@ -178,16 +178,8 @@
const { openModal } = useModal();
const virtualListProps = computed(() => {
if (props.readOnly) {
return {
height: 'calc(60vh - 325px)',
threshold: 200,
fixedSize: true,
buffer: 15, // 10 padding
};
}
return {
height: 'calc(100vh - 300px)',
height: '100%',
threshold: 200,
fixedSize: true,
buffer: 15, // 10 padding

View File

@ -120,7 +120,7 @@
</div>
<a-tooltip v-else :content="step.name">
<div class="step-name-container">
<div class="one-line-text mr-[4px] max-w-[350px] font-medium text-[var(--color-text-1)]">
<div class="step-name-text one-line-text font-medium">
{{ step.name }}
</div>
<MsIcon
@ -155,11 +155,7 @@
</div>
<a-tooltip :content="step.name" :disabled="!step.name">
<div class="step-name-container">
<div
:class="`one-line-text mr-[4px] ${
step.stepType === ScenarioStepType.ONCE_ONLY_CONTROLLER ? 'max-w-[750px]' : 'max-w-[150px]'
} font-normal text-[var(--color-text-1)]`"
>
<div class="step-name-text one-line-text font-normal">
{{ step.name || t('apiScenario.pleaseInputStepDesc') }}
</div>
<MsIcon
@ -1376,6 +1372,9 @@
background-color: var(--color-text-n9) !important;
.arco-tree-node-title {
background-color: var(--color-text-n9) !important;
.step-name-text {
max-width: calc(100% - 244px) !important;
}
}
}
.arco-tree-node-title {
@ -1391,7 +1390,7 @@
gap: 8px;
}
.step-name-container {
@apply flex items-center;
@apply flex flex-1 items-center overflow-hidden;
margin-right: 16px;
&:hover {
@ -1399,6 +1398,11 @@
@apply visible;
}
}
.step-name-text {
margin-right: 4px;
max-width: calc(100% - 170px);
color: var(--color-text-1);
}
.edit-script-name-icon {
@apply invisible cursor-pointer;
@ -1431,6 +1435,9 @@
}
}
}
:deep(.step-tree-node-title) {
@apply w-full;
}
:deep(.step-tree-node-focus) {
background-color: var(--color-text-n9) !important;
.arco-tree-node-title {

View File

@ -1,101 +1,97 @@
<template>
<MsCard no-content-padding simple>
<div class="flex items-center justify-between p-[8px_16px_8px_16px]">
<MsEditableTab
v-model:active-tab="activeScenarioTab"
v-model:tabs="scenarioTabs"
v-permission="['PROJECT_API_SCENARIO:READ+ADD']"
class="flex-1 overflow-hidden"
@add="() => newTab()"
>
<template #label="{ tab }">
<a-tooltip :content="tab.name || tab.label" :mouse-enter-delay="500">
<div class="one-line-text max-w-[144px]">
{{ tab.name || tab.label }}
<MsSplitBox :size="300" :max="0.5">
<template #first>
<div class="flex h-full flex-col">
<div class="flex-1 p-[16px]">
<scenarioModuleTree
ref="scenarioModuleTreeRef"
:is-show-scenario="isShowScenario"
@count-recycle-scenario="selectRecycleCount"
@folder-node-select="handleNodeSelect"
@init="handleModuleInit"
@new-scenario="() => newTab()"
></scenarioModuleTree>
</div>
<a-divider margin="0" />
<div class="case">
<div class="flex items-center px-[20px]" :class="getActiveClass('recycle')" @click="redirectRecycle()">
<MsIcon type="icon-icon_delete-trash_outlined" class="folder-icon" />
<div class="folder-name mx-[4px]">{{ t('apiScenario.tree.recycleBin') }}</div>
<div class="folder-count">({{ recycleModulesCount || 0 }})</div>
</div>
</a-tooltip>
</template>
</MsEditableTab>
<div v-show="activeScenarioTab.id !== 'all'" class="flex items-center gap-[8px]">
<MsEnvironmentSelect :env="activeScenarioTab.environmentId" />
<executeButton
ref="executeButtonRef"
v-permission="['PROJECT_API_SCENARIO:READ+EXECUTE']"
:execute-loading="activeScenarioTab.executeLoading"
@execute="(type) => handleExecute(type)"
@stop-debug="handleStopExecute"
/>
<a-button
v-if="
activeScenarioTab.isNew
? hasAnyPermission(['PROJECT_API_SCENARIO:READ+ADD'])
: hasAnyPermission(['PROJECT_API_SCENARIO:READ+UPDATE'])
"
type="primary"
:loading="saveLoading"
@click="saveScenario"
>
{{ t('common.save') }}
</a-button>
</div>
</div>
<a-divider class="!my-0" />
<div v-if="activeScenarioTab.id === 'all'" class="pageWrap">
<MsSplitBox :size="300" :max="0.5">
<template #first>
<div class="flex h-full flex-col">
<div class="p-[16px]">
<scenarioModuleTree
ref="scenarioModuleTreeRef"
:is-show-scenario="isShowScenario"
@count-recycle-scenario="selectRecycleCount"
@folder-node-select="handleNodeSelect"
@init="handleModuleInit"
@new-scenario="() => newTab()"
></scenarioModuleTree>
</div>
<div class="flex-1">
<a-divider margin="0" />
<div class="case">
<div class="flex items-center px-[20px]" :class="getActiveClass('recycle')" @click="redirectRecycle()">
<MsIcon type="icon-icon_delete-trash_outlined" class="folder-icon" />
<div class="folder-name mx-[4px]">{{ t('apiScenario.tree.recycleBin') }}</div>
<div class="folder-count">({{ recycleModulesCount || 0 }})</div>
</div>
</div>
</template>
<template #second>
<div class="flex items-center justify-between p-[8px_16px_8px_16px]">
<MsEditableTab
v-model:active-tab="activeScenarioTab"
v-model:tabs="scenarioTabs"
v-permission="['PROJECT_API_SCENARIO:READ+ADD']"
class="flex-1 overflow-hidden"
@add="() => newTab()"
>
<template #label="{ tab }">
<a-tooltip :content="tab.name || tab.label" :mouse-enter-delay="500">
<div class="one-line-text max-w-[144px]">
{{ tab.name || tab.label }}
</div>
</div>
</div>
</div>
</template>
<template #second>
<div class="overflow-x-hidden">
<ScenarioTable
ref="apiTableRef"
:active-module="activeModule"
:offspring-ids="offspringIds"
@refresh-module-tree="refreshTree"
@open-scenario="openScenarioTab"
@create-scenario="() => newTab()"
</a-tooltip>
</template>
</MsEditableTab>
<div v-show="activeScenarioTab.id !== 'all'" class="flex items-center gap-[8px]">
<MsEnvironmentSelect :env="activeScenarioTab.environmentId" />
<executeButton
ref="executeButtonRef"
v-permission="['PROJECT_API_SCENARIO:READ+EXECUTE']"
:execute-loading="activeScenarioTab.executeLoading"
@execute="(type) => handleExecute(type)"
@stop-debug="handleStopExecute"
/>
<a-button
v-if="
activeScenarioTab.isNew
? hasAnyPermission(['PROJECT_API_SCENARIO:READ+ADD'])
: hasAnyPermission(['PROJECT_API_SCENARIO:READ+UPDATE'])
"
type="primary"
:loading="saveLoading"
@click="saveScenario"
>
{{ t('common.save') }}
</a-button>
</div>
</template>
</MsSplitBox>
</div>
<div v-else-if="activeScenarioTab.isNew" class="pageWrap">
<create
ref="createRef"
v-model:scenario="activeScenarioTab"
:module-tree="moduleTree"
@batch-debug="realExecute($event, false)"
></create>
</div>
<div v-else class="pageWrap">
<detail
ref="detailRef"
v-model:scenario="activeScenarioTab"
:module-tree="moduleTree"
@batch-debug="realExecute($event, false)"
></detail>
</div>
</div>
<a-divider class="!my-0" />
<div v-if="activeScenarioTab.id === 'all'" class="pageWrap overflow-x-hidden">
<ScenarioTable
ref="apiTableRef"
:active-module="activeModule"
:offspring-ids="offspringIds"
@refresh-module-tree="refreshTree"
@open-scenario="openScenarioTab"
@create-scenario="() => newTab()"
/>
</div>
<div v-else-if="activeScenarioTab.isNew" class="pageWrap">
<create
ref="createRef"
v-model:scenario="activeScenarioTab"
:module-tree="moduleTree"
@batch-debug="realExecute($event, false)"
></create>
</div>
<div v-else class="pageWrap">
<detail
ref="detailRef"
v-model:scenario="activeScenarioTab"
:module-tree="moduleTree"
@batch-debug="realExecute($event, false)"
></detail>
</div>
</template>
</MsSplitBox>
</MsCard>
</template>
@ -707,43 +703,43 @@
height: calc(100% - 50px);
border-radius: var(--border-radius-large);
@apply bg-white;
.case {
padding: 8px 4px;
border-radius: var(--border-radius-small);
@apply flex cursor-pointer items-center justify-between;
&:hover {
background-color: rgb(var(--primary-1));
}
.folder-icon {
margin-right: 4px;
color: var(--color-text-4);
}
.folder-name {
color: var(--color-text-1);
}
}
.case {
padding: 8px 4px;
border-radius: var(--border-radius-small);
@apply flex cursor-pointer items-center justify-between;
&:hover {
background-color: rgb(var(--primary-1));
}
.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);
}
.case-active {
.folder-icon,
.folder-name,
.folder-count {
margin-left: 4px;
color: var(--color-text-4);
color: rgb(var(--primary-5));
}
.case-active {
.folder-icon,
.folder-name,
.folder-count {
color: rgb(var(--primary-5));
}
}
.back {
margin-right: 8px;
width: 20px;
height: 20px;
border: 1px solid #ffffff;
background: linear-gradient(90deg, rgb(var(--primary-9)) 3.36%, #ffffff 100%);
box-shadow: 0 0 7px rgb(15 0 78 / 9%);
.arco-icon {
color: rgb(var(--primary-5));
}
@apply flex cursor-pointer items-center rounded-full;
}
.back {
margin-right: 8px;
width: 20px;
height: 20px;
border: 1px solid #ffffff;
background: linear-gradient(90deg, rgb(var(--primary-9)) 3.36%, #ffffff 100%);
box-shadow: 0 0 7px rgb(15 0 78 / 9%);
.arco-icon {
color: rgb(var(--primary-5));
}
@apply flex cursor-pointer items-center rounded-full;
}
}
.recycle {

View File

@ -1,48 +1,46 @@
<template>
<MsCard has-breadcrumb no-content-padding simple>
<div class="p-[24px_24px_8px_24px]">
<MsEditableTab
v-model:active-tab="activeApiTab"
v-model:tabs="apiTabs"
class="flex-1 overflow-hidden"
:show-add="false"
:readonly="true"
@add="newTab"
>
<template #label="{ tab }">
<a-tooltip :content="tab.label" :mouse-enter-delay="500">
<div class="one-line-text max-w-[144px]">
{{ tab.label }}
</div>
</a-tooltip>
</template>
</MsEditableTab>
</div>
<a-divider class="!my-0" />
<div v-if="activeApiTab.id === 'all'" class="pageWrap">
<MsSplitBox :size="300" :max="0.5">
<template #first>
<div class="flex h-full flex-col">
<div class="p-[16px]">
<recycleTree
ref="recycleTreeRef"
:is-show-scenario="isShowScenario"
@folder-node-select="handleNodeSelect"
@init="handleModuleInit"
></recycleTree>
</div>
</div>
</template>
<template #second>
<MsSplitBox :size="300" :max="0.5">
<template #first>
<div class="h-full p-[16px]">
<recycleTree
ref="recycleTreeRef"
:is-show-scenario="isShowScenario"
@folder-node-select="handleNodeSelect"
@init="handleModuleInit"
></recycleTree>
</div>
</template>
<template #second>
<div class="p-[24px_24px_8px_24px]">
<MsEditableTab
v-model:active-tab="activeApiTab"
v-model:tabs="apiTabs"
class="flex-1 overflow-hidden"
:show-add="false"
:readonly="true"
@add="newTab"
>
<template #label="{ tab }">
<a-tooltip :content="tab.label" :mouse-enter-delay="500">
<div class="one-line-text max-w-[144px]">
{{ tab.label }}
</div>
</a-tooltip>
</template>
</MsEditableTab>
</div>
<a-divider class="!my-0" />
<div v-if="activeApiTab.id === 'all'" class="pageWrap">
<RecycleTable
ref="apiTableRef"
:active-module="activeModule"
:offspring-ids="offspringIds"
@refresh-module-tree="refreshTree"
/>
</template>
</MsSplitBox>
</div>
</div>
</template>
</MsSplitBox>
<!-- <detail v-else v-model:scenario="activeApiTab"></detail> -->
</MsCard>
</template>

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="h-full">
<div class="folder" @click="setActiveFolder('all')">
<div :class="allFolderClass">
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
@ -22,7 +22,7 @@
allow-clear
/>
</div>
<a-spin class="w-full" :loading="loading">
<a-spin class="w-full" :style="{ height: `calc(100vh - 248px)` }" :loading="loading">
<MsTree
v-model:focus-node-key="focusNodeKey"
v-model:selected-keys="selectedKeys"
@ -102,7 +102,7 @@
const virtualListProps = computed(() => {
return {
height: 'calc(100vh - 343px)',
height: '100%',
threshold: 200,
fixedSize: true,
buffer: 15, // 10 padding

View File

@ -210,7 +210,6 @@
<div class="mt-[16px] h-[calc(100%-32px)] border-t border-[var(--color-text-n8)]">
<!-- 脑图开始 -->
<MsFeatureCaseMinder
minder-type="FeatureCase"
:module-id="props.activeFolder"
:modules-count="props.modulesCount"
:module-name="props.moduleName"

View File

@ -102,7 +102,7 @@
</MsCard>
<!-- special-height的174: 上面卡片高度158 + mt的16 -->
<MsCard class="mt-[16px]" :special-height="174" simple has-breadcrumb no-content-padding>
<Plan v-if="activeTab === 'plan'" :plan-id="planId" />
<Plan v-if="activeTab === 'plan'" :plan-id="planId" @refresh="initDetail" />
<FeatureCase
v-if="activeTab === 'featureCase'"
ref="featureCaseRef"

View File

@ -1,8 +1,8 @@
<template>
<div class="flex h-full flex-col">
<MsNotRemind tip="testPlan.planTip" class="p-[16px]" type="info" visited-key="testPlanTip" />
<div class="flex-1 overflow-hidden p-[16px]">
<MsTestPlanMinder :plan-id="props.planId" />
<div class="flex h-full flex-col p-[16px]">
<MsNotRemind tip="testPlan.planTip" class="mb-[16px]" type="info" visited-key="testPlanTip" />
<div class="flex-1 overflow-hidden">
<MsTestPlanMinder :plan-id="props.planId" @save="emit('refresh')" />
</div>
</div>
</template>
@ -14,6 +14,9 @@
const props = defineProps<{
planId: string;
}>();
const emit = defineEmits<{
(e: 'refresh'): void;
}>();
</script>
<style lang="less" scoped></style>