fix: 部分缺陷修复

This commit is contained in:
baiqi 2024-04-15 21:04:04 +08:00 committed by Craftsman
parent 9deabde693
commit 6de3b98fce
59 changed files with 386 additions and 276 deletions

View File

@ -419,14 +419,9 @@
.arco-checkbox-icon { .arco-checkbox-icon {
border: 1px solid var(--color-text-input-border); border: 1px solid var(--color-text-input-border);
} }
&:hover {
.arco-checkbox-icon-hover::before {
background-color: rgb(var(--primary-9)) !important;
}
}
.arco-checkbox-icon-hover { .arco-checkbox-icon-hover {
&:hover::before { &:hover::before {
background-color: rgb(var(--primary-9)) !important; @apply hidden;
} }
} }
} }

View File

@ -62,7 +62,7 @@
@change="emit('change')" @change="emit('change')"
/> />
<a-input-number <a-input-number
v-if="model.type === 'inputNumber'" v-else-if="model.type === 'inputNumber'"
v-model:model-value="element[model.filed]" v-model:model-value="element[model.filed]"
class="flex-1" class="flex-1"
:placeholder="t(model.placeholder || '')" :placeholder="t(model.placeholder || '')"
@ -73,7 +73,7 @@
@change="emit('change')" @change="emit('change')"
/> />
<MsTagsInput <MsTagsInput
v-if="model.type === 'tagInput'" v-else-if="model.type === 'tagInput'"
v-model:model-value="element[model.filed]" v-model:model-value="element[model.filed]"
class="flex-1" class="flex-1"
:placeholder="t(model.placeholder || 'common.tagPlaceholder')" :placeholder="t(model.placeholder || 'common.tagPlaceholder')"
@ -84,7 +84,7 @@
@change="emit('change')" @change="emit('change')"
/> />
<a-select <a-select
v-if="model.type === 'select'" v-else-if="model.type === 'select'"
v-model="element[model.filed]" v-model="element[model.filed]"
class="flex-1" class="flex-1"
:placeholder="t(model.placeholder || '')" :placeholder="t(model.placeholder || '')"
@ -92,7 +92,7 @@
:field-names="model.filedNames" :field-names="model.filedNames"
@change="emit('change')" @change="emit('change')"
/> />
<div v-if="model.type === 'multiple'" class="flex flex-row gap-[4px]"> <div v-else-if="model.type === 'multiple'" class="flex flex-row gap-[4px]">
<a-form-item <a-form-item
v-for="(child, childIndex) in model.children" v-for="(child, childIndex) in model.children"
:key="`${child.filed}${childIndex}${index}`" :key="`${child.filed}${childIndex}${index}`"
@ -114,7 +114,7 @@
@change="emit('change')" @change="emit('change')"
/> />
<a-select <a-select
v-if="child.type === 'select'" v-else-if="child.type === 'select'"
v-model="element[child.filed]" v-model="element[child.filed]"
:class="child.className" :class="child.className"
:placeholder="t(child.placeholder || '')" :placeholder="t(child.placeholder || '')"

View File

@ -26,22 +26,3 @@ export interface FormItemModel {
defaultValue?: string | string[] | number | number[] | boolean; // 默认值 defaultValue?: string | string[] | number | number[] | boolean; // 默认值
hasRedStar?: boolean; // 是否有红星 hasRedStar?: boolean; // 是否有红星
} }
declare const _default: import('vue').DefineComponent<
{
models: FormItemModel[];
formMode: FormMode;
addText: string;
maxHeight?: string;
defaultVals?: Record<string, string[] | string>; // 当外层是编辑状态时,可传入已填充的数据
},
unknown,
import('vue').ComponentOptionsMixin,
import('vue').ComponentOptionsMixin,
{
formValidate: (cb: (res?: Record<string, any>) => void, isSubmit = true) => void;
getFormResult: <T>() => T[];
}
>;
export declare type MsBatchFormInstance = InstanceType<typeof _default>;

View File

