fix(全局): 部分 bug 修复

This commit is contained in:
baiqi 2024-06-20 17:12:44 +08:00 committed by Craftsman
parent fbd0a27486
commit eff826b97c
25 changed files with 275 additions and 227 deletions

View File

@ -295,7 +295,9 @@
}); });
const buttonDropDownVisible = ref(false); const buttonDropDownVisible = ref(false);
onBeforeMount(() => { watch(
() => fileList.value,
() => {
// //
const defaultFiles = fileList.value.filter((item) => item) || []; const defaultFiles = fileList.value.filter((item) => item) || [];
if (defaultFiles.length > 0) { if (defaultFiles.length > 0) {
@ -314,7 +316,11 @@
.map((item) => item?.[props.fields.id] || '') .map((item) => item?.[props.fields.id] || '')
.filter((item) => item); .filter((item) => item);
} }
}); },
{
immediate: true,
}
);
function handleChange(_fileList: MsFileItem[], fileItem: MsFileItem) { function handleChange(_fileList: MsFileItem[], fileItem: MsFileItem) {
if (props.multiple) { if (props.multiple) {

View File

@ -12,7 +12,7 @@
} }
" "
> >
<span class="mx-[2px]"></span> <span class="mx-[2px]"><slot></slot></span>
<template #content> <template #content>
<div class="flex flex-col gap-[16px] text-[14px]"> <div class="flex flex-col gap-[16px] text-[14px]">
<div class="font-semibold text-[var(--color-text-1)]"> <div class="font-semibold text-[var(--color-text-1)]">
@ -68,7 +68,6 @@
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
visible: boolean;
savingFile?: MsFileItem; savingFile?: MsFileItem;
fileSaveAsSourceId: string | number; fileSaveAsSourceId: string | number;
sourceIdKey?: string; // idkey sourceIdKey?: string; // idkey
@ -89,7 +88,7 @@
const { t } = useI18n(); const { t } = useI18n();
const saveFilePopoverVisible = defineModel<boolean>('visible', { const saveFilePopoverVisible = defineModel<boolean>('visible', {
required: true, default: false,
}); });
const saveFileForm = ref({ const saveFileForm = ref({
name: '', name: '',

View File

@ -2,7 +2,7 @@
import { computed, defineComponent, h, ref } from 'vue'; import { computed, defineComponent, h, ref } from 'vue';
import { RouteRecordRaw, useRoute, useRouter } from 'vue-router'; import { RouteRecordRaw, useRoute, useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es'; import { cloneDeep, debounce } from 'lodash-es';
import MsAvatar from '@/components/pure/ms-avatar/index.vue'; import MsAvatar from '@/components/pure/ms-avatar/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue';
@ -407,7 +407,7 @@
// //
const renderMenuItem = (element: RouteRecordRaw | null, icon: (() => any) | null) => const renderMenuItem = (element: RouteRecordRaw | null, icon: (() => any) | null) =>
element?.name === SettingRouteEnum.SETTING_ORGANIZATION ? ( element?.name === SettingRouteEnum.SETTING_ORGANIZATION ? (
<a-menu-item key={element?.name} v-slots={{ icon }} onClick={() => goto(element)}> <a-menu-item key={element?.name} v-slots={{ icon }} onClick={debounce(() => goto(element), 100)}>
<div class="inline-flex w-[calc(100%-34px)] items-center justify-between !bg-transparent"> <div class="inline-flex w-[calc(100%-34px)] items-center justify-between !bg-transparent">
{collapsed.value ? ( {collapsed.value ? (
<div class="text-center text-[12px] leading-[16px]"> <div class="text-center text-[12px] leading-[16px]">
@ -436,7 +436,7 @@
</div> </div>
</a-menu-item> </a-menu-item>
) : ( ) : (
<a-menu-item key={element?.name} v-slots={{ icon }} onClick={() => goto(element)}> <a-menu-item key={element?.name} v-slots={{ icon }} onClick={debounce(() => goto(element), 100)}>
{collapsed.value ? ( {collapsed.value ? (
<div class="text-center text-[12px] leading-[16px]"> <div class="text-center text-[12px] leading-[16px]">
{t(element?.meta?.collapsedLocale || element?.meta?.locale || '')} {t(element?.meta?.collapsedLocale || element?.meta?.locale || '')}

View File

@ -37,23 +37,19 @@
> >
{{ t('ms.upload.preview') }} {{ t('ms.upload.preview') }}
</MsButton> </MsButton>
<MsButton type="button" status="primary" class="!mr-[4px]" @click="transferFile(item)">
{{ t('caseManagement.featureCase.storage') }}
</MsButton>
<SaveAsFilePopover <SaveAsFilePopover
v-model:visible="transferVisible" v-if="item.status !== 'init'"
:saving-file="activeTransferFileParams" :saving-file="activeTransferFileParams"
:file-save-as-source-id="activeCase.id || ''" :file-save-as-source-id="activeCase.id || ''"
:file-save-as-api="transferFileRequest" :file-save-as-api="transferFileRequest"
:file-module-options-api="getTransferFileTree" :file-module-options-api="getTransferFileTree"
source-id-key="caseId" source-id-key="caseId"
/>
<MsButton
v-if="item.status !== 'init'"
type="button"
status="primary"
class="!mr-[4px]"
@click="transferFile(item)"
> >
{{ t('caseManagement.featureCase.storage') }} <span :id="item.uid"></span>
</MsButton> </SaveAsFilePopover>
<MsButton <MsButton
v-if="item.status !== 'init'" v-if="item.status !== 'init'"
type="button" type="button"
@ -198,7 +194,7 @@
return item; return item;
}); });
} }
Message.success(t('ms.upload.uploadSuccess')); Message.success(t('common.linkSuccess'));
emit('uploadSuccess'); emit('uploadSuccess');
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -244,14 +240,12 @@
} }
} }
const transferVisible = ref<boolean>(false);
const activeTransferFileParams = ref<MsFileItem>(); const activeTransferFileParams = ref<MsFileItem>();
// //
function transferFile(item: MsFileItem) { function transferFile(item: MsFileItem) {
activeTransferFileParams.value = { ...item }; activeTransferFileParams.value = { ...item };
transferVisible.value = true; document.getElementById(item.uid)?.click();
} }
// //

View File

@ -87,7 +87,7 @@
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import useMinderStore from '@/store/modules/components/minder-editor/index'; import useMinderStore from '@/store/modules/components/minder-editor/index';
import { MinderCustomEvent } from '@/store/modules/components/minder-editor/types'; import { MinderCustomEvent } from '@/store/modules/components/minder-editor/types';
import { filterTree, getGenerateId, mapTree, replaceNodeInTree } from '@/utils'; import { filterTree, findNodeByKey, getGenerateId, mapTree, replaceNodeInTree } from '@/utils';
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
import { import {
@ -156,10 +156,9 @@
/** /**
* 初始化用例模块树 * 初始化用例模块树
*/ */
async function initCaseTree(notRemote = false) { async function initCaseTree() {
try { try {
loading.value = true; loading.value = true;
if (!notRemote) {
const res = await getCaseMinderTree({ const res = await getCaseMinderTree({
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,
moduleId: '', // moduleId: '', //
@ -171,7 +170,7 @@
id: e.id || e.data?.id || '', id: e.id || e.data?.id || '',
text: e.name || e.data?.text || '', text: e.name || e.data?.text || '',
resource: props.modulesCount[e.id] !== undefined ? [moduleTag] : e.data?.resource, resource: props.modulesCount[e.id] !== undefined ? [moduleTag] : e.data?.resource,
expandState: e.level === 1 ? 'expand' : 'collapse', expandState: e.level === 0 ? 'expand' : 'collapse',
count: props.modulesCount[e.id], count: props.modulesCount[e.id],
isNew: false, isNew: false,
changed: false, changed: false,
@ -202,24 +201,21 @@
}; };
importJson.value.treePath = []; importJson.value.treePath = [];
window.minder.importJson(importJson.value); window.minder.importJson(importJson.value);
}
if (notRemote) {
if (props.moduleId !== 'all') { if (props.moduleId !== 'all') {
// ID // ID
nextTick(() => { nextTick(() => {
minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, undefined, [ minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, undefined, [
window.minder.getNodeById(props.moduleId), findNodeByKey(importJson.value.root.children || [], props.moduleId, 'id', 'data') as MinderJsonNode,
]); ]);
}); });
} else { } else {
// ID //
nextTick(() => { nextTick(() => {
minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, undefined, [ minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, undefined, [
importJson.value.root, importJson.value.root,
]); ]);
}); });
} }
}
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
@ -291,7 +287,10 @@
try { try {
baseInfoLoading.value = true; baseInfoLoading.value = true;
const res = await getCaseDetail(data?.id || activeCase.value.id); const res = await getCaseDetail(data?.id || activeCase.value.id);
activeCase.value = res; activeCase.value = {
...res,
isNew: false,
};
const fileIds = (res.attachments || []).map((item: any) => item.id) || []; const fileIds = (res.attachments || []).map((item: any) => item.id) || [];
if (fileIds.length) { if (fileIds.length) {
checkUpdateFileIds.value = await checkFileIsUpdateRequest(fileIds); checkUpdateFileIds.value = await checkFileIsUpdateRequest(fileIds);
@ -389,7 +388,7 @@
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,
moduleId: data.id, moduleId: data.id,
}); });
const fakeNode = node.children?.find((e) => e.data?.id === undefined); // const fakeNode = node.children?.find((e) => e.data?.id === 'fakeNode'); //
if (fakeNode) { if (fakeNode) {
window.minder.removeNode(fakeNode); window.minder.removeNode(fakeNode);
} }
@ -464,6 +463,10 @@
extraVisible.value = false; extraVisible.value = false;
showDetailMenu.value = false; showDetailMenu.value = false;
resetExtractInfo(); resetExtractInfo();
const fakeNode = node.children?.find((e) => e.data?.id === 'fakeNode'); //
if (fakeNode) {
window.minder.removeNode(fakeNode);
}
} }
setPriorityView(true, 'P'); setPriorityView(true, 'P');
} }
@ -483,20 +486,16 @@
node.data.isNew = true; node.data.isNew = true;
window.minder.execCommand('priority'); window.minder.execCommand('priority');
if (index === nodes.length - 1) { if (index === nodes.length - 1) {
nextTick(() => { window.minder.toggleSelect(node);
handleNodeSelect(node);
});
} }
} else if (node.data?.resource?.includes(caseTag)) { } else if (tag === caseTag && node.data) {
// //
tempMinderParams.value.updateModuleList = tempMinderParams.value.updateModuleList.filter( tempMinderParams.value.updateModuleList = tempMinderParams.value.updateModuleList.filter(
(e) => e.id !== node.data?.id (e) => e.id !== node.data?.id
); );
node.data.isNew = true; node.data.isNew = true;
if (index === nodes.length - 1) { if (index === nodes.length - 1) {
nextTick(() => { window.minder.toggleSelect(node);
handleNodeSelect(node);
});
} }
} else if (node.data?.resource?.some((e) => caseOffspringTags.includes(e))) { } else if (node.data?.resource?.some((e) => caseOffspringTags.includes(e))) {
// //
@ -522,7 +521,13 @@
case MinderEventName.CUT_NODE: case MinderEventName.CUT_NODE:
// TODO: // TODO:
nodes.forEach((node) => { nodes.forEach((node) => {
if (!caseOffspringTags.some((e) => node.data?.resource?.includes(e))) { if (!node.data?.resource || node.data?.resource.length === 0) {
//
tempMinderParams.value.deleteResourceList.push({
id: node.data?.id || '',
type: 'NONE',
});
} else if (!caseOffspringTags.some((e) => node.data?.resource?.includes(e))) {
// //
tempMinderParams.value.deleteResourceList.push({ tempMinderParams.value.deleteResourceList.push({
id: node.data?.id || '', id: node.data?.id || '',
@ -619,7 +624,7 @@
versionId: '', versionId: '',
updateCaseList: [], updateCaseList: [],
updateModuleList: [], updateModuleList: [],
deleteResourceList: [], deleteResourceList: tempMinderParams.value.deleteResourceList, //
additionalNodeList: [], additionalNodeList: [],
}; };
} }
@ -702,7 +707,7 @@
watch( watch(
() => props.moduleId, () => props.moduleId,
() => { () => {
initCaseTree(true); initCaseTree();
}, },
{ {
deep: true, deep: true,

View File

@ -615,7 +615,10 @@ export default function useMinderBaseApi({ hasEditPermission }: { hasEditPermiss
} }
if ([stepTag, textDescTag].some((tag) => node.data?.resource?.includes(tag))) { if ([stepTag, textDescTag].some((tag) => node.data?.resource?.includes(tag))) {
// 用例下的文本描述和步骤描述节点 // 用例下的文本描述和步骤描述节点
if (node.data?.resource?.includes(stepExpectTag)) { if (
node.children?.length === 0 &&
minderStore.clipboard.every((e) => e.data?.resource?.includes(stepExpectTag))
) {
// 粘贴的是期望结果节点 // 粘贴的是期望结果节点
return false; return false;
} }

View File

@ -23,7 +23,10 @@
@save="handleMinderSave" @save="handleMinderSave"
> >
<template #extractMenu> <template #extractMenu>
<a-tooltip v-if="showAssociateCaseMenu" :content="t('ms.case.associate.title')"> <a-tooltip
v-if="showAssociateCaseMenu && hasAnyPermission(['PROJECT_TEST_PLAN:READ+ASSOCIATION'])"
:content="t('ms.case.associate.title')"
>
<MsButton type="icon" class="ms-minder-node-float-menu-icon-button" @click="associateCase"> <MsButton type="icon" class="ms-minder-node-float-menu-icon-button" @click="associateCase">
<MsIcon type="icon-icon_add_outlined" class="text-[var(--color-text-4)]" /> <MsIcon type="icon-icon_add_outlined" class="text-[var(--color-text-4)]" />
</MsButton> </MsButton>
@ -86,7 +89,11 @@
</a-tooltip> </a-tooltip>
</div> </div>
<a-form ref="configFormRef" :model="configForm" :disabled="!hasEditPermission" layout="vertical"> <a-form ref="configFormRef" :model="configForm" :disabled="!hasEditPermission" layout="vertical">
<a-form-item v-if="hasEditPermission && configForm.level === 2"> <a-form-item
v-if="
hasEditPermission && hasAnyPermission(['PROJECT_TEST_PLAN:READ+ASSOCIATION']) && configForm.level === 2
"
>
<template #label> <template #label>
<div class="flex items-center"> <div class="flex items-center">
<div>{{ t('testPlan.planForm.pickCases') }}</div> <div>{{ t('testPlan.planForm.pickCases') }}</div>
@ -116,7 +123,6 @@
</div> </div>
<a-divider margin="8px" direction="vertical" /> <a-divider margin="8px" direction="vertical" />
<MsButton <MsButton
v-permission="['CASE_REVIEW:READ+RELEVANCE']"
type="text" type="text"
class="font-medium" class="font-medium"
:disabled="!hasEditPermission" :disabled="!hasEditPermission"
@ -127,6 +133,15 @@
</div> </div>
</div> </div>
</a-form-item> </a-form-item>
<a-form-item
v-if="configForm.type !== PlanMinderCollectionType.FUNCTIONAL && configForm.level === 2"
class="hidden-item"
>
<div class="flex items-center gap-[8px]">
<a-switch v-model:model-value="configForm.extended" size="small" @change="handleExtendChange"></a-switch>
<div>{{ t('ms.minders.extend') }}</div>
</div>
</a-form-item>
<template v-if="configForm.type !== PlanMinderCollectionType.FUNCTIONAL"> <template v-if="configForm.type !== PlanMinderCollectionType.FUNCTIONAL">
<a-form-item :label="t('system.project.resourcePool')"> <a-form-item :label="t('system.project.resourcePool')">
<a-select <a-select
@ -211,15 +226,6 @@
</a-form-item> </a-form-item>
</template> --> </template> -->
</template> </template>
<a-form-item
v-if="configForm.type !== PlanMinderCollectionType.FUNCTIONAL && configForm.level === 2"
class="hidden-item"
>
<div class="flex items-center gap-[8px]">
<a-switch v-model:model-value="configForm.extended" size="small" @change="handleExtendChange"></a-switch>
<div>{{ t('ms.minders.extend') }}</div>
</div>
</a-form-item>
</a-form> </a-form>
<div v-if="hasEditPermission" class="flex items-center gap-[12px] bg-white pb-[16px]"> <div v-if="hasEditPermission" class="flex items-center gap-[12px] bg-white pb-[16px]">
<a-button <a-button
@ -538,30 +544,6 @@
return false; return false;
} }
/**
* 处理节点选中
* @param node 节点
*/
function handleNodeSelect(node: PlanMinderNode) {
if (checkConfigFormUnsaved()) {
return;
}
checkNodeCanShowMenu(node);
if (extraVisible.value) {
if (node.data?.type === PlanMinderCollectionType.FUNCTIONAL && node.data?.level === 1) {
//
extraVisible.value = false;
return;
}
activePlanSet.value = node;
switchingConfigFormData.value = true;
configForm.value = cloneDeep(activePlanSet.value.data);
nextTick(() => {
switchingConfigFormData.value = false;
});
}
}
/** /**
* 切换测试集配置显示 * 切换测试集配置显示
*/ */
@ -633,6 +615,10 @@
refId: '', refId: '',
projectId: '', projectId: '',
}; };
const node: PlanMinderNode = window.minder.getNodeById(activePlanSet.value?.data.id);
if (node?.data) {
node.data.associateDTOS = [];
}
} }
function associateCase() { function associateCase() {
@ -658,6 +644,7 @@
() => { () => {
if (!switchingConfigFormData.value && configForm.value) { if (!switchingConfigFormData.value && configForm.value) {
configFormUnsaved.value = true; configFormUnsaved.value = true;
minderStore.setMinderUnsaved(true);
} }
}, },
{ {
@ -665,6 +652,31 @@
} }
); );
/**
* 处理节点选中
* @param node 节点
*/
function handleNodeSelect(node: PlanMinderNode) {
if (checkConfigFormUnsaved()) {
return;
}
checkNodeCanShowMenu(node);
if (extraVisible.value) {
if (node.data?.type === PlanMinderCollectionType.FUNCTIONAL && node.data?.level === 1) {
//
extraVisible.value = false;
return;
}
clearSelectedCases();
activePlanSet.value = node;
switchingConfigFormData.value = true;
configForm.value = cloneDeep(activePlanSet.value.data);
nextTick(() => {
switchingConfigFormData.value = false;
});
}
}
/** /**
* 是否停止拖拽排序动作 * 是否停止拖拽排序动作
* @param dragNode 拖动节点 * @param dragNode 拖动节点
@ -828,7 +840,7 @@
editList: [], editList: [],
deletedIds: [], deletedIds: [],
}; };
selectedAssociateCasesParams.value.selectIds = []; clearSelectedCases();
} }
} }

View File

@ -4,7 +4,7 @@ export default {
'ms.personal.baseInfo': 'Basic Information', 'ms.personal.baseInfo': 'Basic Information',
'ms.personal.setPsw': 'Password settings', 'ms.personal.setPsw': 'Password settings',
'ms.personal.setting': 'Personal settings', 'ms.personal.setting': 'Personal settings',
'ms.personal.apiKey': 'APIKEY', 'ms.personal.apiKey': 'API KEY',
'ms.personal.tripartite': 'Tripartite account', 'ms.personal.tripartite': 'Tripartite account',
'ms.personal.changeAvatar': 'Change avatar', 'ms.personal.changeAvatar': 'Change avatar',
'ms.personal.name': 'User name', 'ms.personal.name': 'User name',
@ -28,7 +28,7 @@ export default {
'ms.personal.changePswTip': 'After changing the password, you need to use the new password to log in to the system', 'ms.personal.changePswTip': 'After changing the password, you need to use the new password to log in to the system',
'ms.personal.updatePswSuccess': 'ms.personal.updatePswSuccess':
'The password has been modified successfully and will automatically log out in {count} seconds. Please log in with the new password.', 'The password has been modified successfully and will automatically log out in {count} seconds. Please log in with the new password.',
'ms.personal.apiKeyTip': 'The MeterSphere API is accessible through the APIKEY', 'ms.personal.apiKeyTip': 'The MeterSphere API is accessible through the API KEY',
'ms.personal.expireTime': 'Expiration', 'ms.personal.expireTime': 'Expiration',
'ms.personal.expired': 'Expired', 'ms.personal.expired': 'Expired',
'ms.personal.expiredTip': 'The expiration time can be changed in [Settings]', 'ms.personal.expiredTip': 'The expiration time can be changed in [Settings]',
@ -36,7 +36,7 @@ export default {
'ms.personal.setValidTime': 'Set effective time', 'ms.personal.setValidTime': 'Set effective time',
'ms.personal.createTime': 'Created time', 'ms.personal.createTime': 'Created time',
'ms.personal.copySuccess': 'Copied successfully', 'ms.personal.copySuccess': 'Copied successfully',
'ms.personal.maxTip': 'Up to 5 APIKEYs can be added', 'ms.personal.maxTip': 'Up to 5 API KEYs can be added',
'ms.personal.confirmClose': 'Confirm to close?', 'ms.personal.confirmClose': 'Confirm to close?',
'ms.personal.closeTip': 'ms.personal.closeTip':
'After closing, the test tasks executed using the Access Key will fail. Please operate with caution!', 'After closing, the test tasks executed using the Access Key will fail. Please operate with caution!',

View File

@ -4,7 +4,7 @@ export default {
'ms.personal.baseInfo': '基本信息', 'ms.personal.baseInfo': '基本信息',
'ms.personal.setPsw': '密码设置', 'ms.personal.setPsw': '密码设置',
'ms.personal.setting': '个人设置', 'ms.personal.setting': '个人设置',
'ms.personal.apiKey': 'APIKEY', 'ms.personal.apiKey': 'API KEY',
'ms.personal.tripartite': '三方平台账号', 'ms.personal.tripartite': '三方平台账号',
'ms.personal.changeAvatar': '更换头像', 'ms.personal.changeAvatar': '更换头像',
'ms.personal.name': '姓名', 'ms.personal.name': '姓名',
@ -26,7 +26,7 @@ export default {
'ms.personal.newPsw': '新密码', 'ms.personal.newPsw': '新密码',
'ms.personal.changePswTip': '修改密码后,需要使用新的密码登录系统', 'ms.personal.changePswTip': '修改密码后,需要使用新的密码登录系统',
'ms.personal.updatePswSuccess': '密码修改成功,将在 {count} 秒后自动退出,请使用新密码登录', 'ms.personal.updatePswSuccess': '密码修改成功,将在 {count} 秒后自动退出,请使用新密码登录',
'ms.personal.apiKeyTip': '可通过 APIKEY 访问 MeterSphere API', 'ms.personal.apiKeyTip': '可通过 API KEY 访问 MeterSphere API',
'ms.personal.expireTime': '过期时间', 'ms.personal.expireTime': '过期时间',
'ms.personal.expired': '已到期', 'ms.personal.expired': '已到期',
'ms.personal.expiredTip': '可在【设置】内更改到期时间', 'ms.personal.expiredTip': '可在【设置】内更改到期时间',
@ -34,7 +34,7 @@ export default {
'ms.personal.setValidTime': '设置有效时间', 'ms.personal.setValidTime': '设置有效时间',
'ms.personal.createTime': '创建时间', 'ms.personal.createTime': '创建时间',
'ms.personal.copySuccess': '复制成功', 'ms.personal.copySuccess': '复制成功',
'ms.personal.maxTip': '最多可添加 5 个APIKEY', 'ms.personal.maxTip': '最多可添加 5 个API KEY',
'ms.personal.confirmClose': '确认关闭吗?', 'ms.personal.confirmClose': '确认关闭吗?',
'ms.personal.closeTip': '关闭后,将导致使用该 Access Key 执行的测试任务执行失败,请谨慎操作!', 'ms.personal.closeTip': '关闭后,将导致使用该 Access Key 执行的测试任务执行失败,请谨慎操作!',
'ms.personal.closeSuccess': '关闭成功', 'ms.personal.closeSuccess': '关闭成功',

View File

@ -26,12 +26,8 @@
import MsSelect from '@/components/business/ms-select/index'; import MsSelect from '@/components/business/ms-select/index';
import { useUserStore } from '@/store';
import initOptionsFunc, { UserRequestTypeEnum } from './utils'; import initOptionsFunc, { UserRequestTypeEnum } from './utils';
const userStore = useUserStore();
defineOptions({ name: 'MsUserSelector' }); defineOptions({ name: 'MsUserSelector' });
export interface MsUserSelectorOption { export interface MsUserSelectorOption {
@ -99,7 +95,7 @@
}; };
const optionLabelRender = (option: SelectOptionData) => { const optionLabelRender = (option: SelectOptionData) => {
if (option.email !== '') { if (option.email !== '') {
return `<span class='text-[var(--color-text-1)]'>${option.name}</span><span class='text-[var(--color-text-4)] ml-[4px]'>(${option.email})</span>`; return `<span class='text-[var(--color-text-1)]'>${option.name}</span><span class='text-[var(--color-text-4)] ml-[4px]'>${option.email}</span>`;
} }
return `<span class='text-[var(--color-text-1)]'>${option.name}</span>`; return `<span class='text-[var(--color-text-1)]'>${option.name}</span>`;
}; };

View File

@ -12,7 +12,12 @@
</a-breadcrumb-item> </a-breadcrumb-item>
</a-breadcrumb> </a-breadcrumb>
</div> </div>
<nodeFloatMenu v-if="props.canShowFloatMenu" v-bind="props" @close="emit('floatMenuClose')"> <nodeFloatMenu
v-if="props.canShowFloatMenu"
v-bind="props"
v-model:visible="floatMenuVisible"
@close="emit('floatMenuClose')"
>
<template #extractMenu> <template #extractMenu>
<slot name="extractMenu"></slot> <slot name="extractMenu"></slot>
</template> </template>
@ -21,13 +26,15 @@
</template> </template>
<script lang="ts" name="minderContainer" setup> <script lang="ts" name="minderContainer" setup>
import { cloneDeep } from 'lodash-es';
import nodeFloatMenu from '../menu/nodeFloatMenu.vue'; import nodeFloatMenu from '../menu/nodeFloatMenu.vue';
import minderHeader from './header.vue'; import minderHeader from './header.vue';
import Navigator from './navigator.vue'; import Navigator from './navigator.vue';
import useLeaveUnSaveTip from '@/hooks/useLeaveUnSaveTip'; import useLeaveUnSaveTip from '@/hooks/useLeaveUnSaveTip';
import useMinderStore from '@/store/modules/components/minder-editor'; import useMinderStore from '@/store/modules/components/minder-editor';
import { findNodePathByKey, replaceNodeInTree } from '@/utils'; import { findNodePathByKey, mapTree, replaceNodeInTree } from '@/utils';
import { MinderEventName } from '@/enums/minderEnum'; import { MinderEventName } from '@/enums/minderEnum';
@ -139,9 +146,6 @@
}); });
}); });
const menuVisible = ref(false);
const menuPopupOffset = ref([0, 0]);
function getCurrentTreePath() { function getCurrentTreePath() {
if (innerImportJson.value.root.id === 'NONE' || innerImportJson.value.treePath?.length <= 1) { if (innerImportJson.value.root.id === 'NONE' || innerImportJson.value.treePath?.length <= 1) {
return []; return [];
@ -176,12 +180,15 @@
const root: MinderJsonNode = window.minder.getRoot(); const root: MinderJsonNode = window.minder.getRoot();
window.minder.toggleSelect(root); // window.minder.toggleSelect(root); //
window.minder.select(root); // window.minder.select(root); //
window.minder.execCommand('ExpandToLevel', 1);
currentTreePath.value = getCurrentTreePath(); currentTreePath.value = getCurrentTreePath();
setTimeout(() => { setTimeout(() => {
window.minder.execCommand('camera', root); window.minder.execCommand('camera', root);
}, 100); // TODO: }, 100); // TODO:
} }
const floatMenuVisible = ref(false);
function save() { function save() {
let data = importJson.value; let data = importJson.value;
if (innerImportJson.value.treePath?.length > 1) { if (innerImportJson.value.treePath?.length > 1) {
@ -193,25 +200,33 @@
'id' 'id'
); );
} else { } else {
data = window.minder.exportJson(); const fullJson = window.minder.exportJson();
data = cloneDeep(fullJson);
importJson.value = fullJson;
} }
emit('save', data, () => { emit('save', data, () => {
importJson.value.root.children = mapTree<MinderJsonNode>(importJson.value.root.children || [], (node) => ({
...node,
data: {
...node.data,
isNew: false,
changed: false,
},
}));
if (innerImportJson.value.treePath?.length > 1) {
switchNode(innerImportJson.value.root.data);
} else {
innerImportJson.value = importJson.value;
window.minder.importJson(importJson.value);
}
minderStore.setMinderUnsaved(false); minderStore.setMinderUnsaved(false);
menuVisible.value = false; floatMenuVisible.value = false;
}); });
} }
watch( watch(
() => minderStore.event.eventId, () => minderStore.event.eventId,
() => { () => {
if (minderStore.event.name === MinderEventName.HOTBOX && minderStore.event.nodePosition) {
const nodeDomWidth = minderStore.event.nodeDom?.getBoundingClientRect().width || 0;
menuPopupOffset.value = [
minderStore.event.nodePosition.x + nodeDomWidth / 2,
minderStore.event.nodePosition.y - nodeDomWidth / 4,
];
menuVisible.value = true;
}
if (minderStore.event.name === MinderEventName.ENTER_NODE && minderStore.event.nodes) { if (minderStore.event.name === MinderEventName.ENTER_NODE && minderStore.event.nodes) {
switchNode(minderStore.event.nodes[0]); switchNode(minderStore.event.nodes[0]);
} }

View File

@ -184,11 +184,19 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useMinderStore from '@/store/modules/components/minder-editor/index'; import useMinderStore from '@/store/modules/components/minder-editor/index';
import { MinderNodePosition } from '@/store/modules/components/minder-editor/types'; import { MinderNodePosition } from '@/store/modules/components/minder-editor/types';
import { getGenerateId, sleep, traverseTree } from '@/utils'; import { getGenerateId, sleep } from '@/utils';
import { MinderEventName } from '@/enums/minderEnum'; import { MinderEventName } from '@/enums/minderEnum';
import { floatMenuProps, insertProps, mainEditorProps, MinderJsonNode, priorityProps, tagProps } from '../props'; import {
floatMenuProps,
insertProps,
mainEditorProps,
MinderJsonNode,
MinderJsonNodeData,
priorityProps,
tagProps,
} from '../props';
import { isDisableNode, isNodeInMinderView, setPriorityView } from '../script/tool/utils'; import { isDisableNode, isNodeInMinderView, setPriorityView } from '../script/tool/utils';
const props = defineProps({ const props = defineProps({
@ -208,7 +216,9 @@
const currentNodeTags = ref<string[]>([]); const currentNodeTags = ref<string[]>([]);
const tags = ref<string[]>([]); const tags = ref<string[]>([]);
const menuVisible = ref(false); const menuVisible = defineModel<boolean>('visible', {
default: false,
});
const menuPopupOffset = ref<TriggerPopupTranslate>([0, 0]); const menuPopupOffset = ref<TriggerPopupTranslate>([0, 0]);
watch( watch(
@ -310,6 +320,9 @@
} }
window.minder.execCommand('resource', origin); window.minder.execCommand('resource', origin);
minderStore.dispatchEvent(MinderEventName.SET_TAG, undefined, undefined, undefined, selectedNodes); minderStore.dispatchEvent(MinderEventName.SET_TAG, undefined, undefined, undefined, selectedNodes);
if (props.afterTagEdit) {
props.afterTagEdit(selectedNodes, value);
}
} }
} }
@ -362,21 +375,15 @@
case 'paste': case 'paste':
minderStore.dispatchEvent(MinderEventName.PASTE_NODE, undefined, undefined, undefined, selectedNodes); minderStore.dispatchEvent(MinderEventName.PASTE_NODE, undefined, undefined, undefined, selectedNodes);
window.minder.execCommand('Paste'); window.minder.execCommand('Paste');
const pastedNode: MinderJsonNode = window.minder.getSelectedNode(); let pastedNodes: MinderJsonNode[] = window.minder.getSelectedNodes();
if (pastedNode) { if (pastedNodes.length > 0) {
pastedNode.data = { pastedNodes = pastedNodes.map((e) => {
...pastedNode.data, e.data = {
text: pastedNode.data?.text || '', ...(e.data as MinderJsonNodeData),
isNew: true,
id: getGenerateId(),
};
traverseTree(pastedNode.children || [], (node) => {
node.data = {
...node.data,
text: node.data?.text || '',
isNew: true, isNew: true,
id: getGenerateId(), id: getGenerateId(),
}; };
return e;
}); });
} }
break; break;

View File

@ -48,6 +48,7 @@
tagProps, tagProps,
viewMenuProps, viewMenuProps,
} from './props'; } from './props';
import { isNodeInMinderView } from './script/tool/utils';
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'moldChange', data: number): void; (e: 'moldChange', data: number): void;
@ -97,9 +98,13 @@
(val) => { (val) => {
const node: MinderJsonNode = window.minder.getSelectedNode(); const node: MinderJsonNode = window.minder.getSelectedNode();
if (val && node) { if (val && node) {
const nodePosition = node?.getRenderBox();
//
if (nodePosition && !isNodeInMinderView(undefined, nodePosition, nodePosition.width / 2)) {
setTimeout(() => { setTimeout(() => {
window.minder.execCommand('camera', node, 100); window.minder.execCommand('camera', node, 100);
}, 100); }, 300); // 300ms
}
} }
} }
); );

View File

@ -238,7 +238,7 @@ export default defineComponent({
const computedCurrent = computed(() => props.current ?? _current.value); const computedCurrent = computed(() => props.current ?? _current.value);
const computedPageSize = computed(() => props.pageSize ?? _pageSize.value); const computedPageSize = computed(() => props.pageSize ?? _pageSize.value);
const pages = computed(() => Math.ceil(props.total / computedPageSize.value)); const pages = computed(() => Math.ceil(props.total / computedPageSize.value) || 1); // 页码最小是 1
const handleClick = (page: number) => { const handleClick = (page: number) => {
// when pageJumper blur and input.value is undefined, page is illegal // when pageJumper blur and input.value is undefined, page is illegal
@ -378,14 +378,6 @@ export default defineComponent({
} }
}); });
watch(pages, (curPages, prePages) => {
if (props.autoAdjust && curPages !== prePages && computedCurrent.value > 1 && computedCurrent.value > curPages) {
_current.value = curPages;
emit('update:current', curPages);
emit('change', curPages);
}
});
const cls = computed(() => [ const cls = computed(() => [
prefixCls, prefixCls,
`${prefixCls}-size-${mergedSize.value}`, `${prefixCls}-size-${mergedSize.value}`,

View File

@ -4,6 +4,7 @@
v-model:model-value="innerParams.bodyType" v-model:model-value="innerParams.bodyType"
type="button" type="button"
size="small" size="small"
:disabled="props.disabledBodyType"
@change="(val) => changeBodyFormat(val as RequestBodyFormat)" @change="(val) => changeBodyFormat(val as RequestBodyFormat)"
> >
<a-radio v-for="item of RequestBodyFormat" :key="item" :value="item"> <a-radio v-for="item of RequestBodyFormat" :key="item" :value="item">
@ -109,7 +110,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core';
import { TableColumnData } from '@arco-design/web-vue'; import { TableColumnData } from '@arco-design/web-vue';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue'; import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
@ -132,7 +132,7 @@
import { defaultBodyParamsItem } from '@/views/api-test/components/config'; import { defaultBodyParamsItem } from '@/views/api-test/components/config';
const props = defineProps<{ const props = defineProps<{
params: ExecuteBody; disabledBodyType?: boolean; // body
disabledParamValue?: boolean; // disabledParamValue?: boolean; //
disabledExceptParam?: boolean; // disabledExceptParam?: boolean; //
uploadTempFileApi?: (file: File) => Promise<any>; // uploadTempFileApi?: (file: File) => Promise<any>; //
@ -148,15 +148,24 @@
const appStore = useAppStore(); const appStore = useAppStore();
const { t } = useI18n(); const { t } = useI18n();
const innerParams = useVModel(props, 'params', emit); const innerParams = defineModel<ExecuteBody>('params', {
required: true,
});
const batchAddKeyValVisible = ref(false); const batchAddKeyValVisible = ref(false);
const fileList = ref<MsFileItem[]>([]); const fileList = ref<MsFileItem[]>([]);
onBeforeMount(() => { watch(
if (innerParams.value.binaryBody && innerParams.value.binaryBody.file) { () => innerParams.value.binaryBody?.file,
() => {
if (innerParams.value.binaryBody?.file) {
fileList.value = [innerParams.value.binaryBody.file as unknown as MsFileItem]; fileList.value = [innerParams.value.binaryBody.file as unknown as MsFileItem];
} }
}); },
{
immediate: true,
deep: true,
}
);
async function handleFileChange(files: MsFileItem[], file?: MsFileItem) { async function handleFileChange(files: MsFileItem[], file?: MsFileItem) {
try { try {

View File

@ -780,7 +780,7 @@
/** /**
* 设置插件表单数据 * 设置插件表单数据
*/ */
function setPluginFormData() { async function setPluginFormData() {
const tempForm = temporaryPluginFormMap[requestVModel.value.id]; const tempForm = temporaryPluginFormMap[requestVModel.value.id];
if (tempForm || !requestVModel.value.isNew || requestVModel.value.isCopy) { if (tempForm || !requestVModel.value.isNew || requestVModel.value.isCopy) {
// //
@ -809,6 +809,7 @@
isInitPluginForm.value = true; isInitPluginForm.value = true;
}); });
} }
await nextTick();
} }
const pluginError = ref(false); const pluginError = ref(false);
@ -823,7 +824,7 @@
pluginError.value = false; pluginError.value = false;
isInitPluginForm.value = false; isInitPluginForm.value = false;
if (pluginScriptMap.value[requestVModel.value.protocol] !== undefined) { if (pluginScriptMap.value[requestVModel.value.protocol] !== undefined) {
setPluginFormData(); await setPluginFormData();
// //
return; return;
} }
@ -831,7 +832,7 @@
pluginLoading.value = true; pluginLoading.value = true;
const res = await getPluginScript(pluginId); const res = await getPluginScript(pluginId);
pluginScriptMap.value[requestVModel.value.protocol] = res; pluginScriptMap.value[requestVModel.value.protocol] = res;
setPluginFormData(); await setPluginFormData();
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
@ -1191,7 +1192,13 @@
} }
if (props.request.isExecute && !requestVModel.value.executeLoading) { if (props.request.isExecute && !requestVModel.value.executeLoading) {
// //
if (requestVModel.value.protocol !== 'HTTP') {
setTimeout(() => {
execute(isPriorityLocalExec.value ? 'localExec' : 'serverExec'); execute(isPriorityLocalExec.value ? 'localExec' : 'serverExec');
}, 100);
} else {
execute(isPriorityLocalExec.value ? 'localExec' : 'serverExec');
}
} else if (temporaryResponseMap[props.request.reportId]) { } else if (temporaryResponseMap[props.request.reportId]) {
// //
requestVModel.value.response = temporaryResponseMap[props.request.reportId]; requestVModel.value.response = temporaryResponseMap[props.request.reportId];

View File

@ -239,6 +239,7 @@
v-model:params="requestVModel.body" v-model:params="requestVModel.body"
:disabled-param-value="!isEditableApi && !isEditableParamValue" :disabled-param-value="!isEditableApi && !isEditableParamValue"
:disabled-except-param="!isEditableApi" :disabled-except-param="!isEditableApi"
:disabled-body-type="!isEditableApi"
:upload-temp-file-api="uploadTempFile" :upload-temp-file-api="uploadTempFile"
:file-save-as-source-id="props.step?.id" :file-save-as-source-id="props.step?.id"
:file-save-as-api="stepTransferFile" :file-save-as-api="stepTransferFile"

View File

@ -32,7 +32,7 @@ export default {
'Scenario level: Load CSV before executing the scenario. Data can be read from CSV in any step of the current scenario.', 'Scenario level: Load CSV before executing the scenario. Data can be read from CSV in any step of the current scenario.',
'apiScenario.params.csvScopedTip2': 'apiScenario.params.csvScopedTip2':
'Step level: The CSV needs to be added to the scenario step. The CSV is loaded when executing this step, and the scope is the request within the step.', 'Step level: The CSV needs to be added to the scenario step. The CSV is loaded when executing this step, and the scope is the request within the step.',
'apiScenario.params.searchPlaceholder': 'Search by name or tag', 'apiScenario.params.searchPlaceholder': 'Search by name/tag',
'apiScenario.params.priority': 'apiScenario.params.priority':
'Variable Priority: Temporary Parameters > Scenario Parameters > Environment Parameters > Global Parameters; Note: Avoid using variables with the same name. In case of same name variables, scenario-level CSV has the highest priority.', 'Variable Priority: Temporary Parameters > Scenario Parameters > Environment Parameters > Global Parameters; Note: Avoid using variables with the same name. In case of same name variables, scenario-level CSV has the highest priority.',
'apiScenario.params.name': 'Variable Name', 'apiScenario.params.name': 'Variable Name',

View File

@ -99,7 +99,7 @@
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import useFeatureCaseStore from '@/store/modules/case/featureCase'; import useFeatureCaseStore from '@/store/modules/case/featureCase';
import { mapTree } from '@/utils'; import { mapTree, traverseTree } from '@/utils';
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
import type { CreateOrUpdateModule, UpdateModule } from '@/models/caseManagement/featureCase'; import type { CreateOrUpdateModule, UpdateModule } from '@/models/caseManagement/featureCase';
@ -386,12 +386,9 @@
watch( watch(
() => props.modulesCount, () => props.modulesCount,
(obj) => { (obj) => {
caseTree.value = mapTree<ModuleTreeNode>(caseTree.value, (node) => { traverseTree(caseTree.value, (node) => {
return { node.count = obj?.[node.id] || 0;
...node, node.hideMoreAction = node.id === 'root' || props.isModal;
hideMoreAction: node.id === 'root' || props.isModal,
count: obj?.[node.id] || 0,
};
}); });
} }
); );

View File

@ -9,7 +9,7 @@ export default {
'project.environmental.requestHeader': 'Request Header', 'project.environmental.requestHeader': 'Request Header',
'project.environmental.allParams': 'All Parameters', 'project.environmental.allParams': 'All Parameters',
'project.environmental.mustContain': 'Must Contain', 'project.environmental.mustContain': 'Must Contain',
'project.environmental.searchParamsHolder': 'Search by name or tag', 'project.environmental.searchParamsHolder': 'Search by name/tag',
'project.environmental.paramName': 'Parameter Name', 'project.environmental.paramName': 'Parameter Name',
'project.environmental.globalVariable': 'Parameter', 'project.environmental.globalVariable': 'Parameter',
'project.environmental.paramType': 'Type', 'project.environmental.paramType': 'Type',

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<div class="mb-4 grid grid-cols-4 gap-2"> <div class="mb-[16px] grid grid-cols-4 gap-2">
<div class="col-span-2"> <div class="col-span-2">
<a-button v-permission="['PROJECT_USER:READ+ADD']" class="mr-3" type="primary" @click="addMember"> <a-button v-permission="['PROJECT_USER:READ+ADD']" class="mr-3" type="primary" @click="addMember">
{{ t('project.member.addMember') }} {{ t('project.member.addMember') }}

View File

@ -1,7 +1,7 @@
export default { export default {
'project.member.addMember': 'Add Member', 'project.member.addMember': 'Add Member',
'project.member.updateMember': 'Update Member', 'project.member.updateMember': 'Update Member',
'project.member.searchMember': 'Search by name or email address or phone', 'project.member.searchMember': 'Search by name/email/phone',
'project.member.remove': 'Remove', 'project.member.remove': 'Remove',
'project.member.edit': 'Edit', 'project.member.edit': 'Edit',
'project.member.add': 'Add', 'project.member.add': 'Add',

View File

@ -1,7 +1,7 @@
export default { export default {
'organization.member.addMember': 'Add Member', 'organization.member.addMember': 'Add Member',
'organization.member.updateMember': 'Update Member{name}', 'organization.member.updateMember': 'Update Member{name}',
'organization.member.searchMember': 'Search by name or email address or phone', 'organization.member.searchMember': 'Search by name/email/phone',
'organization.member.remove': 'Remove', 'organization.member.remove': 'Remove',
'organization.member.edit': 'Edit', 'organization.member.edit': 'Edit',
'organization.member.batchActionAddProject': 'Add to project', 'organization.member.batchActionAddProject': 'Add to project',

View File

@ -76,7 +76,7 @@ export default {
'system.project.revokeDeleteToolTip': 'The project will be deleted automatically after {count} days', 'system.project.revokeDeleteToolTip': 'The project will be deleted automatically after {count} days',
'system.project.removeTip': "Remove it, and you'll lose access to the project.", 'system.project.removeTip': "Remove it, and you'll lose access to the project.",
'system.organization.projectIsDisabled': 'The project has ended and can be opened in the project list.', 'system.organization.projectIsDisabled': 'The project has ended and can be opened in the project list.',
'system.project.searchPlaceholder': 'Search by name or id', 'system.project.searchPlaceholder': 'Search by name/id',
'system.project.afterModule': 'system.project.afterModule':
'After the module is canceled, users will be unable to access the specified module, and existing data will remain intact', 'After the module is canceled, users will be unable to access the specified module, and existing data will remain intact',
'system.project.projectAdminIsNotNull': 'Project administrator cannot be empty', 'system.project.projectAdminIsNotNull': 'Project administrator cannot be empty',

View File

@ -2,7 +2,7 @@ export default {
'system.user.createUser': 'Create User', 'system.user.createUser': 'Create User',
'system.user.emailInvite': 'Email Invite', 'system.user.emailInvite': 'Email Invite',
'system.user.importUser': 'Import User', 'system.user.importUser': 'Import User',
'system.user.searchUser': 'Search by name or email or phone', 'system.user.searchUser': 'Search by name/email/phone',
'system.user.editUser': 'Edit', 'system.user.editUser': 'Edit',
'system.user.resetPassword': 'Reset PSW', 'system.user.resetPassword': 'Reset PSW',
'system.user.disable': 'Disable', 'system.user.disable': 'Disable',