fix(全局): 部分 bug 修复
This commit is contained in:
parent
fbd0a27486
commit
eff826b97c
|
@ -295,26 +295,32 @@
|
|||
});
|
||||
const buttonDropDownVisible = ref(false);
|
||||
|
||||
onBeforeMount(() => {
|
||||
// 回显文件
|
||||
const defaultFiles = fileList.value.filter((item) => item) || [];
|
||||
if (defaultFiles.length > 0) {
|
||||
if (props.multiple) {
|
||||
inputFiles.value = defaultFiles.map((item) => ({
|
||||
...item,
|
||||
// 这里取自定义的字段名,因为存在查看的场景时不会与刚选择的文件信息一样
|
||||
value: item?.[props.fields.id] || item.uid || '', // 取uid是因为有可能是本地上传然后组件卸载然后重新挂载,这时候取自定义 id 会是空的
|
||||
label: item?.[props.fields.name] || item?.name || '',
|
||||
}));
|
||||
} else {
|
||||
inputFileName.value = defaultFiles[0]?.[props.fields.name] || defaultFiles[0]?.name || '';
|
||||
watch(
|
||||
() => fileList.value,
|
||||
() => {
|
||||
// 回显文件
|
||||
const defaultFiles = fileList.value.filter((item) => item) || [];
|
||||
if (defaultFiles.length > 0) {
|
||||
if (props.multiple) {
|
||||
inputFiles.value = defaultFiles.map((item) => ({
|
||||
...item,
|
||||
// 这里取自定义的字段名,因为存在查看的场景时不会与刚选择的文件信息一样
|
||||
value: item?.[props.fields.id] || item.uid || '', // 取uid是因为有可能是本地上传然后组件卸载然后重新挂载,这时候取自定义 id 会是空的
|
||||
label: item?.[props.fields.name] || item?.name || '',
|
||||
}));
|
||||
} else {
|
||||
inputFileName.value = defaultFiles[0]?.[props.fields.name] || defaultFiles[0]?.name || '';
|
||||
}
|
||||
getListFunParams.value.combine.hiddenIds = defaultFiles
|
||||
.filter((item) => !item?.local)
|
||||
.map((item) => item?.[props.fields.id] || '')
|
||||
.filter((item) => item);
|
||||
}
|
||||
getListFunParams.value.combine.hiddenIds = defaultFiles
|
||||
.filter((item) => !item?.local)
|
||||
.map((item) => item?.[props.fields.id] || '')
|
||||
.filter((item) => item);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
function handleChange(_fileList: MsFileItem[], fileItem: MsFileItem) {
|
||||
if (props.multiple) {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
}
|
||||
"
|
||||
>
|
||||
<span class="mx-[2px]"></span>
|
||||
<span class="mx-[2px]"><slot></slot></span>
|
||||
<template #content>
|
||||
<div class="flex flex-col gap-[16px] text-[14px]">
|
||||
<div class="font-semibold text-[var(--color-text-1)]">
|
||||
|
@ -68,7 +68,6 @@
|
|||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
visible: boolean;
|
||||
savingFile?: MsFileItem;
|
||||
fileSaveAsSourceId: string | number;
|
||||
sourceIdKey?: string; // 资源id对应key
|
||||
|
@ -89,7 +88,7 @@
|
|||
const { t } = useI18n();
|
||||
|
||||
const saveFilePopoverVisible = defineModel<boolean>('visible', {
|
||||
required: true,
|
||||
default: false,
|
||||
});
|
||||
const saveFileForm = ref({
|
||||
name: '',
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { computed, defineComponent, h, ref } from 'vue';
|
||||
import { RouteRecordRaw, useRoute, useRouter } from 'vue-router';
|
||||
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 MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
|
@ -407,7 +407,7 @@
|
|||
// 渲染菜单项
|
||||
const renderMenuItem = (element: RouteRecordRaw | null, icon: (() => any) | null) =>
|
||||
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">
|
||||
{collapsed.value ? (
|
||||
<div class="text-center text-[12px] leading-[16px]">
|
||||
|
@ -436,7 +436,7 @@
|
|||
</div>
|
||||
</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 ? (
|
||||
<div class="text-center text-[12px] leading-[16px]">
|
||||
{t(element?.meta?.collapsedLocale || element?.meta?.locale || '')}
|
||||
|
|
|
@ -37,23 +37,19 @@
|
|||
>
|
||||
{{ t('ms.upload.preview') }}
|
||||
</MsButton>
|
||||
<MsButton type="button" status="primary" class="!mr-[4px]" @click="transferFile(item)">
|
||||
{{ t('caseManagement.featureCase.storage') }}
|
||||
</MsButton>
|
||||
<SaveAsFilePopover
|
||||
v-model:visible="transferVisible"
|
||||
v-if="item.status !== 'init'"
|
||||
:saving-file="activeTransferFileParams"
|
||||
:file-save-as-source-id="activeCase.id || ''"
|
||||
:file-save-as-api="transferFileRequest"
|
||||
:file-module-options-api="getTransferFileTree"
|
||||
source-id-key="caseId"
|
||||
/>
|
||||
<MsButton
|
||||
v-if="item.status !== 'init'"
|
||||
type="button"
|
||||
status="primary"
|
||||
class="!mr-[4px]"
|
||||
@click="transferFile(item)"
|
||||
>
|
||||
{{ t('caseManagement.featureCase.storage') }}
|
||||
</MsButton>
|
||||
<span :id="item.uid"></span>
|
||||
</SaveAsFilePopover>
|
||||
<MsButton
|
||||
v-if="item.status !== 'init'"
|
||||
type="button"
|
||||
|
@ -198,7 +194,7 @@
|
|||
return item;
|
||||
});
|
||||
}
|
||||
Message.success(t('ms.upload.uploadSuccess'));
|
||||
Message.success(t('common.linkSuccess'));
|
||||
emit('uploadSuccess');
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -244,14 +240,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
const transferVisible = ref<boolean>(false);
|
||||
|
||||
const activeTransferFileParams = ref<MsFileItem>();
|
||||
|
||||
// 转存
|
||||
function transferFile(item: MsFileItem) {
|
||||
activeTransferFileParams.value = { ...item };
|
||||
transferVisible.value = true;
|
||||
document.getElementById(item.uid)?.click();
|
||||
}
|
||||
|
||||
// 删除本地文件
|
||||
|
|
|
@ -87,7 +87,7 @@
|
|||
import useAppStore from '@/store/modules/app';
|
||||
import useMinderStore from '@/store/modules/components/minder-editor/index';
|
||||
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 {
|
||||
|
@ -156,69 +156,65 @@
|
|||
/**
|
||||
* 初始化用例模块树
|
||||
*/
|
||||
async function initCaseTree(notRemote = false) {
|
||||
async function initCaseTree() {
|
||||
try {
|
||||
loading.value = true;
|
||||
if (!notRemote) {
|
||||
const res = await getCaseMinderTree({
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleId: '', // 始终加载全部,然后再进入对应的模块节点
|
||||
});
|
||||
caseTree.value = mapTree<MinderJsonNode>(res, (e) => ({
|
||||
...e,
|
||||
data: {
|
||||
...e.data,
|
||||
id: e.id || e.data?.id || '',
|
||||
text: e.name || e.data?.text || '',
|
||||
resource: props.modulesCount[e.id] !== undefined ? [moduleTag] : e.data?.resource,
|
||||
expandState: e.level === 1 ? 'expand' : 'collapse',
|
||||
count: props.modulesCount[e.id],
|
||||
isNew: false,
|
||||
changed: false,
|
||||
},
|
||||
children:
|
||||
props.modulesCount[e.id] > 0 && !e.children?.length
|
||||
? [
|
||||
{
|
||||
data: {
|
||||
id: 'fakeNode',
|
||||
text: 'fakeNode',
|
||||
resource: ['fakeNode'],
|
||||
isNew: false,
|
||||
changed: false,
|
||||
},
|
||||
const res = await getCaseMinderTree({
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleId: '', // 始终加载全部,然后再进入对应的模块节点
|
||||
});
|
||||
caseTree.value = mapTree<MinderJsonNode>(res, (e) => ({
|
||||
...e,
|
||||
data: {
|
||||
...e.data,
|
||||
id: e.id || e.data?.id || '',
|
||||
text: e.name || e.data?.text || '',
|
||||
resource: props.modulesCount[e.id] !== undefined ? [moduleTag] : e.data?.resource,
|
||||
expandState: e.level === 0 ? 'expand' : 'collapse',
|
||||
count: props.modulesCount[e.id],
|
||||
isNew: false,
|
||||
changed: false,
|
||||
},
|
||||
children:
|
||||
props.modulesCount[e.id] > 0 && !e.children?.length
|
||||
? [
|
||||
{
|
||||
data: {
|
||||
id: 'fakeNode',
|
||||
text: 'fakeNode',
|
||||
resource: ['fakeNode'],
|
||||
isNew: false,
|
||||
changed: false,
|
||||
},
|
||||
]
|
||||
: e.children,
|
||||
}));
|
||||
importJson.value.root = {
|
||||
children: caseTree.value,
|
||||
data: {
|
||||
id: 'NONE',
|
||||
text: t('ms.minders.allModule'),
|
||||
resource: [moduleTag],
|
||||
disabled: true,
|
||||
},
|
||||
};
|
||||
importJson.value.treePath = [];
|
||||
window.minder.importJson(importJson.value);
|
||||
}
|
||||
if (notRemote) {
|
||||
if (props.moduleId !== 'all') {
|
||||
// 携带具体的模块 ID 加载时,进入该模块内
|
||||
nextTick(() => {
|
||||
minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, undefined, [
|
||||
window.minder.getNodeById(props.moduleId),
|
||||
]);
|
||||
});
|
||||
} else {
|
||||
// 携带具体的模块 ID 加载时,进入该模块内
|
||||
nextTick(() => {
|
||||
minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, undefined, [
|
||||
importJson.value.root,
|
||||
]);
|
||||
});
|
||||
}
|
||||
},
|
||||
]
|
||||
: e.children,
|
||||
}));
|
||||
importJson.value.root = {
|
||||
children: caseTree.value,
|
||||
data: {
|
||||
id: 'NONE',
|
||||
text: t('ms.minders.allModule'),
|
||||
resource: [moduleTag],
|
||||
disabled: true,
|
||||
},
|
||||
};
|
||||
importJson.value.treePath = [];
|
||||
window.minder.importJson(importJson.value);
|
||||
if (props.moduleId !== 'all') {
|
||||
// 携带具体的模块 ID 加载时,进入该模块内
|
||||
nextTick(() => {
|
||||
minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, undefined, [
|
||||
findNodeByKey(importJson.value.root.children || [], props.moduleId, 'id', 'data') as MinderJsonNode,
|
||||
]);
|
||||
});
|
||||
} else {
|
||||
// 刷新时不需要重新请求数据,进入根节点
|
||||
nextTick(() => {
|
||||
minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, undefined, [
|
||||
importJson.value.root,
|
||||
]);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -291,7 +287,10 @@
|
|||
try {
|
||||
baseInfoLoading.value = true;
|
||||
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) || [];
|
||||
if (fileIds.length) {
|
||||
checkUpdateFileIds.value = await checkFileIsUpdateRequest(fileIds);
|
||||
|
@ -389,7 +388,7 @@
|
|||
projectId: appStore.currentProjectId,
|
||||
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) {
|
||||
window.minder.removeNode(fakeNode);
|
||||
}
|
||||
|
@ -464,6 +463,10 @@
|
|||
extraVisible.value = false;
|
||||
showDetailMenu.value = false;
|
||||
resetExtractInfo();
|
||||
const fakeNode = node.children?.find((e) => e.data?.id === 'fakeNode'); // 移除占位的虚拟节点
|
||||
if (fakeNode) {
|
||||
window.minder.removeNode(fakeNode);
|
||||
}
|
||||
}
|
||||
setPriorityView(true, 'P');
|
||||
}
|
||||
|
@ -483,20 +486,16 @@
|
|||
node.data.isNew = true;
|
||||
window.minder.execCommand('priority');
|
||||
if (index === nodes.length - 1) {
|
||||
nextTick(() => {
|
||||
handleNodeSelect(node);
|
||||
});
|
||||
window.minder.toggleSelect(node);
|
||||
}
|
||||
} else if (node.data?.resource?.includes(caseTag)) {
|
||||
} else if (tag === caseTag && node.data) {
|
||||
// 排除是从模块节点切换到用例节点的数据
|
||||
tempMinderParams.value.updateModuleList = tempMinderParams.value.updateModuleList.filter(
|
||||
(e) => e.id !== node.data?.id
|
||||
);
|
||||
node.data.isNew = true;
|
||||
if (index === nodes.length - 1) {
|
||||
nextTick(() => {
|
||||
handleNodeSelect(node);
|
||||
});
|
||||
window.minder.toggleSelect(node);
|
||||
}
|
||||
} else if (node.data?.resource?.some((e) => caseOffspringTags.includes(e))) {
|
||||
// 用例子孙节点更新,标记用例节点变化
|
||||
|
@ -522,7 +521,13 @@
|
|||
case MinderEventName.CUT_NODE:
|
||||
// TODO:循环优化
|
||||
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({
|
||||
id: node.data?.id || '',
|
||||
|
@ -619,7 +624,7 @@
|
|||
versionId: '',
|
||||
updateCaseList: [],
|
||||
updateModuleList: [],
|
||||
deleteResourceList: [],
|
||||
deleteResourceList: tempMinderParams.value.deleteResourceList, // 删除的资源不清空,避免请求错误导致数据丢失
|
||||
additionalNodeList: [],
|
||||
};
|
||||
}
|
||||
|
@ -702,7 +707,7 @@
|
|||
watch(
|
||||
() => props.moduleId,
|
||||
() => {
|
||||
initCaseTree(true);
|
||||
initCaseTree();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
|
|
|
@ -615,7 +615,10 @@ export default function useMinderBaseApi({ hasEditPermission }: { hasEditPermiss
|
|||
}
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,10 @@
|
|||
@save="handleMinderSave"
|
||||
>
|
||||
<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">
|
||||
<MsIcon type="icon-icon_add_outlined" class="text-[var(--color-text-4)]" />
|
||||
</MsButton>
|
||||
|
@ -86,7 +89,11 @@
|
|||
</a-tooltip>
|
||||
</div>
|
||||
<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>
|
||||
<div class="flex items-center">
|
||||
<div>{{ t('testPlan.planForm.pickCases') }}</div>
|
||||
|
@ -116,7 +123,6 @@
|
|||
</div>
|
||||
<a-divider margin="8px" direction="vertical" />
|
||||
<MsButton
|
||||
v-permission="['CASE_REVIEW:READ+RELEVANCE']"
|
||||
type="text"
|
||||
class="font-medium"
|
||||
:disabled="!hasEditPermission"
|
||||
|
@ -127,6 +133,15 @@
|
|||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<a-form-item :label="t('system.project.resourcePool')">
|
||||
<a-select
|
||||
|
@ -211,15 +226,6 @@
|
|||
</a-form-item>
|
||||
</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>
|
||||
<div v-if="hasEditPermission" class="flex items-center gap-[12px] bg-white pb-[16px]">
|
||||
<a-button
|
||||
|
@ -538,30 +544,6 @@
|
|||
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: '',
|
||||
projectId: '',
|
||||
};
|
||||
const node: PlanMinderNode = window.minder.getNodeById(activePlanSet.value?.data.id);
|
||||
if (node?.data) {
|
||||
node.data.associateDTOS = [];
|
||||
}
|
||||
}
|
||||
|
||||
function associateCase() {
|
||||
|
@ -658,6 +644,7 @@
|
|||
() => {
|
||||
if (!switchingConfigFormData.value && configForm.value) {
|
||||
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 拖动节点
|
||||
|
@ -828,7 +840,7 @@
|
|||
editList: [],
|
||||
deletedIds: [],
|
||||
};
|
||||
selectedAssociateCasesParams.value.selectIds = [];
|
||||
clearSelectedCases();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ export default {
|
|||
'ms.personal.baseInfo': 'Basic Information',
|
||||
'ms.personal.setPsw': 'Password settings',
|
||||
'ms.personal.setting': 'Personal settings',
|
||||
'ms.personal.apiKey': 'APIKEY',
|
||||
'ms.personal.apiKey': 'API KEY',
|
||||
'ms.personal.tripartite': 'Tripartite account',
|
||||
'ms.personal.changeAvatar': 'Change avatar',
|
||||
'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.updatePswSuccess':
|
||||
'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.expired': 'Expired',
|
||||
'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.createTime': 'Created time',
|
||||
'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.closeTip':
|
||||
'After closing, the test tasks executed using the Access Key will fail. Please operate with caution!',
|
||||
|
|
|
@ -4,7 +4,7 @@ export default {
|
|||
'ms.personal.baseInfo': '基本信息',
|
||||
'ms.personal.setPsw': '密码设置',
|
||||
'ms.personal.setting': '个人设置',
|
||||
'ms.personal.apiKey': 'APIKEY',
|
||||
'ms.personal.apiKey': 'API KEY',
|
||||
'ms.personal.tripartite': '三方平台账号',
|
||||
'ms.personal.changeAvatar': '更换头像',
|
||||
'ms.personal.name': '姓名',
|
||||
|
@ -26,7 +26,7 @@ export default {
|
|||
'ms.personal.newPsw': '新密码',
|
||||
'ms.personal.changePswTip': '修改密码后,需要使用新的密码登录系统',
|
||||
'ms.personal.updatePswSuccess': '密码修改成功,将在 {count} 秒后自动退出,请使用新密码登录',
|
||||
'ms.personal.apiKeyTip': '可通过 APIKEY 访问 MeterSphere API',
|
||||
'ms.personal.apiKeyTip': '可通过 API KEY 访问 MeterSphere API',
|
||||
'ms.personal.expireTime': '过期时间',
|
||||
'ms.personal.expired': '已到期',
|
||||
'ms.personal.expiredTip': '可在【设置】内更改到期时间',
|
||||
|
@ -34,7 +34,7 @@ export default {
|
|||
'ms.personal.setValidTime': '设置有效时间',
|
||||
'ms.personal.createTime': '创建时间',
|
||||
'ms.personal.copySuccess': '复制成功',
|
||||
'ms.personal.maxTip': '最多可添加 5 个APIKEY',
|
||||
'ms.personal.maxTip': '最多可添加 5 个API KEY',
|
||||
'ms.personal.confirmClose': '确认关闭吗?',
|
||||
'ms.personal.closeTip': '关闭后,将导致使用该 Access Key 执行的测试任务执行失败,请谨慎操作!',
|
||||
'ms.personal.closeSuccess': '关闭成功',
|
||||
|
|
|
@ -26,12 +26,8 @@
|
|||
|
||||
import MsSelect from '@/components/business/ms-select/index';
|
||||
|
||||
import { useUserStore } from '@/store';
|
||||
|
||||
import initOptionsFunc, { UserRequestTypeEnum } from './utils';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
defineOptions({ name: 'MsUserSelector' });
|
||||
|
||||
export interface MsUserSelectorOption {
|
||||
|
@ -99,7 +95,7 @@
|
|||
};
|
||||
const optionLabelRender = (option: SelectOptionData) => {
|
||||
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>`;
|
||||
};
|
||||
|
|
|
@ -12,7 +12,12 @@
|
|||
</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
</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>
|
||||
<slot name="extractMenu"></slot>
|
||||
</template>
|
||||
|
@ -21,13 +26,15 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" name="minderContainer" setup>
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import nodeFloatMenu from '../menu/nodeFloatMenu.vue';
|
||||
import minderHeader from './header.vue';
|
||||
import Navigator from './navigator.vue';
|
||||
|
||||
import useLeaveUnSaveTip from '@/hooks/useLeaveUnSaveTip';
|
||||
import useMinderStore from '@/store/modules/components/minder-editor';
|
||||
import { findNodePathByKey, replaceNodeInTree } from '@/utils';
|
||||
import { findNodePathByKey, mapTree, replaceNodeInTree } from '@/utils';
|
||||
|
||||
import { MinderEventName } from '@/enums/minderEnum';
|
||||
|
||||
|
@ -139,9 +146,6 @@
|
|||
});
|
||||
});
|
||||
|
||||
const menuVisible = ref(false);
|
||||
const menuPopupOffset = ref([0, 0]);
|
||||
|
||||
function getCurrentTreePath() {
|
||||
if (innerImportJson.value.root.id === 'NONE' || innerImportJson.value.treePath?.length <= 1) {
|
||||
return [];
|
||||
|
@ -176,12 +180,15 @@
|
|||
const root: MinderJsonNode = window.minder.getRoot();
|
||||
window.minder.toggleSelect(root); // 先取消选中
|
||||
window.minder.select(root); // 再选中,才能触发选中变化事件
|
||||
window.minder.execCommand('ExpandToLevel', 1);
|
||||
currentTreePath.value = getCurrentTreePath();
|
||||
setTimeout(() => {
|
||||
window.minder.execCommand('camera', root);
|
||||
}, 100); // TODO:暂未知渲染时机,临时延迟解决
|
||||
}
|
||||
|
||||
const floatMenuVisible = ref(false);
|
||||
|
||||
function save() {
|
||||
let data = importJson.value;
|
||||
if (innerImportJson.value.treePath?.length > 1) {
|
||||
|
@ -193,25 +200,33 @@
|
|||
'id'
|
||||
);
|
||||
} else {
|
||||
data = window.minder.exportJson();
|
||||
const fullJson = window.minder.exportJson();
|
||||
data = cloneDeep(fullJson);
|
||||
importJson.value = fullJson;
|
||||
}
|
||||
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);
|
||||
menuVisible.value = false;
|
||||
floatMenuVisible.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => 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) {
|
||||
switchNode(minderStore.event.nodes[0]);
|
||||
}
|
||||
|
|
|
@ -184,11 +184,19 @@
|
|||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useMinderStore from '@/store/modules/components/minder-editor/index';
|
||||
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 { 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';
|
||||
|
||||
const props = defineProps({
|
||||
|
@ -208,7 +216,9 @@
|
|||
const currentNodeTags = ref<string[]>([]);
|
||||
const tags = ref<string[]>([]);
|
||||
|
||||
const menuVisible = ref(false);
|
||||
const menuVisible = defineModel<boolean>('visible', {
|
||||
default: false,
|
||||
});
|
||||
const menuPopupOffset = ref<TriggerPopupTranslate>([0, 0]);
|
||||
|
||||
watch(
|
||||
|
@ -310,6 +320,9 @@
|
|||
}
|
||||
window.minder.execCommand('resource', origin);
|
||||
minderStore.dispatchEvent(MinderEventName.SET_TAG, undefined, undefined, undefined, selectedNodes);
|
||||
if (props.afterTagEdit) {
|
||||
props.afterTagEdit(selectedNodes, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -362,21 +375,15 @@
|
|||
case 'paste':
|
||||
minderStore.dispatchEvent(MinderEventName.PASTE_NODE, undefined, undefined, undefined, selectedNodes);
|
||||
window.minder.execCommand('Paste');
|
||||
const pastedNode: MinderJsonNode = window.minder.getSelectedNode();
|
||||
if (pastedNode) {
|
||||
pastedNode.data = {
|
||||
...pastedNode.data,
|
||||
text: pastedNode.data?.text || '',
|
||||
isNew: true,
|
||||
id: getGenerateId(),
|
||||
};
|
||||
traverseTree(pastedNode.children || [], (node) => {
|
||||
node.data = {
|
||||
...node.data,
|
||||
text: node.data?.text || '',
|
||||
let pastedNodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
if (pastedNodes.length > 0) {
|
||||
pastedNodes = pastedNodes.map((e) => {
|
||||
e.data = {
|
||||
...(e.data as MinderJsonNodeData),
|
||||
isNew: true,
|
||||
id: getGenerateId(),
|
||||
};
|
||||
return e;
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
tagProps,
|
||||
viewMenuProps,
|
||||
} from './props';
|
||||
import { isNodeInMinderView } from './script/tool/utils';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'moldChange', data: number): void;
|
||||
|
@ -97,9 +98,13 @@
|
|||
(val) => {
|
||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||
if (val && node) {
|
||||
setTimeout(() => {
|
||||
window.minder.execCommand('camera', node, 100);
|
||||
}, 100);
|
||||
const nodePosition = node?.getRenderBox();
|
||||
// 如果节点不在视图中,将节点移动到视图中
|
||||
if (nodePosition && !isNodeInMinderView(undefined, nodePosition, nodePosition.width / 2)) {
|
||||
setTimeout(() => {
|
||||
window.minder.execCommand('camera', node, 100);
|
||||
}, 300); // 抽屉动画 300ms
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -238,7 +238,7 @@ export default defineComponent({
|
|||
const computedCurrent = computed(() => props.current ?? _current.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) => {
|
||||
// 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(() => [
|
||||
prefixCls,
|
||||
`${prefixCls}-size-${mergedSize.value}`,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
v-model:model-value="innerParams.bodyType"
|
||||
type="button"
|
||||
size="small"
|
||||
:disabled="props.disabledBodyType"
|
||||
@change="(val) => changeBodyFormat(val as RequestBodyFormat)"
|
||||
>
|
||||
<a-radio v-for="item of RequestBodyFormat" :key="item" :value="item">
|
||||
|
@ -109,7 +110,6 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { TableColumnData } from '@arco-design/web-vue';
|
||||
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
|
@ -132,7 +132,7 @@
|
|||
import { defaultBodyParamsItem } from '@/views/api-test/components/config';
|
||||
|
||||
const props = defineProps<{
|
||||
params: ExecuteBody;
|
||||
disabledBodyType?: boolean; // 禁用body类型切换
|
||||
disabledParamValue?: boolean; // 参数值禁用
|
||||
disabledExceptParam?: boolean; // 除了可以修改参数值其他都禁用
|
||||
uploadTempFileApi?: (file: File) => Promise<any>; // 上传临时文件接口
|
||||
|
@ -148,15 +148,24 @@
|
|||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const innerParams = useVModel(props, 'params', emit);
|
||||
const innerParams = defineModel<ExecuteBody>('params', {
|
||||
required: true,
|
||||
});
|
||||
const batchAddKeyValVisible = ref(false);
|
||||
const fileList = ref<MsFileItem[]>([]);
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (innerParams.value.binaryBody && innerParams.value.binaryBody.file) {
|
||||
fileList.value = [innerParams.value.binaryBody.file as unknown as MsFileItem];
|
||||
watch(
|
||||
() => innerParams.value.binaryBody?.file,
|
||||
() => {
|
||||
if (innerParams.value.binaryBody?.file) {
|
||||
fileList.value = [innerParams.value.binaryBody.file as unknown as MsFileItem];
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
async function handleFileChange(files: MsFileItem[], file?: MsFileItem) {
|
||||
try {
|
||||
|
|
|
@ -780,7 +780,7 @@
|
|||
/**
|
||||
* 设置插件表单数据
|
||||
*/
|
||||
function setPluginFormData() {
|
||||
async function setPluginFormData() {
|
||||
const tempForm = temporaryPluginFormMap[requestVModel.value.id];
|
||||
if (tempForm || !requestVModel.value.isNew || requestVModel.value.isCopy) {
|
||||
// 如果缓存的表单数据存在或者是编辑状态,则需要将之前的输入数据填充
|
||||
|
@ -809,6 +809,7 @@
|
|||
isInitPluginForm.value = true;
|
||||
});
|
||||
}
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
const pluginError = ref(false);
|
||||
|
@ -823,7 +824,7 @@
|
|||
pluginError.value = false;
|
||||
isInitPluginForm.value = false;
|
||||
if (pluginScriptMap.value[requestVModel.value.protocol] !== undefined) {
|
||||
setPluginFormData();
|
||||
await setPluginFormData();
|
||||
// 已经初始化过
|
||||
return;
|
||||
}
|
||||
|
@ -831,7 +832,7 @@
|
|||
pluginLoading.value = true;
|
||||
const res = await getPluginScript(pluginId);
|
||||
pluginScriptMap.value[requestVModel.value.protocol] = res;
|
||||
setPluginFormData();
|
||||
await setPluginFormData();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
|
@ -1191,7 +1192,13 @@
|
|||
}
|
||||
if (props.request.isExecute && !requestVModel.value.executeLoading) {
|
||||
// 如果是执行操作打开接口详情,且该接口不在执行状态中,则立即执行
|
||||
execute(isPriorityLocalExec.value ? 'localExec' : 'serverExec');
|
||||
if (requestVModel.value.protocol !== 'HTTP') {
|
||||
setTimeout(() => {
|
||||
execute(isPriorityLocalExec.value ? 'localExec' : 'serverExec');
|
||||
}, 100);
|
||||
} else {
|
||||
execute(isPriorityLocalExec.value ? 'localExec' : 'serverExec');
|
||||
}
|
||||
} else if (temporaryResponseMap[props.request.reportId]) {
|
||||
// 如果有缓存的报告未读取,则直接赋值
|
||||
requestVModel.value.response = temporaryResponseMap[props.request.reportId];
|
||||
|
|
|
@ -239,6 +239,7 @@
|
|||
v-model:params="requestVModel.body"
|
||||
:disabled-param-value="!isEditableApi && !isEditableParamValue"
|
||||
:disabled-except-param="!isEditableApi"
|
||||
:disabled-body-type="!isEditableApi"
|
||||
:upload-temp-file-api="uploadTempFile"
|
||||
:file-save-as-source-id="props.step?.id"
|
||||
:file-save-as-api="stepTransferFile"
|
||||
|
|
|
@ -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.',
|
||||
'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.',
|
||||
'apiScenario.params.searchPlaceholder': 'Search by name or tag',
|
||||
'apiScenario.params.searchPlaceholder': 'Search by name/tag',
|
||||
'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.',
|
||||
'apiScenario.params.name': 'Variable Name',
|
||||
|
|
|
@ -99,7 +99,7 @@
|
|||
import useModal from '@/hooks/useModal';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useFeatureCaseStore from '@/store/modules/case/featureCase';
|
||||
import { mapTree } from '@/utils';
|
||||
import { mapTree, traverseTree } from '@/utils';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import type { CreateOrUpdateModule, UpdateModule } from '@/models/caseManagement/featureCase';
|
||||
|
@ -386,12 +386,9 @@
|
|||
watch(
|
||||
() => props.modulesCount,
|
||||
(obj) => {
|
||||
caseTree.value = mapTree<ModuleTreeNode>(caseTree.value, (node) => {
|
||||
return {
|
||||
...node,
|
||||
hideMoreAction: node.id === 'root' || props.isModal,
|
||||
count: obj?.[node.id] || 0,
|
||||
};
|
||||
traverseTree(caseTree.value, (node) => {
|
||||
node.count = obj?.[node.id] || 0;
|
||||
node.hideMoreAction = node.id === 'root' || props.isModal;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -9,7 +9,7 @@ export default {
|
|||
'project.environmental.requestHeader': 'Request Header',
|
||||
'project.environmental.allParams': 'All Parameters',
|
||||
'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.globalVariable': 'Parameter',
|
||||
'project.environmental.paramType': 'Type',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<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">
|
||||
<a-button v-permission="['PROJECT_USER:READ+ADD']" class="mr-3" type="primary" @click="addMember">
|
||||
{{ t('project.member.addMember') }}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export default {
|
||||
'project.member.addMember': 'Add 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.edit': 'Edit',
|
||||
'project.member.add': 'Add',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export default {
|
||||
'organization.member.addMember': 'Add Member',
|
||||
'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.edit': 'Edit',
|
||||
'organization.member.batchActionAddProject': 'Add to project',
|
||||
|
|
|
@ -76,7 +76,7 @@ export default {
|
|||
'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.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':
|
||||
'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',
|
||||
|
|
|
@ -2,7 +2,7 @@ export default {
|
|||
'system.user.createUser': 'Create User',
|
||||
'system.user.emailInvite': 'Email Invite',
|
||||
'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.resetPassword': 'Reset PSW',
|
||||
'system.user.disable': 'Disable',
|
||||
|
|
Loading…
Reference in New Issue