@ -113,7 +113,7 @@
</a-tooltip> </a-tooltip>
</template> </template>
<template #caseLevel="{ record }"> <template #caseLevel="{ record }">
<span>{{ t(record.priority) }}</span> <caseLevel :case-level="getCaseLevel(record)" />
</template> </template>
</ms-base-table> </ms-base-table>
<div class="footer"> <div class="footer">
@ -168,6 +168,7 @@
import type { CommonList, ModuleTreeNode, TableQueryParams } from '@/models/common'; import type { CommonList, ModuleTreeNode, TableQueryParams } from '@/models/common';
import { CaseManagementRouteEnum } from '@/enums/routeEnum'; import { CaseManagementRouteEnum } from '@/enums/routeEnum';
import type { CaseLevel } from './types';
import { initGetModuleCountFunc, type RequestModuleEnum } from './utils'; import { initGetModuleCountFunc, type RequestModuleEnum } from './utils';
const router = useRouter(); const router = useRouter();
@ -328,7 +329,8 @@
sortDirections: ['ascend', 'descend'], sortDirections: ['ascend', 'descend'],
sorter: true, sorter: true,
}, },
width: 200, width: 150,
fixed: 'left',
}, },
{ {
title: 'ms.case.associate.caseName', title: 'ms.case.associate.caseName',
@ -338,7 +340,7 @@
sorter: true, sorter: true,
}, },
showTooltip: true, showTooltip: true,
width: 300, width: 250,
}, },
{ {
title: 'ms.case.associate.caseLevel', title: 'ms.case.associate.caseLevel',
@ -357,6 +359,7 @@
props.getTableFunc, props.getTableFunc,
{ {
columns, columns,
scroll: { x: '100%' },
showSetting: false, showSetting: false,
selectable: true, selectable: true,
showSelectAll: true, showSelectAll: true,
@ -375,6 +378,11 @@
} }
); );
//
function getCaseLevel(record: CaseManagementTable) {
return (record.customFields.find((item: any) => item.name === '用例等级')?.value as CaseLevel) || 'P1';
}
const searchParams = ref<TableQueryParams>({ const searchParams = ref<TableQueryParams>({
moduleIds: [], moduleIds: [],
version: version.value, version: version.value,

View File

@ -32,6 +32,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useRouter } from 'vue-router';
import { FormInstance, Message } from '@arco-design/web-vue'; import { FormInstance, Message } from '@arco-design/web-vue';
import MsPasswordInput from '@/components/pure/ms-password-input/index.vue'; import MsPasswordInput from '@/components/pure/ms-password-input/index.vue';
@ -39,13 +40,12 @@
import { updatePsw } from '@/api/modules/user'; import { updatePsw } from '@/api/modules/user';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useUser from '@/hooks/useUser';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
import { encrypted } from '@/utils'; import { encrypted } from '@/utils';
import { validatePasswordLength, validateWordPassword } from '@/utils/validate'; import { validatePasswordLength, validateWordPassword } from '@/utils/validate';
const router = useRouter();
const userStore = useUserStore(); const userStore = useUserStore();
const { logout } = useUser();
const { t } = useI18n(); const { t } = useI18n();
const form = ref({ const form = ref({
@ -111,7 +111,13 @@
}, 1000); }, 1000);
setTimeout(() => { setTimeout(() => {
clearInterval(timer); clearInterval(timer);
logout(); router.push({
name: 'login',
query: {
...router.currentRoute.value.query,
redirect: router.currentRoute.value.name as string,
},
});
}, 3000); }, 3000);
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console

View File

@ -458,7 +458,7 @@
.arco-tree-node-drag-icon { .arco-tree-node-drag-icon {
@apply cursor-move; @apply cursor-move;
right: 6px; right: 16px;
.arco-icon { .arco-icon {
font-size: 14px; font-size: 14px;
} }

View File

@ -4,7 +4,7 @@
:popup-visible="currentVisible" :popup-visible="currentVisible"
position="bl" position="bl"
trigger="click" trigger="click"
class="w-[277px]" class="w-[350px]"
:content-class="props.id ? 'move-left' : ''" :content-class="props.id ? 'move-left' : ''"
> >
<template #content> <template #content>
@ -18,13 +18,12 @@
:label-col-props="{ span: 0 }" :label-col-props="{ span: 0 }"
:wrapper-col-props="{ span: 24 }" :wrapper-col-props="{ span: 24 }"
> >
<div class="mb-[8px] text-[14px] font-medium text-[var(--color-text-1)]">{{ <div class="mb-[8px] text-[14px] font-medium text-[var(--color-text-1)]">
props.id ? t('system.userGroup.rename') : t('system.userGroup.createUserGroup') {{ props.id ? t('system.userGroup.rename') : t('system.userGroup.createUserGroup') }}
}}</div> </div>
<a-form-item field="name" :rules="[{ validator: validateName }]"> <a-form-item field="name" :rules="[{ validator: validateName }]">
<a-input <a-input
v-model="form.name" v-model="form.name"
class="w-[243px]"
:max-length="255" :max-length="255"
:placeholder="t('system.userGroup.searchHolder')" :placeholder="t('system.userGroup.searchHolder')"
allow-clear allow-clear
@ -107,7 +106,7 @@
callback(t('system.userGroup.userGroupNameIsExist', { name: value })); callback(t('system.userGroup.userGroupNameIsExist', { name: value }));
} }
} }
if (value.length === 255) { if (value.length > 255) {
callback(t('common.nameIsTooLang')); callback(t('common.nameIsTooLang'));
} }
callback(); callback();

View File

@ -445,7 +445,13 @@
if (isSelect) { if (isSelect) {
// leftCollapse // leftCollapse
if (id) { if (id) {
handleListItemClick(res.find((i) => i.id === id) || res[0]); const item = res.find((i) => i.id === id);
if (item) {
handleListItemClick(item);
} else {
Message.warning(t('common.resourceDeleted'));
handleListItemClick(res[0]);
}
} else { } else {
handleListItemClick(res[0]); handleListItemClick(res[0]);
} }
@ -465,8 +471,8 @@
// //
const handleMoreAction = (item: ActionsItem, id: string, authScope: AuthScopeEnum) => { const handleMoreAction = (item: ActionsItem, id: string, authScope: AuthScopeEnum) => {
const tmpObj = userGroupList.value.filter((ele) => ele.id === id)[0];
if (item.eventTag === 'rename') { if (item.eventTag === 'rename') {
const tmpObj = userGroupList.value.filter((ele) => ele.id === id)[0];
const visibleItem: PopVisibleItem = { visible: true, authScope, defaultName: tmpObj.name, id }; const visibleItem: PopVisibleItem = { visible: true, authScope, defaultName: tmpObj.name, id };
popVisible.value[id] = visibleItem; popVisible.value[id] = visibleItem;
} }
@ -485,7 +491,7 @@
} }
openModal({ openModal({
type: 'error', type: 'error',
title: t('system.userGroup.isDeleteUserGroup', { name: characterLimit(currentName.value) }), title: t('system.userGroup.isDeleteUserGroup', { name: characterLimit(tmpObj.name) }),
content, content,
okText: t('system.userGroup.confirmDelete'), okText: t('system.userGroup.confirmDelete'),
cancelText: t('system.userGroup.cancel'), cancelText: t('system.userGroup.cancel'),

View File

@ -8,7 +8,7 @@
<MsRemoveButton <MsRemoveButton
:title="t('system.userGroup.removeName', { name: characterLimit(record.name) })" :title="t('system.userGroup.removeName', { name: characterLimit(record.name) })"
:sub-title-tip="t('system.userGroup.removeTip')" :sub-title-tip="t('system.userGroup.removeTip')"
:disabled="record.userId === 'admin'" :disabled="systemType === AuthScopeEnum.SYSTEM && record.userId === 'admin'"
@ok="handleRemove(record)" @ok="handleRemove(record)"
/> />
</template> </template>
@ -77,7 +77,7 @@
}; };
}); });
const userGroupUsercolumns: MsTableColumn = [ const userGroupUserColumns: MsTableColumn = [
{ {
title: 'system.userGroup.name', title: 'system.userGroup.name',
dataIndex: 'name', dataIndex: 'name',
@ -113,7 +113,7 @@
const { propsRes, propsEvent, loadList, setLoadListParams, setKeyword } = useTable( const { propsRes, propsEvent, loadList, setLoadListParams, setKeyword } = useTable(
getRequestBySystemType(), getRequestBySystemType(),
{ {
columns: userGroupUsercolumns, columns: userGroupUserColumns,
scroll: { x: '100%', minWidth: 700, y: '100%' }, scroll: { x: '100%', minWidth: 700, y: '100%' },
selectable: false, selectable: false,
noDisable: true, noDisable: true,
@ -130,7 +130,6 @@
const handlePermission = (permission: string[], cb: () => void) => { const handlePermission = (permission: string[], cb: () => void) => {
if (!hasAnyPermission(permission)) { if (!hasAnyPermission(permission)) {
Message.error(t('common.noPermission'));
return false; return false;
} }
cb(); cb();

View File

@ -58,10 +58,10 @@
<div class="flex justify-end gap-[16px]"> <div class="flex justify-end gap-[16px]">
<a-button type="secondary" @click="back">{{ t('mscard.defaultCancelText') }}</a-button> <a-button type="secondary" @click="back">{{ t('mscard.defaultCancelText') }}</a-button>
<a-button v-if="!props.hideContinue && !props.isEdit" type="secondary" @click="emit('saveAndContinue')"> <a-button v-if="!props.hideContinue && !props.isEdit" type="secondary" @click="emit('saveAndContinue')">
{{ t('mscard.defaultSaveAndContinueText') }} {{ props.saveAndContinueText || t('mscard.defaultSaveAndContinueText') }}
</a-button> </a-button>
<a-button type="primary" @click="emit('save')"> <a-button type="primary" @click="emit('save')">
{{ t(props.isEdit ? 'mscard.defaultUpdate' : 'mscard.defaultConfirm') }} {{ props.saveText || t(props.isEdit ? 'mscard.defaultUpdate' : 'mscard.defaultConfirm') }}
</a-button> </a-button>
</div> </div>
</slot> </slot>
@ -102,6 +102,8 @@
handleBack: () => void; // handleBack: () => void; //
dividerHasPX: boolean; // 线padding; dividerHasPX: boolean; // 线padding;
showFullScreen: boolean; // showFullScreen: boolean; //
saveText?: string; //
saveAndContinueText?: string; //
}> }>
>(), >(),
{ {
@ -151,6 +153,10 @@
// //
return props.noContentPadding ? 66 + _specialHeight : 114 + _specialHeight; return props.noContentPadding ? 66 + _specialHeight : 114 + _specialHeight;
} }
if (props.hideFooter) {
//
return props.noContentPadding ? 120 + _specialHeight : 168 + _specialHeight;
}
return 230 + _specialHeight; return 230 + _specialHeight;
}); });

View File

@ -13,10 +13,10 @@
</a-tooltip> </a-tooltip>
<div ref="tabNav" class="ms-editable-tab-nav"> <div ref="tabNav" class="ms-editable-tab-nav">
<div <div
v-for="tab in props.tabs" v-for="tab in tabs"
:key="tab.id" :key="tab.id"
class="ms-editable-tab" class="ms-editable-tab"
:class="{ active: innerActiveTab?.id === tab.id }" :class="{ active: activeTab?.id === tab.id }"
@click="handleTabClick(tab)" @click="handleTabClick(tab)"
> >
<div :draggable="!!tab.draggable" class="flex items-center"> <div :draggable="!!tab.draggable" class="flex items-center">
@ -29,7 +29,7 @@
</slot> </slot>
<div v-if="tab.unSaved" class="ml-[8px] h-[8px] w-[8px] rounded-full bg-[rgb(var(--primary-5))]"></div> <div v-if="tab.unSaved" class="ml-[8px] h-[8px] w-[8px] rounded-full bg-[rgb(var(--primary-5))]"></div>
<MsButton <MsButton
v-if="props.atLeastOne ? props.tabs.length > 1 && tab.closable : tab.closable !== false" v-if="props.atLeastOne ? tabs.length > 1 && tab.closable : tab.closable !== false"
type="icon" type="icon"
status="secondary" status="secondary"
class="ms-editable-tab-close-button" class="ms-editable-tab-close-button"
@ -54,20 +54,20 @@
<a-tooltip <a-tooltip
v-if="!props.readonly && showAdd" v-if="!props.readonly && showAdd"
:content="t('ms.editableTab.limitTip', { max: props.limit })" :content="t('ms.editableTab.limitTip', { max: props.limit })"
:disabled="!props.limit || props.tabs.length >= props.limit" :disabled="!props.limit || tabs.length >= props.limit"
> >
<MsButton <MsButton
type="icon" type="icon"
status="secondary" status="secondary"
class="ms-editable-tab-button !mr-[4px]" class="ms-editable-tab-button !mr-[4px]"
:disabled="!!props.limit && props.tabs.length >= props.limit" :disabled="!!props.limit && tabs.length >= props.limit"
@click="addTab" @click="addTab"
> >
<MsIcon type="icon-icon_add_outlined" /> <MsIcon type="icon-icon_add_outlined" />
</MsButton> </MsButton>
</a-tooltip> </a-tooltip>
<MsMoreAction <MsMoreAction
v-if="!props.hideMoreAction && !props.readonly" v-if="!props.hideMoreAction && !props.readonly && mergedMoreActionList.length > 0"
:list="mergedMoreActionList" :list="mergedMoreActionList"
@select="handleMoreActionSelect" @select="handleMoreActionSelect"
> >
@ -95,8 +95,6 @@
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
tabs: TabItem[];
activeTab?: TabItem;
moreActionList?: ActionsItem[]; moreActionList?: ActionsItem[];
showAdd?: boolean; // tab showAdd?: boolean; // tab
limit?: number; // tab limit?: number; // tab
@ -109,8 +107,6 @@
} }
); );
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:tabs', tabs: TabItem[]): void;
(e: 'update:activeTab', activeTab: TabItem): void;
(e: 'add'): void; (e: 'add'): void;
(e: 'close', item: TabItem): void; (e: 'close', item: TabItem): void;
(e: 'change', item: TabItem): void; (e: 'change', item: TabItem): void;
@ -120,8 +116,12 @@
const { t } = useI18n(); const { t } = useI18n();
const { openModal } = useModal(); const { openModal } = useModal();
const innerActiveTab = useVModel(props, 'activeTab', emit); const activeTab = defineModel<TabItem | undefined>('activeTab', {
const innerTabs = useVModel(props, 'tabs', emit); default: undefined,
});
const tabs = defineModel<TabItem[]>('tabs', {
required: true,
});
const tabNav = ref<HTMLElement>(); const tabNav = ref<HTMLElement>();
const { arrivedState } = useScroll(tabNav); const { arrivedState } = useScroll(tabNav);
const isNotOverflow = computed(() => arrivedState.left && arrivedState.right); // const isNotOverflow = computed(() => arrivedState.left && arrivedState.right); //
@ -178,6 +178,9 @@
const dl = props.atLeastOne const dl = props.atLeastOne
? defaultMoreActionList.filter((e) => e.eventTag !== 'closeAll') ? defaultMoreActionList.filter((e) => e.eventTag !== 'closeAll')
: defaultMoreActionList; : defaultMoreActionList;
if (tabs.value.filter((item) => item.closable === true).length === 0) {
return props.moreActionList ? [...props.moreActionList] : [];
}
return props.moreActionList ? [...dl, ...props.moreActionList] : dl; return props.moreActionList ? [...dl, ...props.moreActionList] : dl;
}); });
@ -185,9 +188,9 @@
* 监听激活的tab变化滚动到激活的tab * 监听激活的tab变化滚动到激活的tab
*/ */
watch( watch(
() => props.activeTab, () => activeTab.value,
() => { () => {
useDraggable('.ms-editable-tab-nav', innerTabs, { useDraggable('.ms-editable-tab-nav', tabs, {
ghostClass: 'ms-editable-tab-ghost', ghostClass: 'ms-editable-tab-ghost',
}); });
nextTick(() => { nextTick(() => {
@ -211,11 +214,11 @@
* 关闭一个tab * 关闭一个tab
*/ */
function closeOneTab(item: TabItem) { function closeOneTab(item: TabItem) {
const index = innerTabs.value.findIndex((e) => e.id === item.id); const index = tabs.value.findIndex((e) => e.id === item.id);
innerTabs.value.splice(index, 1); tabs.value.splice(index, 1);
if (innerActiveTab.value?.id === item.id && innerTabs.value[0]) { if (activeTab.value?.id === item.id && tabs.value[0]) {
[innerActiveTab.value] = innerTabs.value; [activeTab.value] = tabs.value;
emit('change', innerTabs.value[0]); emit('change', tabs.value[0]);
} }
} }
@ -241,8 +244,8 @@
} }
function handleTabClick(item: TabItem) { function handleTabClick(item: TabItem) {
if (innerActiveTab.value?.id !== item.id) { if (activeTab.value?.id !== item.id) {
innerActiveTab.value = item; activeTab.value = item;
nextTick(() => { nextTick(() => {
tabNav.value?.querySelector('.tab.active')?.scrollIntoView({ behavior: 'smooth', block: 'center' }); tabNav.value?.querySelector('.tab.active')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}); });
@ -256,14 +259,12 @@
function executeAction(event: ActionsItem) { function executeAction(event: ActionsItem) {
switch (event.eventTag) { switch (event.eventTag) {
case 'closeAll': case 'closeAll':
innerTabs.value = innerTabs.value.filter((item) => item.closable === false); tabs.value = tabs.value.filter((item) => item.closable === false);
[innerActiveTab.value] = innerTabs.value; [activeTab.value] = tabs.value;
emit('change', innerActiveTab.value); emit('change', activeTab.value);
break; break;
case 'closeOther': case 'closeOther':
innerTabs.value = innerTabs.value.filter( tabs.value = tabs.value.filter((item) => item.id === activeTab.value?.id || item.closable === false);
(item) => item.id === innerActiveTab.value?.id || item.closable === false
);
break; break;
default: default:
emit('moreActionSelect', event); emit('moreActionSelect', event);
@ -276,9 +277,8 @@
*/ */
function handleMoreActionSelect(event: ActionsItem) { function handleMoreActionSelect(event: ActionsItem) {
if ( if (
(event.eventTag === 'closeAll' && innerTabs.value.some((item) => item.unSaved)) || (event.eventTag === 'closeAll' && tabs.value.some((item) => item.unSaved)) ||
(event.eventTag === 'closeOther' && (event.eventTag === 'closeOther' && tabs.value.some((item) => item.unSaved && item.id !== activeTab.value?.id))
innerTabs.value.some((item) => item.unSaved && item.id !== innerActiveTab.value?.id))
) { ) {
openModal({ openModal({
title: t('common.tip'), title: t('common.tip'),

View File

@ -3,14 +3,14 @@ import { onBeforeRouteLeave } from 'vue-router';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
const isSave = ref(false);
// 离开页面确认提示 // 离开页面确认提示
export default function useLeaveUnSaveTip() { export default function useLeaveUnSaveTip() {
const { openModal } = useModal(); const { openModal } = useModal();
const { t } = useI18n(); const { t } = useI18n();
const setState = (flag: boolean) => { const isSave = ref(true);
const setIsSave = (flag: boolean) => {
isSave.value = flag; isSave.value = flag;
}; };
onBeforeRouteLeave((to, from, next) => { onBeforeRouteLeave((to, from, next) => {
@ -40,6 +40,6 @@ export default function useLeaveUnSaveTip() {
} }
}); });
return { return {
setState, setIsSave,
}; };
} }

View File

@ -1,5 +1,4 @@
import { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'; import { RouteLocationNormalized, RouteRecordRaw } from 'vue-router';
import { includes } from 'lodash-es';
import { firstLevelMenu } from '@/config/permission'; import { firstLevelMenu } from '@/config/permission';
import { hasAnyPermission, topLevelMenuHasPermission } from '@/utils/permission'; import { hasAnyPermission, topLevelMenuHasPermission } from '@/utils/permission';

View File

@ -151,4 +151,5 @@ export default {
'The content of some tabs has not been saved. The unsaved content will be lost after leaving. Are you sure you want to leave?', 'The content of some tabs has not been saved. The unsaved content will be lost after leaving. Are you sure you want to leave?',
'common.image': 'Image', 'common.image': 'Image',
'common.text': 'Text', 'common.text': 'Text',
'common.resourceDeleted': 'Resource has been deleted',
}; };

View File

@ -151,4 +151,5 @@ export default {
'common.unsavedLeave': '有标签页的内容未保存,离开后未保存的内容将丢失,确定要离开吗?', 'common.unsavedLeave': '有标签页的内容未保存,离开后未保存的内容将丢失,确定要离开吗?',
'common.image': '图片', 'common.image': '图片',
'common.text': '文本', 'common.text': '文本',
'common.resourceDeleted': '资源已被删除',
}; };

View File

@ -51,6 +51,7 @@ export interface ResourcePoolItem extends ResourcePoolInfo {
export type ResourcePoolDetail = Omit<ResourcePoolInfo, 'testResourceDTO'> & { export type ResourcePoolDetail = Omit<ResourcePoolInfo, 'testResourceDTO'> & {
id: string; id: string;
deleted: boolean;
testResourceReturnDTO: TestResourceDTO; testResourceReturnDTO: TestResourceDTO;
}; };

View File

@ -525,7 +525,6 @@
const appStore = useAppStore(); const appStore = useAppStore();
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
data: ExecuteConditionProcessor;
disabled?: boolean; disabled?: boolean;
response?: string; // response?: string; //
heightUsed?: number; heightUsed?: number;
@ -542,7 +541,6 @@
} }
); );
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:data', data: ExecuteConditionProcessor): void;
(e: 'copy'): void; (e: 'copy'): void;
(e: 'delete', id: number): void; (e: 'delete', id: number): void;
(e: 'change'): void; (e: 'change'): void;
@ -551,30 +549,36 @@
const { t } = useI18n(); const { t } = useI18n();
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig'); const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
const condition = useVModel(props, 'data', emit); const condition = defineModel<ExecuteConditionProcessor>('data', {
required: true,
});
function filterDataSource() {
if (condition.value.processorType === RequestConditionProcessor.SQL && condition.value.dataSourceId) {
// SQL
const dataSourceItem = currentEnvConfig?.value.dataSources.find(
(item) => item.dataSource === condition.value.dataSourceName
);
if (currentEnvConfig?.value.dataSources.length === 0) {
//
condition.value.dataSourceName = '';
condition.value.dataSourceId = '';
} else if (dataSourceItem) {
//
condition.value.dataSourceName = dataSourceItem.dataSource;
condition.value.dataSourceId = dataSourceItem.id;
} else if (currentEnvConfig && currentEnvConfig.value.dataSources.length > 0) {
//
condition.value.dataSourceName = currentEnvConfig.value.dataSources[0].dataSource;
condition.value.dataSourceId = currentEnvConfig.value.dataSources[0].id;
}
}
}
watch( watch(
() => currentEnvConfig?.value, () => currentEnvConfig?.value,
() => { () => {
if (condition.value.processorType === RequestConditionProcessor.SQL && condition.value.dataSourceId) { filterDataSource();
// SQL
const dataSourceItem = currentEnvConfig?.value.dataSources.find(
(item) => item.dataSource === condition.value.dataSourceName
);
if (currentEnvConfig?.value.dataSources.length === 0) {
//
condition.value.dataSourceName = '';
condition.value.dataSourceId = '';
} else if (dataSourceItem) {
//
condition.value.dataSourceName = dataSourceItem.dataSource;
condition.value.dataSourceId = dataSourceItem.id;
} else if (currentEnvConfig && currentEnvConfig.value.dataSources.length > 0) {
//
condition.value.dataSourceName = currentEnvConfig.value.dataSources[0].dataSource;
condition.value.dataSourceId = currentEnvConfig.value.dataSources[0].id;
}
}
}, },
{ {
immediate: true, immediate: true,
@ -582,6 +586,13 @@
} }
); );
watch(
() => condition.value.id,
() => {
filterDataSource();
}
);
// //
const isShowEditScriptNameInput = ref(false); const isShowEditScriptNameInput = ref(false);
const scriptNameInputRef = ref<InputInstance>(); const scriptNameInputRef = ref<InputInstance>();
@ -1003,12 +1014,14 @@ if (!result){
const protocolList = ref<ProtocolItem[]>([]); const protocolList = ref<ProtocolItem[]>([]);
onBeforeMount(async () => { onBeforeMount(async () => {
try { if (props.showPrePostRequest) {
// TODO: try {
protocolList.value = await getProtocolList(appStore.currentOrgId); // TODO:
} catch (error) { protocolList.value = await getProtocolList(appStore.currentOrgId);
// eslint-disable-next-line no-console } catch (error) {
console.log(error); // eslint-disable-next-line no-console
console.log(error);
}
} }
}); });
watch( watch(

View File

@ -17,10 +17,10 @@
<slot name="titleRight"></slot> <slot name="titleRight"></slot>
</div> </div>
</div> </div>
<div v-if="data.length > 0 && activeItem" class="flex h-[calc(100%-40px)] w-full gap-[8px]"> <div v-if="list.length > 0 && activeItem" class="flex h-[calc(100%-40px)] w-full gap-[8px]">
<div class="h-full w-[20%] min-w-[220px]"> <div class="h-full w-[20%] min-w-[220px]">
<conditionList <conditionList
v-model:list="data" v-model:list="list"
:disabled="props.disabled" :disabled="props.disabled"
:active-id="activeItemId" :active-id="activeItemId"
:show-associated-scene="props.showAssociatedScene" :show-associated-scene="props.showAssociatedScene"
@ -32,7 +32,7 @@
<conditionContent <conditionContent
v-model:data="activeItem" v-model:data="activeItem"
:disabled="props.disabled" :disabled="props.disabled"
:total-list="data" :total-list="list"
:response="props.response" :response="props.response"
:height-used="props.heightUsed" :height-used="props.heightUsed"
:show-associated-scene="props.showAssociatedScene" :show-associated-scene="props.showAssociatedScene"
@ -57,12 +57,11 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { ConditionType, ExecuteConditionProcessor, RegexExtract } from '@/models/apiTest/common'; import { ConditionType, ExecuteConditionProcessor, RegexExtract } from '@/models/apiTest/common';
import { RequestConditionProcessor, RequestExtractExpressionEnum, RequestExtractScope } from '@/enums/apiEnum'; import { RequestConditionProcessor, RequestExtractScope } from '@/enums/apiEnum';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
disabled?: boolean; disabled?: boolean;
list: ExecuteConditionProcessor[];
conditionTypes: Array<ConditionType>; conditionTypes: Array<ConditionType>;
addText: string; addText: string;
requestRadioTextProps?: Record<string, any>; requestRadioTextProps?: Record<string, any>;
@ -77,16 +76,15 @@
} }
); );
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:list', list: ExecuteConditionProcessor[]): void;
(e: 'change'): void; (e: 'change'): void;
}>(); }>();
const { t } = useI18n(); const { t } = useI18n();
const data = defineModel<ExecuteConditionProcessor[]>('list', { const list = defineModel<ExecuteConditionProcessor[]>('list', {
required: true, required: true,
}); });
const activeItem = ref<ExecuteConditionProcessor>(data.value[0]); const activeItem = ref<ExecuteConditionProcessor>(list.value[0]);
const activeItemId = computed(() => activeItem.value?.id); const activeItemId = computed(() => activeItem.value?.id);
function handleListActiveChange(item: ExecuteConditionProcessor) { function handleListActiveChange(item: ExecuteConditionProcessor) {
@ -101,8 +99,8 @@
...cloneDeep(activeItem.value), ...cloneDeep(activeItem.value),
id: new Date().getTime(), id: new Date().getTime(),
}; };
data.value.push(copyItem as ExecuteConditionProcessor); list.value.push(copyItem as ExecuteConditionProcessor);
activeItem.value = copyItem as ExecuteConditionProcessor; activeItem.value = list.value[list.value.length - 1];
emit('change'); emit('change');
} }
@ -110,9 +108,9 @@
* 删除列表项 * 删除列表项
*/ */
function deleteListItem(id: string | number) { function deleteListItem(id: string | number) {
data.value = data.value.filter((precondition) => precondition.id !== activeItemId.value); list.value = list.value.filter((precondition) => precondition.id !== activeItemId.value);
if (activeItemId.value === id) { if (activeItemId.value === id) {
[activeItem.value] = data.value; [activeItem.value] = list.value;
} }
emit('change'); emit('change');
} }
@ -131,10 +129,10 @@
} else if (props.showPrePostRequest) { } else if (props.showPrePostRequest) {
type = RequestConditionProcessor.REQUEST_SCRIPT; type = RequestConditionProcessor.REQUEST_SCRIPT;
} }
const isExistPre = data.value.filter( const isExistPre = list.value.filter(
(item) => item.beforeStepScript && item.processorType === RequestConditionProcessor.REQUEST_SCRIPT (item) => item.beforeStepScript && item.processorType === RequestConditionProcessor.REQUEST_SCRIPT
).length; ).length;
const isExistPost = data.value.filter( const isExistPost = list.value.filter(
(item) => !item.beforeStepScript && item.processorType === RequestConditionProcessor.REQUEST_SCRIPT (item) => !item.beforeStepScript && item.processorType === RequestConditionProcessor.REQUEST_SCRIPT
).length; ).length;
// //
@ -142,7 +140,7 @@
if (isExistPre && isExistPost && props.showPrePostRequest) { if (isExistPre && isExistPost && props.showPrePostRequest) {
return; return;
} }
data.value.push({ list.value.push({
id, id,
processorType: type, processorType: type,
name: t('apiTestDebug.preconditionScriptName'), name: t('apiTestDebug.preconditionScriptName'),
@ -165,12 +163,12 @@
break; break;
case RequestConditionProcessor.SQL: case RequestConditionProcessor.SQL:
const isSQL = data.value.find((item) => item.processorType === RequestConditionProcessor.SQL); const isSQL = list.value.find((item) => item.processorType === RequestConditionProcessor.SQL);
if (props.showPrePostRequest && isSQL) { if (props.showPrePostRequest && isSQL) {
return; return;
} }
data.value.push({ list.value.push({
id, id,
processorType: RequestConditionProcessor.SQL, processorType: RequestConditionProcessor.SQL,
enableCommonScript: false, enableCommonScript: false,
@ -189,7 +187,7 @@
break; break;
case RequestConditionProcessor.TIME_WAITING: case RequestConditionProcessor.TIME_WAITING:
data.value.push({ list.value.push({
id, id,
processorType: RequestConditionProcessor.TIME_WAITING, processorType: RequestConditionProcessor.TIME_WAITING,
associateScenarioResult: false, associateScenarioResult: false,
@ -200,11 +198,11 @@
}); });
break; break;
case RequestConditionProcessor.EXTRACT: case RequestConditionProcessor.EXTRACT:
const isEXTRACT = data.value.find((item) => item.processorType === RequestConditionProcessor.EXTRACT); const isEXTRACT = list.value.find((item) => item.processorType === RequestConditionProcessor.EXTRACT);
if (isEXTRACT) { if (isEXTRACT) {
return; return;
} }
data.value.push({ list.value.push({
id, id,
processorType: RequestConditionProcessor.EXTRACT, processorType: RequestConditionProcessor.EXTRACT,
enableCommonScript: false, enableCommonScript: false,
@ -218,14 +216,14 @@
default: default:
break; break;
} }
activeItem.value = data.value[data.value.length - 1]; activeItem.value = list.value[list.value.length - 1];
emit('change'); emit('change');
} }
watchEffect(() => { watchEffect(() => {
// id // id
let hasNoIdItem = false; let hasNoIdItem = false;
const tempArr = props.list.map((item, i) => { const tempArr = list.value.map((item, i) => {
if (!item.id) { if (!item.id) {
hasNoIdItem = true; hasNoIdItem = true;
return { return {
@ -241,8 +239,8 @@
return item; return item;
}); });
if (hasNoIdItem) { if (hasNoIdItem) {
data.value = tempArr.map((e) => e); list.value = tempArr;
[activeItem.value] = data.value; [activeItem.value] = list.value;
} }
}); });

View File

@ -2,7 +2,7 @@
<MsList <MsList
v-model:active-item-key="activeItem.id" v-model:active-item-key="activeItem.id"
v-model:focus-item-key="focusItemKey" v-model:focus-item-key="focusItemKey"
v-model:data="data" v-model:data="list"
mode="static" mode="static"
item-key-field="id" item-key-field="id"
:disabled="props.disabled" :disabled="props.disabled"
@ -59,7 +59,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import MsList from '@/components/pure/ms-list/index.vue'; import MsList from '@/components/pure/ms-list/index.vue';
@ -74,20 +73,20 @@
import { RequestConditionProcessor } from '@/enums/apiEnum'; import { RequestConditionProcessor } from '@/enums/apiEnum';
const props = defineProps<{ const props = defineProps<{
list: ExecuteConditionProcessor[];
activeId?: string | number; activeId?: string | number;
showAssociatedScene?: boolean; showAssociatedScene?: boolean;
disabled?: boolean; disabled?: boolean;
showPrePostRequest?: boolean; // showPrePostRequest?: boolean; //
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:list', list: ExecuteConditionProcessor[]): void;
(e: 'activeChange', item: ExecuteConditionProcessor): void; (e: 'activeChange', item: ExecuteConditionProcessor): void;
(e: 'change'): void; (e: 'change'): void;
}>(); }>();
const { t } = useI18n(); const { t } = useI18n();
const data = useVModel(props, 'list', emit); const list = defineModel<ExecuteConditionProcessor[]>('list', {
required: true,
});
const { openModal } = useModal(); const { openModal } = useModal();
// //
@ -98,11 +97,11 @@
const hasPreAndPost = computed(() => { const hasPreAndPost = computed(() => {
if (props.showPrePostRequest) { if (props.showPrePostRequest) {
const hasPre = const hasPre =
data.value.filter( list.value.filter(
(item) => item.beforeStepScript && item.processorType === RequestConditionProcessor.REQUEST_SCRIPT (item) => item.beforeStepScript && item.processorType === RequestConditionProcessor.REQUEST_SCRIPT
).length > 0; ).length > 0;
const hasPost = const hasPost =
data.value.filter( list.value.filter(
(item) => !item.beforeStepScript && item.processorType === RequestConditionProcessor.REQUEST_SCRIPT (item) => !item.beforeStepScript && item.processorType === RequestConditionProcessor.REQUEST_SCRIPT
).length > 0; ).length > 0;
if (hasPre && hasPost) { if (hasPre && hasPost) {
@ -114,12 +113,12 @@
}); });
const hasEXTRACT = computed(() => { const hasEXTRACT = computed(() => {
return data.value.filter((item: any) => item.processorType === RequestConditionProcessor.EXTRACT).length > 0; return list.value.filter((item: any) => item.processorType === RequestConditionProcessor.EXTRACT).length > 0;
}); });
const hasSql = computed( const hasSql = computed(
() => () =>
data.value.filter((item: any) => item.processorType === RequestConditionProcessor.SQL).length > 0 && list.value.filter((item: any) => item.processorType === RequestConditionProcessor.SQL).length > 0 &&
props.showPrePostRequest props.showPrePostRequest
); );
@ -139,7 +138,7 @@
let moreActions: ActionsItem[] = [...itemMoreActions]; let moreActions: ActionsItem[] = [...itemMoreActions];
watchEffect(() => { watchEffect(() => {
activeItem.value = data.value.find((item) => item.id === props.activeId) || data.value[0] || {}; activeItem.value = list.value.find((item) => item.id === props.activeId) || list.value[0] || {};
emit('activeChange', activeItem.value); emit('activeChange', activeItem.value);
if (hasPreAndPost.value || hasEXTRACT.value || hasSql.value) { if (hasPreAndPost.value || hasEXTRACT.value || hasSql.value) {
moreActions = itemMoreActions.slice(-1); moreActions = itemMoreActions.slice(-1);
@ -162,10 +161,10 @@
...cloneDeep(item), ...cloneDeep(item),
id: new Date().getTime(), id: new Date().getTime(),
}; };
const isExistPre = data.value.filter( const isExistPre = list.value.filter(
(current) => current.beforeStepScript && current.processorType === RequestConditionProcessor.REQUEST_SCRIPT (current) => current.beforeStepScript && current.processorType === RequestConditionProcessor.REQUEST_SCRIPT
).length; ).length;
const isExistPost = data.value.filter( const isExistPost = list.value.filter(
(current) => !current.beforeStepScript && current.processorType === RequestConditionProcessor.REQUEST_SCRIPT (current) => !current.beforeStepScript && current.processorType === RequestConditionProcessor.REQUEST_SCRIPT
).length; ).length;
// //
@ -180,9 +179,9 @@
id: new Date().getTime(), id: new Date().getTime(),
}; };
const copyIndex = data.value.findIndex((e: ExecuteConditionProcessor) => e.id === item.id); const copyIndex = list.value.findIndex((e: ExecuteConditionProcessor) => e.id === item.id);
if (copyIndex > -1) { if (copyIndex > -1) {
data.value.splice(copyIndex, 0, copyItem); list.value.splice(copyIndex, 0, copyItem);
activeItem.value = copyItem; activeItem.value = copyItem;
emit('activeChange', activeItem.value); emit('activeChange', activeItem.value);
} }
@ -193,9 +192,9 @@
* @param item 列表项 * @param item 列表项
*/ */
function deleteListItem(item: ExecuteConditionProcessor) { function deleteListItem(item: ExecuteConditionProcessor) {
data.value = data.value.filter((precondition) => precondition.id !== item.id); list.value = list.value.filter((precondition) => precondition.id !== item.id);
if (activeItem.value.id === item.id) { if (activeItem.value.id === item.id) {
[activeItem.value] = data.value; [activeItem.value] = list.value;
} }
emit('activeChange', activeItem.value); emit('activeChange', activeItem.value);
} }

View File

@ -1,12 +1,12 @@
<template> <template>
<div> <div>
<a-select <a-select
v-model:model-value="currentEnv" v-model:model-value="innerCurrentEnv"
:options="envOptions" :options="envOptions"
class="!w-[200px] pl-0 pr-[8px]" class="!w-[200px] pl-0 pr-[8px]"
:loading="envLoading" :loading="envLoading"
allow-search allow-search
@change="initEnvironment" @change="(val) => initEnvironment(val as string)"
@popup-visible-change="popupVisibleChange" @popup-visible-change="popupVisibleChange"
> >
<template #prefix> <template #prefix>
@ -28,23 +28,36 @@
import { EnvConfig } from '@/models/projectManagement/environmental'; import { EnvConfig } from '@/models/projectManagement/environmental';
import { ProjectManagementRouteEnum } from '@/enums/routeEnum'; import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
const props = withDefaults(
defineProps<{
currentEnv?: string;
setDefaultEnv?: boolean; // currentEnvcurrentEnv
}>(),
{
setDefaultEnv: true,
}
);
const emit = defineEmits<{
(e: 'update:currentEnv', val: string): void;
}>();
const appStore = useAppStore(); const appStore = useAppStore();
const { openNewPage } = useOpenNewPage(); const { openNewPage } = useOpenNewPage();
const currentEnv = defineModel<string>('currentEnv', { default: '' }); const innerCurrentEnv = ref(props.currentEnv || '');
const currentEnvConfig = defineModel<EnvConfig>('currentEnvConfig', { const currentEnvConfig = defineModel<EnvConfig>('currentEnvConfig', {
default: {}, default: {},
}); });
const envLoading = ref(false); const envLoading = ref(false);
const envOptions = ref<SelectOptionData[]>([]); const envOptions = ref<SelectOptionData[]>([]);
async function initEnvironment() { async function initEnvironment(id: string) {
try { try {
const res = await getEnvironment(currentEnv.value); const res = await getEnvironment(id);
currentEnvConfig.value = { currentEnvConfig.value = {
...res, ...res,
id: currentEnv.value, id,
name: envOptions.value.find((item) => item.value === currentEnv.value)?.label || '', name: envOptions.value.find((item) => item.value === id)?.label || '',
}; };
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -60,12 +73,10 @@
label: item.name, label: item.name,
value: item.id, value: item.id,
})); }));
currentEnv.value = currentEnv.value.length ? currentEnv.value : res[0]?.id; if (!innerCurrentEnv.value) {
nextTick(() => { innerCurrentEnv.value = res[0]?.id;
if (currentEnv.value) { }
initEnvironment(); initEnvironment(innerCurrentEnv.value || res[0]?.id);
}
});
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
@ -85,14 +96,24 @@
} }
watch( watch(
() => currentEnv.value, () => props.currentEnv,
(val) => { (val) => {
if (!val) { if (!val) {
currentEnv.value = (envOptions.value[0]?.value as string) || ''; if (props.setDefaultEnv) {
innerCurrentEnv.value = (envOptions.value[0]?.value as string) || '';
initEnvironment((envOptions.value[0]?.value as string) || '');
}
} else {
innerCurrentEnv.value = val;
initEnvironment(val);
} }
nextTick(() => { }
initEnvironment(); );
});
watch(
() => innerCurrentEnv.value,
(val) => {
emit('update:currentEnv', val);
} }
); );

View File

@ -838,10 +838,12 @@
} }
return item; return item;
}); });
const lastTwoIsSame =
arr.length >= 2 && filterKeyValParams([arr[arr.length - 2]], arr[arr.length - 1]).lastDataIsDefault;
if ( if (
!filterKeyValParams(arr, props.defaultParamItem).lastDataIsDefault && !filterKeyValParams(arr, props.defaultParamItem).lastDataIsDefault &&
!props.isTreeTable && !props.isTreeTable &&
!filterKeyValParams([arr[arr.length - 2], arr[arr.length - 1]], arr[arr.length - 1]).lastDataIsDefault // !lastTwoIsSame //
) { ) {
addTableLine(arr.length - 1, false, true); addTableLine(arr.length - 1, false, true);
} }

View File

@ -34,7 +34,7 @@
title: 'apiTestDebug.status', title: 'apiTestDebug.status',
dataIndex: 'pass', dataIndex: 'pass',
slotName: 'status', slotName: 'status',
width: 100, width: 120,
}, },
{ {
title: 'apiTestDebug.reason', title: 'apiTestDebug.reason',

View File

@ -277,22 +277,24 @@
const { url, headers, queryParameters } = parseCurlScript(curlCode.value); const { url, headers, queryParameters } = parseCurlScript(curlCode.value);
addDebugTab({ addDebugTab({
url, url,
headers: headers?.map((e) => ({ headers:
contentType: RequestContentTypeEnum.TEXT, headers?.map((e) => ({
description: '', contentType: RequestContentTypeEnum.TEXT,
enable: true, description: '',
...e, enable: true,
})), ...e,
query: queryParameters?.map((e) => ({ })) || [],
paramType: RequestParamsType.STRING, query:
description: '', queryParameters?.map((e) => ({
required: false, paramType: RequestParamsType.STRING,
maxLength: undefined, description: '',
minLength: undefined, required: false,
encode: false, maxLength: undefined,
enable: true, minLength: undefined,
...e, encode: false,
})), enable: true,
...e,
})) || [],
}); });
curlCode.value = ''; curlCode.value = '';
importDrawerVisible.value = false; importDrawerVisible.value = false;

View File

@ -314,11 +314,12 @@
{{ t('apiTestManagement.timeTaskList') }} {{ t('apiTestManagement.timeTaskList') }}
<a-input-search <a-input-search
v-model:model-value="keyword" v-model:model-value="keyword"
:placeholder="t('apiTestManagement.searchPlaceholder')" :placeholder="t('apiTestManagement.searchTaskPlaceholder')"
allow-clear allow-clear
class="mr-[8px] w-[240px]" class="mr-[8px] w-[240px]"
@search="loadTaskList" @search="loadTaskList"
@press-enter="loadTaskList" @press-enter="loadTaskList"
@clear="loadTaskList"
/> />
</div> </div>
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent"> <ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
@ -437,7 +438,7 @@
{ label: t('apiTestManagement.timeTaskTwelveHour'), value: '0 0 0/12 * * ?' }, { label: t('apiTestManagement.timeTaskTwelveHour'), value: '0 0 0/12 * * ?' },
{ label: t('apiTestManagement.timeTaskDay'), value: '0 0 0 * * ?' }, { label: t('apiTestManagement.timeTaskDay'), value: '0 0 0 * * ?' },
]; ];
const cronValue = ref('0 0 0/1 * * ? '); const cronValue = ref('0 0 0/1 * * ?');
const importLoading = ref(false); const importLoading = ref(false);
const taskDrawerVisible = ref(false); const taskDrawerVisible = ref(false);

View File

@ -5,7 +5,7 @@
<a-form-item <a-form-item
field="name" field="name"
:label="t('apiTestManagement.apiName')" :label="t('apiTestManagement.apiName')"
class="mb-[16px] w-[80%]" class="mb-[16px] w-[60%]"
:rules="[{ required: true, message: t('apiTestManagement.apiNameRequired') }]" :rules="[{ required: true, message: t('apiTestManagement.apiNameRequired') }]"
> >
<a-input <a-input
@ -16,7 +16,7 @@
@change="handleActiveApiChange" @change="handleActiveApiChange"
/> />
</a-form-item> </a-form-item>
<a-form-item :label="t('common.desc')" class="mb-[16px] w-[80%]"> <a-form-item :label="t('common.desc')" class="mb-[16px] w-[60%]">
<a-textarea v-model:model-value="requestVModel.description" :max-length="1000" @change="handleActiveApiChange" /> <a-textarea v-model:model-value="requestVModel.description" :max-length="1000" @change="handleActiveApiChange" />
</a-form-item> </a-form-item>
<a-form-item :label="t('apiTestManagement.belongModule')" class="mb-[16px] w-[436px]"> <a-form-item :label="t('apiTestManagement.belongModule')" class="mb-[16px] w-[436px]">

View File

@ -28,7 +28,11 @@
</a-tooltip> </a-tooltip>
</template> </template>
</MsEditableTab> </MsEditableTab>
<environmentSelect ref="environmentSelectRef" v-model:current-env="activeApiTab.environmentId" /> <environmentSelect
ref="environmentSelectRef"
v-model:current-env="activeApiTab.environmentId"
:set-default-env="false"
/>
</div> </div>
<api <api
v-show="(activeApiTab.id === 'all' && currentTab === 'api') || activeApiTab.type === 'api'" v-show="(activeApiTab.id === 'all' && currentTab === 'api') || activeApiTab.type === 'api'"
@ -220,14 +224,14 @@
); );
// tab // tab
function changeActiveApiTabTofirst() { function changeActiveApiTabToFirst() {
activeApiTab.value = apiTabs.value[0] as RequestParam; activeApiTab.value = apiTabs.value[0] as RequestParam;
} }
// //
function currentTabChange(val: any) { function currentTabChange(val: any) {
apiTabs.value[0].label = val === 'api' ? t('apiTestManagement.allApi') : t('case.allCase'); apiTabs.value[0].label = val === 'api' ? t('apiTestManagement.allApi') : t('case.allCase');
changeActiveApiTabTofirst(); changeActiveApiTabToFirst();
} }
watch( watch(
@ -327,7 +331,7 @@
refreshApiTable, refreshApiTable,
handleApiUpdateFromModuleTree, handleApiUpdateFromModuleTree,
handleDeleteApiFromModuleTree, handleDeleteApiFromModuleTree,
changeActiveApiTabTofirst, changeActiveApiTabToFirst,
}); });
</script> </script>

View File

@ -107,7 +107,7 @@
function handleNodeSelect(keys: string[], _offspringIds: string[]) { function handleNodeSelect(keys: string[], _offspringIds: string[]) {
[activeModule.value] = keys; [activeModule.value] = keys;
offspringIds.value = _offspringIds; offspringIds.value = _offspringIds;
managementRef.value?.changeActiveApiTabTofirst(); managementRef.value?.changeActiveApiTabToFirst();
} }
function handleApiNodeClick(node: ModuleTreeNode) { function handleApiNodeClick(node: ModuleTreeNode) {

View File

@ -34,6 +34,7 @@ export default {
'apiTestManagement.closeOther': 'Close other tabs', 'apiTestManagement.closeOther': 'Close other tabs',
'apiTestManagement.showSubdirectory': 'Show subdirectory use case', 'apiTestManagement.showSubdirectory': 'Show subdirectory use case',
'apiTestManagement.searchPlaceholder': 'Enter ID/name/api path search', 'apiTestManagement.searchPlaceholder': 'Enter ID/name/api path search',
'apiTestManagement.searchTaskPlaceholder': 'Enter resource Id/name search',
'apiTestManagement.apiName': 'Api name', 'apiTestManagement.apiName': 'Api name',
'apiTestManagement.apiType': 'Api type', 'apiTestManagement.apiType': 'Api type',
'apiTestManagement.apiStatus': 'Status', 'apiTestManagement.apiStatus': 'Status',

View File

@ -33,6 +33,7 @@ export default {
'apiTestManagement.closeOther': '关闭其他tab', 'apiTestManagement.closeOther': '关闭其他tab',
'apiTestManagement.showSubdirectory': '显示子目录用例', 'apiTestManagement.showSubdirectory': '显示子目录用例',
'apiTestManagement.searchPlaceholder': '输入 ID/名称/api路径搜索', 'apiTestManagement.searchPlaceholder': '输入 ID/名称/api路径搜索',
'apiTestManagement.searchTaskPlaceholder': '输入资源ID/名称搜索',
'apiTestManagement.apiName': '接口名称', 'apiTestManagement.apiName': '接口名称',
'apiTestManagement.apiType': '请求类型', 'apiTestManagement.apiType': '请求类型',
'apiTestManagement.apiStatus': '状态', 'apiTestManagement.apiStatus': '状态',

View File

@ -1209,6 +1209,7 @@
requestVModel.value = cloneDeep({ requestVModel.value = cloneDeep({
...defaultApiParams, ...defaultApiParams,
...props.request, ...props.request,
name: props.step?.name || props.request.name,
url: props.request.path, // path url: props.request.path, // path
activeTab: contentTabList.value[0].value, activeTab: contentTabList.value[0].value,
responseActiveTab: ResponseComposition.BODY, responseActiveTab: ResponseComposition.BODY,

View File

@ -5,7 +5,7 @@
:page-size="1" :page-size="1"
:total="props.loopTotal" :total="props.loopTotal"
:show-jumper="props.loopTotal > 5" :show-jumper="props.loopTotal > 5"
:base-size="Infinity" :base-size="0"
show-total show-total
size="mini" size="mini"
class="loop-pagination" class="loop-pagination"

View File

@ -10,11 +10,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { WorkbenchRouteEnum } from '@/enums/routeEnum'; import { getFirstRouteNameByPermission } from '@/utils/permission';
const router = useRouter(); const router = useRouter();
const back = () => { const back = () => {
// warning Go to the node that has the permission router.push({ name: getFirstRouteNameByPermission(router.getRoutes()) });
router.push({ name: WorkbenchRouteEnum.WORKBENCH });
}; };
</script> </script>

View File

@ -270,8 +270,8 @@
import { convertToFileByBug } from './utils'; import { convertToFileByBug } from './utils';
defineOptions({ name: 'BugEditPage' }); defineOptions({ name: 'BugEditPage' });
const { setState } = useLeaveUnSaveTip(); const { setIsSave } = useLeaveUnSaveTip();
setState(false); setIsSave(false);
const { t } = useI18n(); const { t } = useI18n();
interface TemplateOption { interface TemplateOption {
@ -609,7 +609,7 @@
// //
const res = await createOrUpdateBug({ request: tmpObj, fileList: localFiles as unknown as File[] }); const res = await createOrUpdateBug({ request: tmpObj, fileList: localFiles as unknown as File[] });
if (isEdit.value) { if (isEdit.value) {
setState(true); setIsSave(true);
Message.success(t('common.updateSuccess')); Message.success(t('common.updateSuccess'));
router.push({ router.push({
name: BugManagementRouteEnum.BUG_MANAGEMENT_INDEX, name: BugManagementRouteEnum.BUG_MANAGEMENT_INDEX,
@ -617,7 +617,7 @@
} else { } else {
Message.success(t('common.createSuccess')); Message.success(t('common.createSuccess'));
if (isContinue) { if (isContinue) {
setState(false); setIsSave(false);
// //
const { templateId } = form.value; const { templateId } = form.value;
// //
@ -632,7 +632,7 @@
// //
fileList.value = []; fileList.value = [];
} else { } else {
setState(true); setIsSave(true);
// //
if (getIsVisited()) { if (getIsVisited()) {
router.push({ router.push({
@ -831,4 +831,4 @@
font-size: 14px; font-size: 14px;
color: var(--color-text-4); color: var(--color-text-4);
} }
</style> </style>

View File

@ -49,12 +49,12 @@
import Message from '@arco-design/web-vue/es/message'; import Message from '@arco-design/web-vue/es/message';
const { setState } = useLeaveUnSaveTip(); const { setIsSave } = useLeaveUnSaveTip();
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
setState(false); setIsSave(false);
const featureCaseStore = useFeatureCaseStore(); const featureCaseStore = useFeatureCaseStore();
@ -87,7 +87,7 @@
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE, name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE,
query: { orgId: route.query.orgId, pId: route.query.pId }, query: { orgId: route.query.orgId, pId: route.query.pId },
}); });
setState(true); setIsSave(true);
// //
} else { } else {
// //
@ -104,7 +104,7 @@
isShowTip.value = !getIsVisited(); isShowTip.value = !getIsVisited();
if (isReview) { if (isReview) {
Message.success(t('caseManagement.featureCase.createAndLinkSuccess')); Message.success(t('caseManagement.featureCase.createAndLinkSuccess'));
setState(true); setIsSave(true);
router.back(); router.back();
return; return;
} }
@ -126,7 +126,7 @@
}, },
}); });
} }
setState(true); setIsSave(true);
} }
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console

View File

@ -430,7 +430,6 @@
async function getFetch() { async function getFetch() {
if (!hasAnyPermission(['FUNCTIONAL_CASE:READ', 'FUNCTIONAL_CASE:READ+UPDATE', 'FUNCTIONAL_CASE:READ+DELETE'])) { if (!hasAnyPermission(['FUNCTIONAL_CASE:READ', 'FUNCTIONAL_CASE:READ+UPDATE', 'FUNCTIONAL_CASE:READ+DELETE'])) {
Message.error(t('common.noPermission'));
return; return;
} }
if (showType.value === 'link') { if (showType.value === 'link') {

View File

@ -1,5 +1,5 @@
<template> <template>
<MsCard :min-width="1100" simple has-breadcrumb hide-footer no-content-padding hide-divider show-full-screen> <MsCard :min-width="1100" has-breadcrumb hide-footer no-content-padding hide-divider show-full-screen>
<template #headerLeft> <template #headerLeft>
<a-tooltip :content="reviewDetail.name"> <a-tooltip :content="reviewDetail.name">
<div class="one-line-text mr-[8px] max-w-[260px] font-medium text-[var(--color-text-000)]"> <div class="one-line-text mr-[8px] max-w-[260px] font-medium text-[var(--color-text-000)]">

View File

@ -147,7 +147,11 @@
</template> </template>
<template v-if="keyword.trim() === ''" #empty> <template v-if="keyword.trim() === ''" #empty>
<div class="flex w-full items-center justify-center p-[8px] text-[var(--color-text-4)]"> <div class="flex w-full items-center justify-center p-[8px] text-[var(--color-text-4)]">
{{ t('caseManagement.caseReview.tableNoData') }} {{
hasAllPermission(['CASE_REVIEW:READ+ADD'])
? t('caseManagement.caseReview.tableNoData')
: t('caseManagement.caseReview.tableNoDataNoPermission')
}}
<MsButton v-permission="['CASE_REVIEW:READ+ADD']" class="ml-[8px]" @click="() => emit('goCreate')"> <MsButton v-permission="['CASE_REVIEW:READ+ADD']" class="ml-[8px]" @click="() => emit('goCreate')">
{{ t('caseManagement.caseReview.create') }} {{ t('caseManagement.caseReview.create') }}
</MsButton> </MsButton>
@ -214,7 +218,7 @@
import useTableStore from '@/hooks/useTableStore'; import useTableStore from '@/hooks/useTableStore';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
import { hasAnyPermission } from '@/utils/permission'; import { hasAllPermission, hasAnyPermission } from '@/utils/permission';
import { import {
ReviewDetailReviewersItem, ReviewDetailReviewersItem,

View File

@ -6,6 +6,7 @@ export default {
'caseManagement.caseReview.list.searchPlaceholder': 'Search by ID, name, or tag', 'caseManagement.caseReview.list.searchPlaceholder': 'Search by ID, name, or tag',
'caseManagement.caseReview.archive': 'Archive', 'caseManagement.caseReview.archive': 'Archive',
'caseManagement.caseReview.tableNoData': 'No data yet, please', 'caseManagement.caseReview.tableNoData': 'No data yet, please',
'caseManagement.caseReview.tableNoDataNoPermission': 'No data yet',
'caseManagement.caseReview.name': 'Review name', 'caseManagement.caseReview.name': 'Review name',
'caseManagement.caseReview.creator': 'Creator', 'caseManagement.caseReview.creator': 'Creator',
'caseManagement.caseReview.reviewer': 'Reviewer', 'caseManagement.caseReview.reviewer': 'Reviewer',

View File

@ -6,6 +6,7 @@ export default {
'caseManagement.caseReview.list.searchPlaceholder': '通过ID、名称或标签搜索', 'caseManagement.caseReview.list.searchPlaceholder': '通过ID、名称或标签搜索',
'caseManagement.caseReview.archive': '归档', 'caseManagement.caseReview.archive': '归档',
'caseManagement.caseReview.tableNoData': '暂无数据,请', 'caseManagement.caseReview.tableNoData': '暂无数据,请',
'caseManagement.caseReview.tableNoDataNoPermission': '暂无数据',
'caseManagement.caseReview.name': '评审名称', 'caseManagement.caseReview.name': '评审名称',
'caseManagement.caseReview.creator': '创建人', 'caseManagement.caseReview.creator': '创建人',
'caseManagement.caseReview.reviewer': '评审人', 'caseManagement.caseReview.reviewer': '评审人',

View File

@ -33,7 +33,11 @@
v-model="userInfo.username" v-model="userInfo.username"
:max-length="64" :max-length="64"
size="large" size="large"
:placeholder="t('login.form.userName.placeholder')" :placeholder="
userInfo.authenticate !== 'LOCAL'
? t('login.form.userName.placeholderOther')
: t('login.form.userName.placeholder')
"
/> />
</a-form-item> </a-form-item>
<a-form-item <a-form-item
@ -91,6 +95,7 @@
import useLoading from '@/hooks/useLoading'; import useLoading from '@/hooks/useLoading';
import { NO_PROJECT_ROUTE_NAME, NO_RESOURCE_ROUTE_NAME } from '@/router/constants'; import { NO_PROJECT_ROUTE_NAME, NO_RESOURCE_ROUTE_NAME } from '@/router/constants';
import { useAppStore, useUserStore } from '@/store'; import { useAppStore, useUserStore } from '@/store';
import useLicenseStore from '@/store/modules/setting/license';
import { encrypted } from '@/utils'; import { encrypted } from '@/utils';
import { setLoginExpires } from '@/utils/auth'; import { setLoginExpires } from '@/utils/auth';
import { getFirstRouteNameByPermission, routerNameHasPermission } from '@/utils/permission'; import { getFirstRouteNameByPermission, routerNameHasPermission } from '@/utils/permission';
@ -103,6 +108,7 @@
const { t } = useI18n(); const { t } = useI18n();
const userStore = useUserStore(); const userStore = useUserStore();
const appStore = useAppStore(); const appStore = useAppStore();
const licenseStore = useLicenseStore();
const props = defineProps<{ const props = defineProps<{
isPreview?: boolean; isPreview?: boolean;
@ -181,7 +187,7 @@
![NO_RESOURCE_ROUTE_NAME, NO_PROJECT_ROUTE_NAME].includes(redirect as string) && ![NO_RESOURCE_ROUTE_NAME, NO_PROJECT_ROUTE_NAME].includes(redirect as string) &&
routerNameHasPermission(redirect as string, router.getRoutes()); routerNameHasPermission(redirect as string, router.getRoutes());
const currentRouteName = getFirstRouteNameByPermission(router.getRoutes()); const currentRouteName = getFirstRouteNameByPermission(router.getRoutes());
const res = await getProjectInfo(appStore.currentProjectId); const [res] = await Promise.all([getProjectInfo(appStore.currentProjectId), licenseStore.getValidateLicense()]); // license license
if (!res || res.deleted) { if (!res || res.deleted) {
router.push({ router.push({
name: NO_PROJECT_ROUTE_NAME, name: NO_PROJECT_ROUTE_NAME,

View File

@ -4,8 +4,9 @@ export default {
'login.form.password.errMsg': 'Password cannot be empty', 'login.form.password.errMsg': 'Password cannot be empty',
'login.form.login.errMsg': 'Login error, refresh and try again', 'login.form.login.errMsg': 'Login error, refresh and try again',
'login.form.login.success': 'welcome to use', 'login.form.login.success': 'welcome to use',
'login.form.userName.placeholder': 'Username', 'login.form.userName.placeholder': 'Please enter your email',
'login.form.password.placeholder': 'Password', 'login.form.userName.placeholderOther': 'Please enter your account',
'login.form.password.placeholder': 'Please enter your password',
'login.form.rememberPassword': 'Remember password', 'login.form.rememberPassword': 'Remember password',
'login.form.forgetPassword': 'Forgot password', 'login.form.forgetPassword': 'Forgot password',
'login.form.login': 'login', 'login.form.login': 'login',

View File

@ -5,7 +5,8 @@ export default {
'login.form.login.errMsg': '登录出错,请刷新重试', 'login.form.login.errMsg': '登录出错,请刷新重试',
'login.form.login.success': '欢迎使用', 'login.form.login.success': '欢迎使用',
'login.form.userName.placeholder': '请输入邮箱登录', 'login.form.userName.placeholder': '请输入邮箱登录',
'login.form.password.placeholder': '密码', 'login.form.userName.placeholderOther': '请输入账号登录',
'login.form.password.placeholder': '请输入密码',
'login.form.rememberPassword': '记住密码', 'login.form.rememberPassword': '记住密码',
'login.form.forgetPassword': '忘记密码', 'login.form.forgetPassword': '忘记密码',
'login.form.login': '登录', 'login.form.login': '登录',

View File

@ -20,7 +20,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import AllPrams from './allParams/index.vue';
import RequestHeader from './requestHeader/index.vue'; import RequestHeader from './requestHeader/index.vue';
import { updateOrAddGlobalParam } from '@/api/modules/project-management/envManagement'; import { updateOrAddGlobalParam } from '@/api/modules/project-management/envManagement';
@ -38,8 +37,8 @@
const headerParams = ref<EnvConfigItem[]>([]); const headerParams = ref<EnvConfigItem[]>([]);
const GlobalVariable = ref<EnvConfigItem[]>([]); const GlobalVariable = ref<EnvConfigItem[]>([]);
const { t } = useI18n(); const { t } = useI18n();
const { setState } = useLeaveUnSaveTip(); const { setIsSave } = useLeaveUnSaveTip();
setState(true); setIsSave(true);
const canSave = ref(false); const canSave = ref(false);
const loading = ref(false); const loading = ref(false);
@ -64,7 +63,7 @@
function change() { function change() {
canSave.value = true; canSave.value = true;
setState(false); setIsSave(false);
} }
const handleSave = async () => { const handleSave = async () => {
try { try {
@ -85,7 +84,7 @@
}, },
}; };
await updateOrAddGlobalParam(params); await updateOrAddGlobalParam(params);
setState(true); setIsSave(true);
Message.success(t('common.saveSuccess')); Message.success(t('common.saveSuccess'));
canSave.value = false; canSave.value = false;
initEnvDetail(); initEnvDetail();

View File

@ -101,8 +101,8 @@
import { defaultHeaderParamsItem } from '@/views/api-test/components/config'; import { defaultHeaderParamsItem } from '@/views/api-test/components/config';
import { filterKeyValParams } from '@/views/api-test/components/utils'; import { filterKeyValParams } from '@/views/api-test/components/utils';
const { setState } = useLeaveUnSaveTip(); const { setIsSave } = useLeaveUnSaveTip();
setState(false); setIsSave(false);
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'ok', envId: string | undefined): void; (e: 'ok', envId: string | undefined): void;
(e: 'resetEnv'): void; (e: 'resetEnv'): void;
@ -231,7 +231,7 @@
loading.value = true; loading.value = true;
store.currentEnvDetailInfo.mock = true; store.currentEnvDetailInfo.mock = true;
await updateOrAddEnv({ fileList: [], request: getParameters() }); await updateOrAddEnv({ fileList: [], request: getParameters() });
setState(true); setIsSave(true);
Message.success(store.currentEnvDetailInfo.id ? t('common.updateSuccess') : t('common.saveSuccess')); Message.success(store.currentEnvDetailInfo.id ? t('common.updateSuccess') : t('common.saveSuccess'));
emit('ok', store.currentEnvDetailInfo.id); emit('ok', store.currentEnvDetailInfo.id);
@ -331,4 +331,4 @@
gap: 16px; gap: 16px;
} }
} }
</style> </style>

View File

@ -284,7 +284,7 @@
NEW_ENV_PARAM, NEW_ENV_PARAM,
} from '@/store/modules/setting/useProjectEnvStore'; } from '@/store/modules/setting/useProjectEnvStore';
import { downloadByteFile } from '@/utils'; import { downloadByteFile } from '@/utils';
import { hasAllPermission, hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
import { EnvListItem, PopVisible } from '@/models/projectManagement/environmental'; import { EnvListItem, PopVisible } from '@/models/projectManagement/environmental';
import { EnvAuthScopeEnum, EnvAuthTypeEnum } from '@/enums/envEnum'; import { EnvAuthScopeEnum, EnvAuthTypeEnum } from '@/enums/envEnum';
@ -293,8 +293,8 @@
const { openModal } = useModal(); const { openModal } = useModal();
const { setState } = useLeaveUnSaveTip(); const { setIsSave } = useLeaveUnSaveTip();
setState(false); setIsSave(false);
const { t } = useI18n(); const { t } = useI18n();
const store = useProjectEnvStore(); const store = useProjectEnvStore();

View File

@ -35,7 +35,7 @@
<a-input <a-input
v-else v-else
v-model:model-value="form.field" v-model:model-value="form.field"
:max-length="props.fieldConfig?.maxLength || 50" :max-length="props.fieldConfig?.maxLength || 255"
:placeholder="props.fieldConfig?.placeholder || t('project.fileManagement.namePlaceholder')" :placeholder="props.fieldConfig?.placeholder || t('project.fileManagement.namePlaceholder')"
class="w-[245px]" class="w-[245px]"
@press-enter="beforeConfirm(undefined)" @press-enter="beforeConfirm(undefined)"

View File

@ -670,7 +670,6 @@
} }
if (!hasAuth) { if (!hasAuth) {
Message.error(t('common.noPermission'));
return; return;
} }
@ -773,7 +772,6 @@
} }
if (!hasAuth) { if (!hasAuth) {
Message.error(t('common.noPermission'));
return; return;
} }
await postUpdateMenu( await postUpdateMenu(

View File

@ -267,9 +267,9 @@
const currentOrgId = computed(() => appStore.currentOrgId); const currentOrgId = computed(() => appStore.currentOrgId);
const currentProjectId = computed(() => appStore.currentProjectId); const currentProjectId = computed(() => appStore.currentProjectId);
const { setState } = useLeaveUnSaveTip(); const { setIsSave } = useLeaveUnSaveTip();
setState(false); setIsSave(false);
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@ -405,7 +405,7 @@
router.push({ name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT, query: route.query }); router.push({ name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_TEMPLATE_MANAGEMENT, query: route.query });
} }
setState(true); setIsSave(true);
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@ -834,7 +834,6 @@
background: var(--color-text-n9); background: var(--color-text-n9);
} }
} }
:deep(.arco-form-item-layout-vertical > .arco-form-item-label-col) { :deep(.arco-form-item-layout-vertical > .arco-form-item-label-col) {
overflow-wrap: break-word; overflow-wrap: break-word;
} }

View File

@ -150,7 +150,7 @@
import MsDrawer from '@/components/pure/ms-drawer/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import type { FormItemType } from '@/components/pure/ms-form-create/types'; import type { FormItemType } from '@/components/pure/ms-form-create/types';
import MsBatchForm from '@/components/business/ms-batch-form/index.vue'; import MsBatchForm from '@/components/business/ms-batch-form/index.vue';
import type { FormItemModel, MsBatchFormInstance } from '@/components/business/ms-batch-form/types'; import type { FormItemModel } from '@/components/business/ms-batch-form/types';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
@ -252,7 +252,7 @@
}, },
]); ]);
const optionsModels: Ref<FormItemModel[]> = ref([]); const optionsModels: Ref<FormItemModel[]> = ref([]);
const batchFormRef = ref<MsBatchFormInstance | null>(null); const batchFormRef = ref<InstanceType<typeof MsBatchForm>>();
const resetForm = () => { const resetForm = () => {
fieldForm.value = { ...initFieldForm }; fieldForm.value = { ...initFieldForm };

View File

@ -5,6 +5,15 @@
<a-button v-permission="['SYSTEM_PARAMETER_SETTING_AUTH:READ+ADD']" type="primary" @click="createAuth"> <a-button v-permission="['SYSTEM_PARAMETER_SETTING_AUTH:READ+ADD']" type="primary" @click="createAuth">
{{ t('system.config.auth.add') }} {{ t('system.config.auth.add') }}
</a-button> </a-button>
<a-input-search
v-model:model-value="keyword"
:placeholder="t('system.config.auth.searchTip')"
class="w-[230px]"
allow-clear
@search="searchAuth"
@press-enter="searchAuth"
@clear="searchAuth"
/>
</div> </div>
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent"> <ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
<template #name="{ record }"> <template #name="{ record }">
@ -684,7 +693,7 @@
}, },
]; ];
const tableStore = useTableStore(); const tableStore = useTableStore();
const { propsRes, propsEvent, loadList } = useTable(getAuthList, { const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(getAuthList, {
tableKey: TableKeyEnum.SYSTEM_AUTH, tableKey: TableKeyEnum.SYSTEM_AUTH,
columns, columns,
scroll: { y: 'auto' }, scroll: { y: 'auto' },
@ -696,6 +705,15 @@
loadList(); loadList();
}); });
const keyword = ref('');
function searchAuth() {
setLoadListParams({
keyword: keyword.value,
});
loadList();
}
/** /**
* 启用认证源 * 启用认证源
*/ */

View File

@ -50,10 +50,10 @@
<div :class="['config-preview', currentLocale === 'en-US' ? 'config-preview--en' : '']"> <div :class="['config-preview', currentLocale === 'en-US' ? 'config-preview--en' : '']">
<div ref="loginPageFullRef" class="login-preview"> <div ref="loginPageFullRef" class="login-preview">
<div :class="['config-preview-head', isLoginPageFullscreen ? 'full-preview-head' : '']"> <div :class="['config-preview-head', isLoginPageFullscreen ? 'full-preview-head' : '']">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between overflow-hidden">
<img v-if="pageConfig.icon[0]?.url" :src="pageConfig.icon[0].url" class="h-[18px] w-[18px]" /> <img v-if="pageConfig.icon[0]?.url" :src="pageConfig.icon[0].url" class="h-[18px] w-[18px]" />
<svg-icon v-else name="logo" class="h-[18px] w-[18px]"></svg-icon> <svg-icon v-else name="logo" class="h-[18px] w-[18px]"></svg-icon>
<div class="ml-[4px] text-[10px]">{{ pageConfig.title }}</div> <div class="one-line-text ml-[4px] text-[10px]">{{ pageConfig.title }}</div>
</div> </div>
<div <div
class="w-[96px] cursor-pointer text-right !text-[var(--color-text-4)]" class="w-[96px] cursor-pointer text-right !text-[var(--color-text-4)]"

View File

@ -96,6 +96,7 @@ export default {
'system.config.page.unsave': 'Unsaved', 'system.config.page.unsave': 'Unsaved',
'system.config.page.saveSuccess': 'Saved successfully', 'system.config.page.saveSuccess': 'Saved successfully',
'system.config.auth.add': 'Add authentication', 'system.config.auth.add': 'Add authentication',
'system.config.auth.searchTip': 'Search by name',
'system.config.auth.enable': 'Enable', 'system.config.auth.enable': 'Enable',
'system.config.auth.enableSuccess': 'Enabled successfully', 'system.config.auth.enableSuccess': 'Enabled successfully',
'system.config.auth.enableTipTitle': 'Enable authentication {name}', 'system.config.auth.enableTipTitle': 'Enable authentication {name}',

View File

@ -92,6 +92,7 @@ export default {
'system.config.page.unsave': '未保存', 'system.config.page.unsave': '未保存',
'system.config.page.saveSuccess': '保存成功', 'system.config.page.saveSuccess': '保存成功',
'system.config.auth.add': '添加认证', 'system.config.auth.add': '添加认证',
'system.config.auth.searchTip': '输入名称搜索',
'system.config.auth.enable': '启用', 'system.config.auth.enable': '启用',
'system.config.auth.enableSuccess': '启用成功', 'system.config.auth.enableSuccess': '启用成功',
'system.config.auth.enableTipTitle': '启用认证 {name}', 'system.config.auth.enableTipTitle': '启用认证 {name}',

View File

@ -3,6 +3,9 @@
:loading="loading" :loading="loading"
:title="title" :title="title"
:is-edit="isEdit" :is-edit="isEdit"
:save-text="t('system.resourcePool.add')"
:save-and-continue-text="t('system.resourcePool.addAndContinue')"
:handle-back="handleBack"
has-breadcrumb has-breadcrumb
@save="beforeSave" @save="beforeSave"
@save-and-continue="beforeSave(true)" @save-and-continue="beforeSave(true)"
@ -19,6 +22,7 @@
v-model:model-value="form.name" v-model:model-value="form.name"
:placeholder="t('system.resourcePool.namePlaceholder')" :placeholder="t('system.resourcePool.namePlaceholder')"
:max-length="255" :max-length="255"
@change="() => setIsSave(false)"
></a-input> ></a-input>
</a-form-item> </a-form-item>
<a-form-item :label="t('system.resourcePool.desc')" field="description" class="form-item"> <a-form-item :label="t('system.resourcePool.desc')" field="description" class="form-item">
@ -26,6 +30,7 @@
v-model:model-value="form.description" v-model:model-value="form.description"
:placeholder="t('system.resourcePool.descPlaceholder')" :placeholder="t('system.resourcePool.descPlaceholder')"
:max-length="1000" :max-length="1000"
@change="() => setIsSave(false)"
></a-textarea> ></a-textarea>
</a-form-item> </a-form-item>
<a-form-item :label="t('system.resourcePool.serverUrl')" field="serverUrl" class="form-item"> <a-form-item :label="t('system.resourcePool.serverUrl')" field="serverUrl" class="form-item">
@ -33,10 +38,11 @@
v-model:model-value="form.serverUrl" v-model:model-value="form.serverUrl"
:placeholder="t('system.resourcePool.rootUrlPlaceholder')" :placeholder="t('system.resourcePool.rootUrlPlaceholder')"
:max-length="255" :max-length="255"
@change="() => setIsSave(false)"
></a-input> ></a-input>
</a-form-item> </a-form-item>
<a-form-item :label="t('system.resourcePool.orgRange')" field="orgType" class="form-item"> <a-form-item :label="t('system.resourcePool.orgRange')" field="orgType" class="form-item">
<a-radio-group v-model:model-value="form.orgType"> <a-radio-group v-model:model-value="form.orgType" @change="() => setIsSave(false)">
<a-radio value="allOrg"> <a-radio value="allOrg">
{{ t('system.resourcePool.orgAll') }} {{ t('system.resourcePool.orgAll') }}
<a-tooltip :content="t('system.resourcePool.orgRangeTip')" position="top" mini> <a-tooltip :content="t('system.resourcePool.orgRangeTip')" position="top" mini>
@ -59,6 +65,7 @@
:placeholder="t('system.resourcePool.orgPlaceholder')" :placeholder="t('system.resourcePool.orgPlaceholder')"
multiple multiple
allow-clear allow-clear
@change="() => setIsSave(false)"
> >
<a-option v-for="org of orgOptions" :key="org.id" :value="org.id">{{ org.name }}</a-option> <a-option v-for="org of orgOptions" :key="org.id" :value="org.id">{{ org.name }}</a-option>
</a-select> </a-select>
@ -70,7 +77,7 @@
:rules="[{ required: true, message: t('system.resourcePool.useRequired') }]" :rules="[{ required: true, message: t('system.resourcePool.useRequired') }]"
asterisk-position="end" asterisk-position="end"
> >
<a-checkbox-group v-model:model-value="form.use"> <a-checkbox-group v-model:model-value="form.use" @change="() => setIsSave(false)">
<a-checkbox v-for="use of useList" :key="use.value" :value="use.value">{{ t(use.label) }}</a-checkbox> <a-checkbox v-for="use of useList" :key="use.value" :value="use.value">{{ t(use.label) }}</a-checkbox>
</a-checkbox-group> </a-checkbox-group>
<MsFormItemSub <MsFormItemSub
@ -191,6 +198,7 @@
:default-vals="defaultVals" :default-vals="defaultVals"
:hide-add="!isXpack" :hide-add="!isXpack"
max-height="250px" max-height="250px"
@change="() => setIsSave(false)"
></MsBatchForm> ></MsBatchForm>
<!-- TODO:代码编辑器懒加载 --> <!-- TODO:代码编辑器懒加载 -->
<div v-show="form.addType === 'multiple'"> <div v-show="form.addType === 'multiple'">
@ -200,6 +208,7 @@
height="400px" height="400px"
theme="MS-text" theme="MS-text"
:show-theme-change="false" :show-theme-change="false"
@change="() => setIsSave(false)"
> >
<template #leftTitle> <template #leftTitle>
<a-form-item <a-form-item
@ -228,6 +237,7 @@
v-model:model-value="form.testResourceDTO.ip" v-model:model-value="form.testResourceDTO.ip"
:placeholder="t('system.resourcePool.testResourceDTO.ipPlaceholder')" :placeholder="t('system.resourcePool.testResourceDTO.ipPlaceholder')"
:max-length="255" :max-length="255"
@change="() => setIsSave(false)"
></a-input> ></a-input>
<div class="mt-[4px] text-[12px] leading-[16px] text-[var(--color-text-4)]"> <div class="mt-[4px] text-[12px] leading-[16px] text-[var(--color-text-4)]">
{{ t('system.resourcePool.testResourceDTO.ipSubTip', { ip: '100.0.0.100', domain: 'example.com' }) }} {{ t('system.resourcePool.testResourceDTO.ipSubTip', { ip: '100.0.0.100', domain: 'example.com' }) }}
@ -245,6 +255,7 @@
:placeholder="t('system.resourcePool.testResourceDTO.tokenPlaceholder')" :placeholder="t('system.resourcePool.testResourceDTO.tokenPlaceholder')"
:max-length="1500" :max-length="1500"
autocomplete="new-password" autocomplete="new-password"
@change="() => setIsSave(false)"
/> />
</a-form-item> </a-form-item>
<a-form-item <a-form-item
@ -259,6 +270,7 @@
:placeholder="t('system.resourcePool.testResourceDTO.nameSpacesPlaceholder')" :placeholder="t('system.resourcePool.testResourceDTO.nameSpacesPlaceholder')"
:max-length="255" :max-length="255"
class="mr-[8px] flex-1" class="mr-[8px] flex-1"
@change="() => setIsSave(false)"
></a-input> ></a-input>
<a-tooltip <a-tooltip
:content="t('system.resourcePool.testResourceDTO.downloadRoleYamlTip')" :content="t('system.resourcePool.testResourceDTO.downloadRoleYamlTip')"
@ -284,6 +296,7 @@
:placeholder="t('system.resourcePool.testResourceDTO.deployNamePlaceholder')" :placeholder="t('system.resourcePool.testResourceDTO.deployNamePlaceholder')"
:max-length="255" :max-length="255"
class="mr-[8px] flex-1" class="mr-[8px] flex-1"
@change="() => setIsSave(false)"
></a-input> ></a-input>
<a-tooltip <a-tooltip
:content="t('system.resourcePool.testResourceDTO.downloadDeployYamlTip')" :content="t('system.resourcePool.testResourceDTO.downloadDeployYamlTip')"
@ -314,6 +327,8 @@
:step="1" :step="1"
mode="button" mode="button"
class="w-[160px]" class="w-[160px]"
model-event="input"
@change="() => setIsSave(false)"
></a-input-number> ></a-input-number>
</a-form-item> </a-form-item>
<a-form-item <a-form-item
@ -328,6 +343,8 @@
:step="1" :step="1"
mode="button" mode="button"
class="w-[160px]" class="w-[160px]"
model-event="input"
@change="() => setIsSave(false)"
></a-input-number> ></a-input-number>
</a-form-item> </a-form-item>
</template> </template>
@ -351,19 +368,20 @@
import { computed, onBeforeMount, Ref, ref, watch, watchEffect } from 'vue'; import { computed, onBeforeMount, Ref, ref, watch, watchEffect } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { FormInstance, Message, SelectOptionData } from '@arco-design/web-vue'; import { FormInstance, Message, SelectOptionData } from '@arco-design/web-vue';
import { isEmpty } from 'lodash-es'; import { cloneDeep, isEmpty } from 'lodash-es';
import MsButton from '@/components/pure/ms-button/index.vue'; import MsButton from '@/components/pure/ms-button/index.vue';
import MsCard from '@/components/pure/ms-card/index.vue'; import MsCard from '@/components/pure/ms-card/index.vue';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue'; import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import MsBatchForm from '@/components/business/ms-batch-form/index.vue'; import MsBatchForm from '@/components/business/ms-batch-form/index.vue';
import type { FormItemModel, MsBatchFormInstance } from '@/components/business/ms-batch-form/types'; import type { FormItemModel } from '@/components/business/ms-batch-form/types';
import MsFormItemSub from '@/components/business/ms-form-item-sub/index.vue'; import MsFormItemSub from '@/components/business/ms-form-item-sub/index.vue';
import JobTemplateDrawer from './components/jobTemplateDrawer.vue'; import JobTemplateDrawer from './components/jobTemplateDrawer.vue';
import { getSystemOrgOption } from '@/api/modules/setting/organizationAndProject'; import { getSystemOrgOption } from '@/api/modules/setting/organizationAndProject';
import { addPool, getPoolInfo, updatePoolInfo } from '@/api/modules/setting/resourcePool'; import { addPool, getPoolInfo, updatePoolInfo } from '@/api/modules/setting/resourcePool';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useLeaveUnSaveTip from '@/hooks/useLeaveUnSaveTip';
import useVisit from '@/hooks/useVisit'; import useVisit from '@/hooks/useVisit';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import useLicenseStore from '@/store/modules/setting/license'; import useLicenseStore from '@/store/modules/setting/license';
@ -371,6 +389,7 @@
import { scrollIntoView } from '@/utils/dom'; import { scrollIntoView } from '@/utils/dom';
import type { NodesListItem, UpdateResourcePoolParams } from '@/models/setting/resourcePool'; import type { NodesListItem, UpdateResourcePoolParams } from '@/models/setting/resourcePool';
import { SettingRouteEnum } from '@/enums/routeEnum';
import { getYaml, job, YamlType } from './template'; import { getYaml, job, YamlType } from './template';
@ -380,6 +399,7 @@
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStore(); const appStore = useAppStore();
const { setIsSave } = useLeaveUnSaveTip();
const title = ref(''); const title = ref('');
const loading = ref(false); const loading = ref(false);
@ -390,7 +410,7 @@
description: '', description: '',
serverUrl: '', serverUrl: '',
orgType: 'allOrg', orgType: 'allOrg',
use: ['performance', 'API'], use: ['API'],
type: 'Node', type: 'Node',
addType: 'single', addType: 'single',
testResourceDTO: { testResourceDTO: {
@ -409,7 +429,7 @@
orgIds: [] as string[], orgIds: [] as string[],
}, },
}; };
const form = ref({ ...defaultForm }); const form = ref({ ...cloneDeep(defaultForm) });
const formRef = ref<FormInstance | null>(null); const formRef = ref<FormInstance | null>(null);
const orgOptions = ref<SelectOptionData>([]); const orgOptions = ref<SelectOptionData>([]);
// TODO: // TODO:
@ -427,7 +447,7 @@
// value: 'UI', // value: 'UI',
// }, // },
]); ]);
const defaultGrid = 'http://selenium-hub:4444'; // const defaultGrid = 'http://selenium-hub:4444';
const maxConcurrentNumber = computed(() => { const maxConcurrentNumber = computed(() => {
if (isXpack.value) { if (isXpack.value) {
return 9999999; return 9999999;
@ -482,10 +502,10 @@
} }
}); });
const defaultHeap = '-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m'; // const defaultHeap = '-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m';
function fillHeapByDefault() { // function fillHeapByDefault() {
form.value.testResourceDTO.loadTestHeap = defaultHeap; // form.value.testResourceDTO.loadTestHeap = defaultHeap;
} // }
const visitedKey = 'changeAddResourceType'; const visitedKey = 'changeAddResourceType';
const { addVisited, getIsVisited } = useVisit(visitedKey); const { addVisited, getIsVisited } = useVisit(visitedKey);
@ -534,7 +554,7 @@
} }
); );
const batchFormRef = ref<MsBatchFormInstance | null>(null); const batchFormRef = ref<InstanceType<typeof MsBatchForm>>();
const batchFormModels: Ref<FormItemModel[]> = ref([ const batchFormModels: Ref<FormItemModel[]> = ref([
{ {
filed: 'ip', filed: 'ip',
@ -580,7 +600,7 @@
// //
const defaultVals = computed(() => { const defaultVals = computed(() => {
const { nodesList } = form.value.testResourceDTO; const { nodesList } = form.value.testResourceDTO;
return nodesList.map((node) => node); return nodesList.map((node) => cloneDeep(node));
}); });
// //
@ -606,9 +626,9 @@
* 提取动态表单项输入的内容 * 提取动态表单项输入的内容
*/ */
function setBatchFormRes() { function setBatchFormRes() {
const res = batchFormRef.value?.getFormResult<NodesListItem>(); const res = batchFormRef.value?.getFormResult();
if (res?.length) { if (res?.length) {
form.value.testResourceDTO.nodesList = res.map((e) => e); form.value.testResourceDTO.nodesList = res.map((e) => e) as NodesListItem[];
} }
} }
@ -652,12 +672,14 @@
// //
setBatchFormRes(); setBatchFormRes();
} }
setIsSave(false);
} }
function changeResourceType(val: string | number | boolean) { function changeResourceType(val: string | number | boolean) {
if (val === 'Kubernetes') { if (val === 'Kubernetes') {
setBatchFormRes(); setBatchFormRes();
} }
setIsSave(false);
} }
/** /**
@ -699,7 +721,7 @@
* 重置表单信息 * 重置表单信息
*/ */
function resetForm() { function resetForm() {
form.value = { ...defaultForm }; form.value = { ...cloneDeep(defaultForm) };
} }
/** /**
@ -841,6 +863,10 @@
return scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' }); return scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
}); });
} }
function handleBack() {
router.replace({ name: SettingRouteEnum.SETTING_SYSTEM_RESOURCE_POOL });
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -287,6 +287,12 @@
try { try {
const res = await getPoolInfo(id); const res = await getPoolInfo(id);
if (res) { if (res) {
if (res.deleted) {
Message.warning(t('common.resourceDeleted'));
drawerLoading.value = false;
showDetailDrawer.value = false;
return;
}
activePool.value = res; activePool.value = res;
const poolUses = [ const poolUses = [
activePool.value.apiTest ? t('system.resourcePool.useAPI') : '', activePool.value.apiTest ? t('system.resourcePool.useAPI') : '',

View File

@ -20,14 +20,14 @@ export default {
'system.resourcePool.disablePoolSuccess': 'Disabled successfully', 'system.resourcePool.disablePoolSuccess': 'Disabled successfully',
'system.resourcePool.deletePoolTip': 'Are you sure to delete the `{name}` resource?', 'system.resourcePool.deletePoolTip': 'Are you sure to delete the `{name}` resource?',
'system.resourcePool.deletePoolContentUsed': 'system.resourcePool.deletePoolContentUsed':
'This resource pool has been used, and related tests will stop immediately after deletion, please operate with caution!', 'If this resource pool has been used, and related tests will stop immediately after deletion, please operate with caution!',
'system.resourcePool.deletePoolContentUnuse': 'This resource pool is not in use. Are you sure to delete it?', 'system.resourcePool.deletePoolContentUnuse': 'This resource pool is not in use. Are you sure to delete it?',
'system.resourcePool.deletePoolConfirm': 'Confirm', 'system.resourcePool.deletePoolConfirm': 'Confirm',
'system.resourcePool.deletePoolCancel': 'Cancel', 'system.resourcePool.deletePoolCancel': 'Cancel',
'system.resourcePool.deletePoolSuccess': 'Deleted successfully', 'system.resourcePool.deletePoolSuccess': 'Deleted successfully',
'system.resourcePool.detailDesc': 'Description', 'system.resourcePool.detailDesc': 'Description',
'system.resourcePool.detailUrl': 'Current site URL', 'system.resourcePool.detailUrl': 'Current site URL',
'system.resourcePool.detailRange': 'Available range', 'system.resourcePool.detailRange': 'Applied organization',
'system.resourcePool.detailUse': 'Use', 'system.resourcePool.detailUse': 'Use',
'system.resourcePool.detailMirror': 'Mirror', 'system.resourcePool.detailMirror': 'Mirror',
'system.resourcePool.detailJMHeap': 'JMeter HEAP', 'system.resourcePool.detailJMHeap': 'JMeter HEAP',
@ -43,7 +43,7 @@ export default {
'system.resourcePool.descPlaceholder': 'Please describe the resource pool', 'system.resourcePool.descPlaceholder': 'Please describe the resource pool',
'system.resourcePool.serverUrl': 'Current site URL', 'system.resourcePool.serverUrl': 'Current site URL',
'system.resourcePool.rootUrlPlaceholder': 'MS deployment address', 'system.resourcePool.rootUrlPlaceholder': 'MS deployment address',
'system.resourcePool.orgRange': 'Application organization', 'system.resourcePool.orgRange': 'Applied organization',
'system.resourcePool.orgAll': 'All organization', 'system.resourcePool.orgAll': 'All organization',
'system.resourcePool.orgSetup': 'Specified organization', 'system.resourcePool.orgSetup': 'Specified organization',
'system.resourcePool.orgSelect': 'specified organization', 'system.resourcePool.orgSelect': 'specified organization',
@ -122,4 +122,6 @@ export default {
'system.resourcePool.addSuccess': 'Added resource pool successfully', 'system.resourcePool.addSuccess': 'Added resource pool successfully',
'system.resourcePool.updateSuccess': 'Resource pool updated successfully', 'system.resourcePool.updateSuccess': 'Resource pool updated successfully',
'system.resourcePool.atLeastOnePool': 'Reserve at least one resource pool', 'system.resourcePool.atLeastOnePool': 'Reserve at least one resource pool',
'system.resourcePool.add': 'Add',
'system.resourcePool.addAndContinue': 'Save and continue adding',
}; };

View File

@ -19,14 +19,14 @@ export default {
'system.resourcePool.disablePoolCancel': '取消', 'system.resourcePool.disablePoolCancel': '取消',
'system.resourcePool.disablePoolSuccess': '禁用成功', 'system.resourcePool.disablePoolSuccess': '禁用成功',
'system.resourcePool.deletePoolTip': '确认删除 `{name}` 这个资源吗?', 'system.resourcePool.deletePoolTip': '确认删除 `{name}` 这个资源吗?',
'system.resourcePool.deletePoolContentUsed': '该资源池已被使用,删除后相关测试会立即停止,请谨慎操作!', 'system.resourcePool.deletePoolContentUsed': '该资源池已被使用,删除后相关测试会立即停止,请谨慎操作!',
'system.resourcePool.deletePoolContentUnuse': '该资源池未被使用,是否确认删除?', 'system.resourcePool.deletePoolContentUnuse': '该资源池未被使用,是否确认删除?',
'system.resourcePool.deletePoolConfirm': '确认删除', 'system.resourcePool.deletePoolConfirm': '确认删除',
'system.resourcePool.deletePoolCancel': '取消', 'system.resourcePool.deletePoolCancel': '取消',
'system.resourcePool.deletePoolSuccess': '删除成功', 'system.resourcePool.deletePoolSuccess': '删除成功',
'system.resourcePool.detailDesc': '描述', 'system.resourcePool.detailDesc': '描述',
'system.resourcePool.detailUrl': '当前站点 URL', 'system.resourcePool.detailUrl': '当前站点 URL',
'system.resourcePool.detailRange': '可用范围', 'system.resourcePool.detailRange': '应用组织',
'system.resourcePool.detailUse': '用途', 'system.resourcePool.detailUse': '用途',
'system.resourcePool.detailMirror': '镜像', 'system.resourcePool.detailMirror': '镜像',
'system.resourcePool.detailJMHeap': 'JMeter HEAP', 'system.resourcePool.detailJMHeap': 'JMeter HEAP',
@ -67,7 +67,7 @@ export default {
'system.resourcePool.batchAdd': '批量添加', 'system.resourcePool.batchAdd': '批量添加',
'system.resourcePool.batchAddTipConfirm': '知道了', 'system.resourcePool.batchAddTipConfirm': '知道了',
'system.resourcePool.batchAddResource': '批量添加资源', 'system.resourcePool.batchAddResource': '批量添加资源',
'system.resourcePool.changeAddTypeTip': '切换后,已添加资源内容将继续在 yaml 内;可批量修改已添加资源', 'system.resourcePool.changeAddTypeTip': '切换后,已添加资源内容将继续显示在 yaml 内;可批量修改已添加资源',
'system.resourcePool.changeAddTypePopTitle': '切换添加资源类型?', 'system.resourcePool.changeAddTypePopTitle': '切换添加资源类型?',
'system.resourcePool.allUseTip': '如果配置多个测试类型,会存在抢占资源的情况,建议一种测试类型配置一个资源池', 'system.resourcePool.allUseTip': '如果配置多个测试类型,会存在抢占资源的情况,建议一种测试类型配置一个资源池',
'system.resourcePool.ip': 'IP', 'system.resourcePool.ip': 'IP',
@ -117,4 +117,6 @@ export default {
'system.resourcePool.addSuccess': '添加资源池成功', 'system.resourcePool.addSuccess': '添加资源池成功',
'system.resourcePool.updateSuccess': '更新资源池成功', 'system.resourcePool.updateSuccess': '更新资源池成功',
'system.resourcePool.atLeastOnePool': '至少保留一个资源池', 'system.resourcePool.atLeastOnePool': '至少保留一个资源池',
'system.resourcePool.add': '添加',
'system.resourcePool.addAndContinue': '保存并继续添加',
}; };

View File

@ -145,7 +145,7 @@
</a-form-item> </a-form-item>
</a-form> </a-form>
<template #footer> <template #footer>
<a-button type="secondary" :disabled="loading" @click="cancelCreate"> <a-button type="secondary" :disabled="loading" @click="handleBeforeClose">
{{ t('system.user.editUserModalCancelCreate') }} {{ t('system.user.editUserModalCancelCreate') }}
</a-button> </a-button>
<a-button v-if="userFormMode === 'create'" type="secondary" :loading="loading" @click="saveAndContinue"> <a-button v-if="userFormMode === 'create'" type="secondary" :loading="loading" @click="saveAndContinue">
@ -302,7 +302,7 @@
import MsTagGroup from '@/components/pure/ms-tag/ms-tag-group.vue'; import MsTagGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
import MsUpload from '@/components/pure/ms-upload/index.vue'; import MsUpload from '@/components/pure/ms-upload/index.vue';
import MsBatchForm from '@/components/business/ms-batch-form/index.vue'; import MsBatchForm from '@/components/business/ms-batch-form/index.vue';
import type { FormItemModel, MsBatchFormInstance } from '@/components/business/ms-batch-form/types'; import type { FormItemModel } from '@/components/business/ms-batch-form/types';
import MsSelect from '@/components/business/ms-select'; import MsSelect from '@/components/business/ms-select';
import batchModal from './components/batchModal.vue'; import batchModal from './components/batchModal.vue';
import inviteModal from './components/inviteModal.vue'; import inviteModal from './components/inviteModal.vue';
@ -816,7 +816,7 @@
} }
} }
const batchFormRef = ref<MsBatchFormInstance | null>(null); const batchFormRef = ref<InstanceType<typeof MsBatchForm>>();
const batchFormModels: Ref<FormItemModel[]> = ref([ const batchFormModels: Ref<FormItemModel[]> = ref([
{ {
filed: 'name', filed: 'name',
@ -933,7 +933,7 @@
} else { } else {
Message.success(t('system.user.addUserSuccess')); Message.success(t('system.user.addUserSuccess'));
if (!isContinue) { if (!isContinue) {
visible.value = false; cancelCreate();
} }
loadList(); loadList();
} }
@ -981,6 +981,7 @@
userFormValidate(async () => { userFormValidate(async () => {
await createUser(true); await createUser(true);
resetUserForm(); resetUserForm();
batchFormRef.value?.resetForm();
}); });
} }

View File

@ -84,7 +84,7 @@
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const currentKeyword = ref(''); const currentKeyword = ref('');
const ugLeftRef = ref(); const ugLeftRef = ref<InstanceType<typeof UserGroupLeft>>();
const currentUserGroupItem = ref<CurrentUserGroupItem>({ const currentUserGroupItem = ref<CurrentUserGroupItem>({
id: '', id: '',
@ -147,7 +147,7 @@
} }
}); });
onMounted(() => { onMounted(() => {
ugLeftRef.value?.initData(router.currentRoute.value.query.id, true); ugLeftRef.value?.initData(router.currentRoute.value.query.id as string, true);
}); });
</script> </script>