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 {
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 {
&:hover::before {
background-color: rgb(var(--primary-9)) !important;
@apply hidden;
}
}
}

View File

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

View File

@ -26,22 +26,3 @@ export interface FormItemModel {
defaultValue?: string | string[] | number | number[] | 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>
</template>
<template #caseLevel="{ record }">
<span>{{ t(record.priority) }}</span>
<caseLevel :case-level="getCaseLevel(record)" />
</template>
</ms-base-table>
<div class="footer">
@ -168,6 +168,7 @@
import type { CommonList, ModuleTreeNode, TableQueryParams } from '@/models/common';
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
import type { CaseLevel } from './types';
import { initGetModuleCountFunc, type RequestModuleEnum } from './utils';
const router = useRouter();
@ -328,7 +329,8 @@
sortDirections: ['ascend', 'descend'],
sorter: true,
},
width: 200,
width: 150,
fixed: 'left',
},
{
title: 'ms.case.associate.caseName',
@ -338,7 +340,7 @@
sorter: true,
},
showTooltip: true,
width: 300,
width: 250,
},
{
title: 'ms.case.associate.caseLevel',
@ -357,6 +359,7 @@
props.getTableFunc,
{
columns,
scroll: { x: '100%' },
showSetting: false,
selectable: 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>({
moduleIds: [],
version: version.value,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,4 @@
import { RouteLocationNormalized, RouteRecordRaw } from 'vue-router';
import { includes } from 'lodash-es';
import { firstLevelMenu } from '@/config/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?',
'common.image': 'Image',
'common.text': 'Text',
'common.resourceDeleted': 'Resource has been deleted',
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -314,11 +314,12 @@
{{ t('apiTestManagement.timeTaskList') }}
<a-input-search
v-model:model-value="keyword"
:placeholder="t('apiTestManagement.searchPlaceholder')"
:placeholder="t('apiTestManagement.searchTaskPlaceholder')"
allow-clear
class="mr-[8px] w-[240px]"
@search="loadTaskList"
@press-enter="loadTaskList"
@clear="loadTaskList"
/>
</div>
<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.timeTaskDay'), value: '0 0 0 * * ?' },
];
const cronValue = ref('0 0 0/1 * * ? ');
const cronValue = ref('0 0 0/1 * * ?');
const importLoading = ref(false);
const taskDrawerVisible = ref(false);

View File

@ -5,7 +5,7 @@
<a-form-item
field="name"
:label="t('apiTestManagement.apiName')"
class="mb-[16px] w-[80%]"
class="mb-[16px] w-[60%]"
:rules="[{ required: true, message: t('apiTestManagement.apiNameRequired') }]"
>
<a-input
@ -16,7 +16,7 @@
@change="handleActiveApiChange"
/>
</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-form-item>
<a-form-item :label="t('apiTestManagement.belongModule')" class="mb-[16px] w-[436px]">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
<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>
<a-tooltip :content="reviewDetail.name">
<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 v-if="keyword.trim() === ''" #empty>
<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')">
{{ t('caseManagement.caseReview.create') }}
</MsButton>
@ -214,7 +218,7 @@
import useTableStore from '@/hooks/useTableStore';
import useAppStore from '@/store/modules/app';
import useUserStore from '@/store/modules/user';
import { hasAnyPermission } from '@/utils/permission';
import { hasAllPermission, hasAnyPermission } from '@/utils/permission';
import {
ReviewDetailReviewersItem,

View File

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

View File

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

View File

@ -33,7 +33,11 @@
v-model="userInfo.username"
:max-length="64"
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
@ -91,6 +95,7 @@
import useLoading from '@/hooks/useLoading';
import { NO_PROJECT_ROUTE_NAME, NO_RESOURCE_ROUTE_NAME } from '@/router/constants';
import { useAppStore, useUserStore } from '@/store';
import useLicenseStore from '@/store/modules/setting/license';
import { encrypted } from '@/utils';
import { setLoginExpires } from '@/utils/auth';
import { getFirstRouteNameByPermission, routerNameHasPermission } from '@/utils/permission';
@ -103,6 +108,7 @@
const { t } = useI18n();
const userStore = useUserStore();
const appStore = useAppStore();
const licenseStore = useLicenseStore();
const props = defineProps<{
isPreview?: boolean;
@ -181,7 +187,7 @@
![NO_RESOURCE_ROUTE_NAME, NO_PROJECT_ROUTE_NAME].includes(redirect as string) &&
routerNameHasPermission(redirect as string, 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) {
router.push({
name: NO_PROJECT_ROUTE_NAME,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -35,7 +35,7 @@
<a-input
v-else
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')"
class="w-[245px]"
@press-enter="beforeConfirm(undefined)"

View File

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

View File

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

View File

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

View File

@ -5,6 +5,15 @@
<a-button v-permission="['SYSTEM_PARAMETER_SETTING_AUTH:READ+ADD']" type="primary" @click="createAuth">
{{ t('system.config.auth.add') }}
</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>
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
<template #name="{ record }">
@ -684,7 +693,7 @@
},
];
const tableStore = useTableStore();
const { propsRes, propsEvent, loadList } = useTable(getAuthList, {
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(getAuthList, {
tableKey: TableKeyEnum.SYSTEM_AUTH,
columns,
scroll: { y: 'auto' },
@ -696,6 +705,15 @@
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 ref="loginPageFullRef" class="login-preview">
<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]" />
<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
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.saveSuccess': 'Saved successfully',
'system.config.auth.add': 'Add authentication',
'system.config.auth.searchTip': 'Search by name',
'system.config.auth.enable': 'Enable',
'system.config.auth.enableSuccess': 'Enabled successfully',
'system.config.auth.enableTipTitle': 'Enable authentication {name}',

View File

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

View File

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

View File

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

View File

@ -20,14 +20,14 @@ export default {
'system.resourcePool.disablePoolSuccess': 'Disabled successfully',
'system.resourcePool.deletePoolTip': 'Are you sure to delete the `{name}` resource?',
'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.deletePoolConfirm': 'Confirm',
'system.resourcePool.deletePoolCancel': 'Cancel',
'system.resourcePool.deletePoolSuccess': 'Deleted successfully',
'system.resourcePool.detailDesc': 'Description',
'system.resourcePool.detailUrl': 'Current site URL',
'system.resourcePool.detailRange': 'Available range',
'system.resourcePool.detailRange': 'Applied organization',
'system.resourcePool.detailUse': 'Use',
'system.resourcePool.detailMirror': 'Mirror',
'system.resourcePool.detailJMHeap': 'JMeter HEAP',
@ -43,7 +43,7 @@ export default {
'system.resourcePool.descPlaceholder': 'Please describe the resource pool',
'system.resourcePool.serverUrl': 'Current site URL',
'system.resourcePool.rootUrlPlaceholder': 'MS deployment address',
'system.resourcePool.orgRange': 'Application organization',
'system.resourcePool.orgRange': 'Applied organization',
'system.resourcePool.orgAll': 'All organization',
'system.resourcePool.orgSetup': 'Specified organization',
'system.resourcePool.orgSelect': 'specified organization',
@ -122,4 +122,6 @@ export default {
'system.resourcePool.addSuccess': 'Added resource pool successfully',
'system.resourcePool.updateSuccess': 'Resource pool updated successfully',
'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.disablePoolSuccess': '禁用成功',
'system.resourcePool.deletePoolTip': '确认删除 `{name}` 这个资源吗?',
'system.resourcePool.deletePoolContentUsed': '该资源池已被使用,删除后相关测试会立即停止,请谨慎操作!',
'system.resourcePool.deletePoolContentUsed': '该资源池已被使用,删除后相关测试会立即停止,请谨慎操作!',
'system.resourcePool.deletePoolContentUnuse': '该资源池未被使用,是否确认删除?',
'system.resourcePool.deletePoolConfirm': '确认删除',
'system.resourcePool.deletePoolCancel': '取消',
'system.resourcePool.deletePoolSuccess': '删除成功',
'system.resourcePool.detailDesc': '描述',
'system.resourcePool.detailUrl': '当前站点 URL',
'system.resourcePool.detailRange': '可用范围',
'system.resourcePool.detailRange': '应用组织',
'system.resourcePool.detailUse': '用途',
'system.resourcePool.detailMirror': '镜像',
'system.resourcePool.detailJMHeap': 'JMeter HEAP',
@ -67,7 +67,7 @@ export default {
'system.resourcePool.batchAdd': '批量添加',
'system.resourcePool.batchAddTipConfirm': '知道了',
'system.resourcePool.batchAddResource': '批量添加资源',
'system.resourcePool.changeAddTypeTip': '切换后,已添加资源内容将继续在 yaml 内;可批量修改已添加资源',
'system.resourcePool.changeAddTypeTip': '切换后,已添加资源内容将继续显示在 yaml 内;可批量修改已添加资源',
'system.resourcePool.changeAddTypePopTitle': '切换添加资源类型?',
'system.resourcePool.allUseTip': '如果配置多个测试类型,会存在抢占资源的情况,建议一种测试类型配置一个资源池',
'system.resourcePool.ip': 'IP',
@ -117,4 +117,6 @@ export default {
'system.resourcePool.addSuccess': '添加资源池成功',
'system.resourcePool.updateSuccess': '更新资源池成功',
'system.resourcePool.atLeastOnePool': '至少保留一个资源池',
'system.resourcePool.add': '添加',
'system.resourcePool.addAndContinue': '保存并继续添加',
};

View File

@ -145,7 +145,7 @@
</a-form-item>
</a-form>
<template #footer>
<a-button type="secondary" :disabled="loading" @click="cancelCreate">
<a-button type="secondary" :disabled="loading" @click="handleBeforeClose">
{{ t('system.user.editUserModalCancelCreate') }}
</a-button>
<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 MsUpload from '@/components/pure/ms-upload/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 batchModal from './components/batchModal.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([
{
filed: 'name',
@ -933,7 +933,7 @@
} else {
Message.success(t('system.user.addUserSuccess'));
if (!isContinue) {
visible.value = false;
cancelCreate();
}
loadList();
}
@ -981,6 +981,7 @@
userFormValidate(async () => {
await createUser(true);
resetUserForm();
batchFormRef.value?.resetForm();
});
}

View File

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