feat(用例评审): 用例评审部分接口&部分组件调整

This commit is contained in:
baiqi 2023-12-08 14:55:03 +08:00 committed by rubylliu
parent 24f12d7b12
commit c6723155a5
28 changed files with 453 additions and 202 deletions

View File

@ -5,10 +5,12 @@ import {
GetAuthDetailUrl, GetAuthDetailUrl,
GetAuthListUrl, GetAuthListUrl,
GetBaseInfoUrl, GetBaseInfoUrl,
GetCleanConfigUrl,
GetEmailInfoUrl, GetEmailInfoUrl,
GetPageConfigUrl, GetPageConfigUrl,
SaveBaseInfoUrl, SaveBaseInfoUrl,
SaveBaseUrlUrl, SaveBaseUrlUrl,
SaveCleanConfigUrl,
SaveEmailInfoUrl, SaveEmailInfoUrl,
SavePageConfigUrl, SavePageConfigUrl,
TestEmailUrl, TestEmailUrl,
@ -23,6 +25,7 @@ import type {
AuthItem, AuthItem,
AuthParams, AuthParams,
BaseConfig, BaseConfig,
CleanupConfig,
EmailConfig, EmailConfig,
LDAPConfig, LDAPConfig,
LDAPConnectConfig, LDAPConnectConfig,
@ -112,3 +115,13 @@ export function testLdapConnect(data: LDAPConnectConfig) {
export function testLdapLogin(data: LDAPConfig) { export function testLdapLogin(data: LDAPConfig) {
return MSR.post({ url: TestLdapLoginUrl, data }); return MSR.post({ url: TestLdapLoginUrl, data });
} }
// 保存内存清理配置
export function saveCleanupConfig(data: SaveInfoParams) {
return MSR.post({ url: SaveCleanConfigUrl, data });
}
// 保存内存清理配置
export function getCleanupConfig() {
return MSR.get<CleanupConfig>({ url: GetCleanConfigUrl });
}

View File

@ -30,6 +30,10 @@ export const DeleteAuthUrl = '/system/authsource/delete';
export const TestLdapConnectUrl = '/system/authsource/ldap/test-connect'; export const TestLdapConnectUrl = '/system/authsource/ldap/test-connect';
// 测试ldap登录 // 测试ldap登录
export const TestLdapLoginUrl = '/system/authsource/ldap/test-login'; export const TestLdapLoginUrl = '/system/authsource/ldap/test-login';
// 内存清理配置保存
export const SaveCleanConfigUrl = '/system/parameter/edit/clean-config';
// 获取内存清理配置
export const GetCleanConfigUrl = '/system/parameter/get/clean-config';
// 获取系统主页左上角图片 // 获取系统主页左上角图片
export const GetTitleImgUrl = `${import.meta.env.VITE_API_BASE_URL}/base-display/get/logo-platform`; export const GetTitleImgUrl = `${import.meta.env.VITE_API_BASE_URL}/base-display/get/logo-platform`;

View File

@ -19,7 +19,7 @@
<div :class="getFolderClass('all')" @click="setActiveFolder('all')"> <div :class="getFolderClass('all')" @click="setActiveFolder('all')">
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" /> <MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
<div class="folder-name">{{ t('caseManagement.caseReview.allReviews') }}</div> <div class="folder-name">{{ t('caseManagement.caseReview.allReviews') }}</div>
<div class="folder-count">({{ allFileCount }})</div> <div class="folder-count">({{ allCaseCount }})</div>
</div> </div>
</div> </div>
<a-divider class="my-[8px]" /> <a-divider class="my-[8px]" />
@ -50,12 +50,23 @@
</a-spin> </a-spin>
</div> </div>
<div class="flex w-[calc(100%-293px)] flex-col p-[16px]"> <div class="flex w-[calc(100%-293px)] flex-col p-[16px]">
<div class="mb-[16px] flex items-center justify-between"> <MsAdvanceFilter
<div class="flex items-center"> v-model:keyword="keyword"
<div class="mr-[4px] text-[var(--color-text-1)]">{{ activeFolderName }}</div> :filter-config-list="filterConfigList"
<div class="text-[var(--color-text-4)]">({{ activeFolderName }})</div> :row-count="filterRowCount"
</div> :search-placeholder="t('caseManagement.caseReview.searchPlaceholder')"
<div class="flex items-center gap-[8px]"> @keyword-search="searchCase"
@adv-search="searchCase"
>
<template #left>
<div class="flex items-center justify-between">
<div class="flex items-center">
<div class="mr-[4px] text-[var(--color-text-1)]">{{ activeFolderName }}</div>
<div class="text-[var(--color-text-4)]">({{ propsRes.msPagination?.total }})</div>
</div>
</div>
</template>
<template #right>
<a-select <a-select
v-model:model-value="version" v-model:model-value="version"
:options="versionOptions" :options="versionOptions"
@ -63,21 +74,9 @@
class="w-[200px]" class="w-[200px]"
allow-clear allow-clear
/> />
<a-input-search </template>
v-model="keyword" </MsAdvanceFilter>
:placeholder="t('ms.case.associate.searchPlaceholder')" <ms-base-table v-bind="propsRes" no-disable class="mt-[16px]" v-on="propsEvent">
allow-clear
class="w-[200px]"
@press-enter="searchCase"
@search="searchCase"
/>
<a-button type="outline" class="arco-btn-outline--secondary px-[8px]">
<MsIcon type="icon-icon-filter" class="mr-[4px] text-[var(--color-text-4)]" />
<div class="text-[var(--color-text-4)]">{{ t('common.filter') }}</div>
</a-button>
</div>
</div>
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
<template #caseLevel="{ record }"> <template #caseLevel="{ record }">
<caseLevel :case-level="record.caseLevel" /> <caseLevel :case-level="record.caseLevel" />
</template> </template>
@ -88,9 +87,9 @@
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<slot name="footerRight"> <slot name="footerRight">
<a-button type="secondary" :disabled="loading" class="mr-[12px]" @click="cancel">{{ <a-button type="secondary" :disabled="loading" class="mr-[12px]" @click="cancel">
t('common.cancel') {{ t('common.cancel') }}
}}</a-button> </a-button>
<a-button <a-button
type="primary" type="primary"
:loading="loading" :loading="loading"
@ -111,6 +110,8 @@
import { computed, onBeforeMount, ref, watch } from 'vue'; import { computed, onBeforeMount, ref, watch } from 'vue';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
import { FilterFormItem } from '@/components/pure/ms-advance-filter/type';
import MsDrawer from '@/components/pure/ms-drawer/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue'; import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
@ -121,7 +122,6 @@
import type { MsTreeNodeData } from '@/components/business/ms-tree/types'; import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import caseLevel from './caseLevel.vue'; import caseLevel from './caseLevel.vue';
import { getModules } from '@/api/modules/project-management/fileManagement';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import { mapTree } from '@/utils'; import { mapTree } from '@/utils';
@ -131,8 +131,10 @@
const props = defineProps<{ const props = defineProps<{
visible: boolean; visible: boolean;
project: string; project: string;
getModulesFunc: (params: any) => Promise<ModuleTreeNode[]>;
modulesCount?: Record<string, number>; // modulesCount?: Record<string, number>; //
okButtonDisabled?: boolean; // okButtonDisabled?: boolean; //
selectedKeys?: string[]; // id
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:visible', val: boolean): void; (e: 'update:visible', val: boolean): void;
@ -187,7 +189,9 @@
const activeFolder = ref('all'); const activeFolder = ref('all');
const activeFolderName = ref(t('ms.case.associate.allCase')); const activeFolderName = ref(t('ms.case.associate.allCase'));
const allFileCount = ref(0); const allCaseCount = ref(0);
const filterRowCount = ref(0);
const filterConfigList = ref<FilterFormItem[]>([]);
function getFolderClass(id: string) { function getFolderClass(id: string) {
return activeFolder.value === id ? 'folder-text folder-text--active' : 'folder-text'; return activeFolder.value === id ? 'folder-text folder-text--active' : 'folder-text';
@ -213,7 +217,7 @@
async function initModules(isSetDefaultKey = false) { async function initModules(isSetDefaultKey = false) {
try { try {
moduleLoading.value = true; moduleLoading.value = true;
const res = await getModules(appStore.currentProjectId); const res = await props.getModulesFunc(appStore.currentProjectId);
folderTree.value = res; folderTree.value = res;
if (isSetDefaultKey) { if (isSetDefaultKey) {
selectedModuleKeys.value = [folderTree.value[0].id]; selectedModuleKeys.value = [folderTree.value[0].id];
@ -259,7 +263,7 @@
}); });
/** /**
* 初始化模块文件数量 * 初始化模块资源数量
*/ */
watch( watch(
() => props.modulesCount, () => props.modulesCount,
@ -327,7 +331,7 @@
isTag: true, isTag: true,
}, },
]; ];
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable( const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
() => () =>
Promise.resolve({ Promise.resolve({
list: [ list: [
@ -443,6 +447,7 @@
Message.success(t('ms.case.associate.associateSuccess')); Message.success(t('ms.case.associate.associateSuccess'));
innerVisible.value = false; innerVisible.value = false;
emit('success', Array.from(propsRes.value.selectedKeys)); emit('success', Array.from(propsRes.value.selectedKeys));
resetSelector();
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
@ -453,6 +458,7 @@
function cancel() { function cancel() {
innerVisible.value = false; innerVisible.value = false;
resetSelector();
emit('close'); emit('close');
} }

View File

@ -27,7 +27,7 @@
:detail-id="props.detailId" :detail-id="props.detailId"
:detail-index="props.detailIndex" :detail-index="props.detailIndex"
:table-data="props.tableData" :table-data="props.tableData"
@loaded="(e) => emit('loaded', e)" @loaded="handleDetailLoaded"
/> />
<div class="ml-auto flex items-center"> <div class="ml-auto flex items-center">
<slot name="titleRight" :loading="loading" :detail="detail"></slot> <slot name="titleRight" :loading="loading" :detail="detail"></slot>
@ -90,6 +90,11 @@
prevNextButtonRef.value?.openNextDetail(); prevNextButtonRef.value?.openNextDetail();
} }
function handleDetailLoaded(val: any) {
detail.value = val;
emit('loaded', val);
}
watch( watch(
() => innerVisible.value, () => innerVisible.value,
(val) => { (val) => {

View File

@ -60,9 +60,17 @@
</a-form> </a-form>
<MsDescription v-else :descriptions="descriptions"> <MsDescription v-else :descriptions="descriptions">
<template #tag> <template #tag>
<MsTag> 组织 1 </MsTag> <div v-for="org of orgList" :key="org.orgId" class="mb-[16px]">
<br /> <MsTag class="h-[26px]"> {{ org.orgName }} </MsTag>
<MsTag size="small" class="mt-[8px] !bg-[rgb(var(--primary-1))] !text-[rgb(var(--primary-5))]"> 项目 1 </MsTag> <br />
<MsTag
v-for="project of org.projectList"
:key="project.projectId"
class="!mr-[8px] mt-[8px] !bg-[rgb(var(--primary-1))] !text-[rgb(var(--primary-5))]"
>
{{ project.projectName }}
</MsTag>
</div>
</template> </template>
</MsDescription> </MsDescription>
<a-modal <a-modal
@ -131,6 +139,8 @@
import useUserStore from '@/store/modules/user/index'; import useUserStore from '@/store/modules/user/index';
import { validateEmail, validatePhone } from '@/utils/validate'; import { validateEmail, validatePhone } from '@/utils/validate';
import { OrganizationProjectListItem } from '@/models/user';
import type { FormInstance } from '@arco-design/web-vue'; import type { FormInstance } from '@arco-design/web-vue';
const userStore = useUserStore(); const userStore = useUserStore();
@ -139,38 +149,43 @@
const loading = ref(false); const loading = ref(false);
const isEdit = ref(false); const isEdit = ref(false);
const descriptions = ref<Description[]>([ const descriptions = ref<Description[]>([]);
{
label: t('ms.personal.name'),
value: userStore.name || '',
},
{
label: t('ms.personal.email'),
value: userStore.email || '',
},
{
label: t('ms.personal.phone'),
value: userStore.phone || '',
},
{
label: t('ms.personal.org'),
value: [],
isTag: true,
},
]);
const baseInfoForm = ref({ const baseInfoForm = ref({
name: userStore.name, name: userStore.name,
email: userStore.email, email: userStore.email,
phone: userStore.phone, phone: userStore.phone,
}); });
const baseInfoFormRef = ref<FormInstance>(); const baseInfoFormRef = ref<FormInstance>();
const orgList = ref([]); const orgList = ref<OrganizationProjectListItem[]>([]);
function initBaseInfo() {
descriptions.value = [
{
label: t('ms.personal.name'),
value: userStore.name || '',
},
{
label: t('ms.personal.email'),
value: userStore.email || '',
},
{
label: t('ms.personal.phone'),
value: userStore.phone || '',
},
{
label: t('ms.personal.org'),
value: [],
isTag: true,
},
];
}
onBeforeMount(async () => { onBeforeMount(async () => {
initBaseInfo();
try { try {
loading.value = true; loading.value = true;
const res = await getBaseInfo(userStore.id || ''); const res = await getBaseInfo(userStore.id || '');
console.log(res); orgList.value = res.orgProjectList;
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
@ -223,6 +238,7 @@
}); });
Message.success(t('common.updateSuccess')); Message.success(t('common.updateSuccess'));
await userStore.isLogin(); await userStore.isLogin();
initBaseInfo();
isEdit.value = false; isEdit.value = false;
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console

View File

@ -100,10 +100,17 @@
}); });
Message.success({ Message.success({
content: t('ms.personal.updatePswSuccess', { count: counting.value }), content: t('ms.personal.updatePswSuccess', { count: counting.value }),
duration: 4000, duration: 1000,
}); });
setInterval(() => counting.value--, 1000); const timer = setInterval(() => {
counting.value--;
Message.success({
content: t('ms.personal.updatePswSuccess', { count: counting.value }),
duration: 1000,
});
}, 1000);
setTimeout(() => { setTimeout(() => {
clearInterval(timer);
logout(); logout();
}, 3000); }, 3000);
} catch (error) { } catch (error) {

View File

@ -45,6 +45,7 @@ export interface MsSearchSelectSlots {
header?: (() => JSX.Element) | Slot<any>; header?: (() => JSX.Element) | Slot<any>;
default?: () => JSX.Element[]; default?: () => JSX.Element[];
footer?: Slot<any>; footer?: Slot<any>;
empty?: Slot<any>;
} }
export default defineComponent( export default defineComponent(
@ -283,6 +284,9 @@ export default defineComponent(
if (slots.footer) { if (slots.footer) {
_slots.footer = slots.footer; _slots.footer = slots.footer;
} }
if (slots.empty) {
_slots.empty = slots.empty;
}
return _slots; return _slots;
}; };

View File

@ -5,7 +5,7 @@
v-bind="props" v-bind="props"
ref="treeRef" ref="treeRef"
v-model:expanded-keys="expandedKeys" v-model:expanded-keys="expandedKeys"
:selected-keys="selectedKeys" v-model:selected-keys="innerSelectedKeys"
:data="treeData" :data="treeData"
class="ms-tree" class="ms-tree"
@drop="onDrop" @drop="onDrop"
@ -355,17 +355,17 @@
} }
); );
const selectedKeys = ref(props.selectedKeys || []); const innerSelectedKeys = ref(props.selectedKeys || []);
watch( watch(
() => props.selectedKeys, () => props.selectedKeys,
(val) => { (val) => {
selectedKeys.value = val || []; innerSelectedKeys.value = val || [];
} }
); );
watch( watch(
() => selectedKeys.value, () => innerSelectedKeys.value,
(val) => { (val) => {
emit('update:selectedKeys', val); emit('update:selectedKeys', val);
} }

View File

@ -2,6 +2,7 @@
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<slot name="left"></slot> <slot name="left"></slot>
<div class="flex flex-row gap-[8px]"> <div class="flex flex-row gap-[8px]">
<slot name="right"></slot>
<a-input-search <a-input-search
v-model="innerKeyword" v-model="innerKeyword"
size="small" size="small"
@ -26,7 +27,6 @@
</span> </span>
</span> </span>
</MsTag> </MsTag>
<slot name="right"></slot>
<MsTag no-margin size="large" class="cursor-pointer" theme="outline" @click="handleResetSearch"> <MsTag no-margin size="large" class="cursor-pointer" theme="outline" @click="handleResetSearch">
<MsIcon class="text-[var(color-text-4)]" :size="16" type="icon-icon_reset_outlined" /> <MsIcon class="text-[var(color-text-4)]" :size="16" type="icon-icon_reset_outlined" />
</MsTag> </MsTag>

View File

@ -145,3 +145,8 @@ export interface LDAPConfig extends LDAPConnectConfig {
ldapUserOu: string; ldapUserOu: string;
ldapUserMapping: string; ldapUserMapping: string;
} }
// 内存清理配置
export interface CleanupConfig {
operationLog: string;
operationHistory: string;
}

View File

@ -128,8 +128,14 @@ export interface PersonalOrganization {
enable: boolean; enable: boolean;
moduleSetting: string; moduleSetting: string;
} }
export interface OrganizationProjectMap { export interface PersonalProject {
[key: string]: PersonalOrganization[]; projectId: string;
projectName: string;
}
export interface OrganizationProjectListItem {
orgId: string;
orgName: string;
projectList: PersonalProject[];
} }
export interface PersonalInfo { export interface PersonalInfo {
id: string; id: string;
@ -148,7 +154,7 @@ export interface PersonalInfo {
updateUser: string; updateUser: string;
deleted: boolean; deleted: boolean;
avatar: string; avatar: string;
organizationProjectMap: OrganizationProjectMap; orgProjectList: OrganizationProjectListItem[];
} }
export interface UpdateBaseInfo { export interface UpdateBaseInfo {
id: string; id: string;

View File

@ -3,8 +3,8 @@
v-model:visible="innerVisible" v-model:visible="innerVisible"
v-model:project="innerProject" v-model:project="innerProject"
:ok-button-disabled="associateForm.reviewers.length === 0" :ok-button-disabled="associateForm.reviewers.length === 0"
:get-modules-func="getCaseModuleTree"
@success="writeAssociateCases" @success="writeAssociateCases"
@close="emit('close')"
> >
<template #footerLeft> <template #footerLeft>
<a-form ref="associateFormRef" :model="associateForm"> <a-form ref="associateFormRef" :model="associateForm">
@ -45,7 +45,17 @@
allow-clear allow-clear
multiple multiple
class="w-[300px]" class="w-[300px]"
:loading="reviewerLoading"
> >
<template #empty>
<div class="p-[3px_8px] text-[var(--color-text-4)]">
{{ t('caseManagement.caseReview.noMatchReviewer') }}
<span class="cursor-pointer text-[rgb(var(--primary-5))]" @click="goProjectManagement">
{{ t('menu.projectManagement') }}
</span>
<span v-if="currentLocale === 'zh-CN'" class="ml-[4px]">{{ t('common.setting') }}</span>
</div>
</template>
</MsSelect> </MsSelect>
</a-form-item> </a-form-item>
</a-form> </a-form>
@ -55,12 +65,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { FormInstance } from '@arco-design/web-vue'; import { FormInstance, SelectOptionData } from '@arco-design/web-vue';
import MsCaseAssociate from '@/components/business/ms-case-associate/index.vue'; import MsCaseAssociate from '@/components/business/ms-case-associate/index.vue';
import MsSelect from '@/components/business/ms-select'; import MsSelect from '@/components/business/ms-select';
import { getReviewUsers } from '@/api/modules/case-management/caseReview';
import { getCaseModuleTree } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useLocale from '@/locale/useLocale';
import useAppStore from '@/store/modules/app';
import { ProjectManagementRouteEnum } from '@/enums/routeEnum'; import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
@ -76,6 +90,8 @@
}>(); }>();
const router = useRouter(); const router = useRouter();
const appStore = useAppStore();
const { currentLocale } = useLocale();
const { t } = useI18n(); const { t } = useI18n();
const innerVisible = ref(false); const innerVisible = ref(false);
@ -125,24 +141,29 @@
); );
} }
const reviewersOptions = ref([ const reviewersOptions = ref<SelectOptionData[]>([]);
{ const reviewerLoading = ref(false);
label: '张三',
value: '1', async function initReviewers() {
}, try {
{ reviewerLoading.value = true;
label: '李四', const res = await getReviewUsers(appStore.currentProjectId, '');
value: '2', reviewersOptions.value = res.map((e) => ({ label: e.name, value: e.id }));
}, } catch (error) {
{ // eslint-disable-next-line no-console
label: '王五', console.log(error);
value: '3', } finally {
}, reviewerLoading.value = false;
]); }
}
function writeAssociateCases(ids: string[]) { function writeAssociateCases(ids: string[]) {
emit('success', ids); emit('success', ids);
} }
onBeforeMount(() => {
initReviewers();
});
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -18,7 +18,7 @@
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" /> <MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
</MsButton> </MsButton>
</a-tooltip> </a-tooltip>
<popConfirm mode="add" :all-names="rootModulesName" parent-id="none" @add-finish="initModules"> <popConfirm mode="add" :all-names="rootModulesName" parent-id="NONE" @add-finish="initModules">
<MsButton type="icon" class="!mr-0 p-[2px]"> <MsButton type="icon" class="!mr-0 p-[2px]">
<MsIcon <MsIcon
type="icon-icon_create_planarity" type="icon-icon_create_planarity"
@ -33,7 +33,7 @@
<a-spin class="min-h-[400px] w-full" :loading="loading"> <a-spin class="min-h-[400px] w-full" :loading="loading">
<MsTree <MsTree
v-model:focus-node-key="focusNodeKey" v-model:focus-node-key="focusNodeKey"
:selected-keys="selectedKeys" v-model:selected-keys="selectedKeys"
:data="folderTree" :data="folderTree"
:keyword="moduleKeyword" :keyword="moduleKeyword"
:node-more-actions="folderMoreActions" :node-more-actions="folderMoreActions"
@ -83,7 +83,7 @@
:field-config="{ field: renameFolderTitle }" :field-config="{ field: renameFolderTitle }"
:all-names="(nodeData.children || []).map((e: ModuleTreeNode) => e.name || '')" :all-names="(nodeData.children || []).map((e: ModuleTreeNode) => e.name || '')"
@close="resetFocusNodeKey" @close="resetFocusNodeKey"
@rename-finish="(val) => (nodeData.name = val)" @rename-finish="initModules"
> >
<span :id="`renameSpan${nodeData.id}`" class="relative"></span> <span :id="`renameSpan${nodeData.id}`" class="relative"></span>
</popConfirm> </popConfirm>
@ -139,6 +139,7 @@
const allFileCount = ref(0); const allFileCount = ref(0);
const isExpandAll = ref(props.isExpandAll); const isExpandAll = ref(props.isExpandAll);
const rootModulesName = ref<string[]>([]); // const rootModulesName = ref<string[]>([]); //
const selectedKeys = ref<string[]>([]);
watch( watch(
() => props.isExpandAll, () => props.isExpandAll,
@ -157,6 +158,9 @@
function setActiveFolder(id: string) { function setActiveFolder(id: string) {
activeFolder.value = id; activeFolder.value = id;
if (id === 'all') {
selectedKeys.value = [];
}
emit('folderNodeSelect', [id], []); emit('folderNodeSelect', [id], []);
} }
@ -182,8 +186,6 @@
]; ];
const renamePopVisible = ref(false); const renamePopVisible = ref(false);
const selectedKeys = ref<string[]>([]);
/** /**
* 初始化模块树 * 初始化模块树
* @param isSetDefaultKey 是否设置第一个节点为选中节点 * @param isSetDefaultKey 是否设置第一个节点为选中节点
@ -264,7 +266,7 @@
offspringIds.push(e.id); offspringIds.push(e.id);
return e; return e;
}); });
setActiveFolder(node.id);
emit('folderNodeSelect', _selectedKeys, offspringIds); emit('folderNodeSelect', _selectedKeys, offspringIds);
} }

View File

@ -51,12 +51,7 @@
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { import { addReviewModule, updateReviewModule } from '@/api/modules/case-management/caseReview';
addModule,
updateFile,
updateModule,
updateRepository,
} from '@/api/modules/project-management/fileManagement';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
@ -121,13 +116,14 @@
); );
function beforeConfirm(done?: (closed: boolean) => void) { function beforeConfirm(done?: (closed: boolean) => void) {
if (loading.value) return;
formRef.value?.validate(async (errors) => { formRef.value?.validate(async (errors) => {
if (!errors) { if (!errors) {
try { try {
loading.value = true; loading.value = true;
if (props.mode === 'add') { if (props.mode === 'add') {
// //
await addModule({ await addReviewModule({
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,
parentId: props.parentId || '', parentId: props.parentId || '',
name: form.value.field, name: form.value.field,
@ -136,31 +132,7 @@
emit('addFinish', form.value.field); emit('addFinish', form.value.field);
} else if (props.mode === 'rename') { } else if (props.mode === 'rename') {
// //
await updateModule({ await updateReviewModule({
id: props.nodeId || '',
name: form.value.field,
});
Message.success(t('project.fileManagement.renameSuccess'));
emit('renameFinish', form.value.field);
} else if (props.mode === 'fileRename') {
//
await updateFile({
id: props.nodeId || '',
name: form.value.field,
});
Message.success(t('project.fileManagement.renameSuccess'));
emit('renameFinish', form.value.field);
} else if (props.mode === 'fileUpdateDesc') {
//
await updateFile({
id: props.nodeId || '',
description: form.value.field,
});
Message.success(t('project.fileManagement.updateDescSuccess'));
emit('updateDescFinish', form.value.field);
} else if (props.mode === 'repositoryRename') {
//
await updateRepository({
id: props.nodeId || '', id: props.nodeId || '',
name: form.value.field, name: form.value.field,
}); });

View File

@ -7,6 +7,7 @@
:row-count="filterRowCount" :row-count="filterRowCount"
:search-placeholder="t('caseManagement.caseReview.searchPlaceholder')" :search-placeholder="t('caseManagement.caseReview.searchPlaceholder')"
@keyword-search="searchReview" @keyword-search="searchReview"
@adv-search="searchReview"
> >
<template #left> <template #left>
<div class="flex items-center"> <div class="flex items-center">
@ -92,7 +93,7 @@
<template v-if="keyword.trim() === ''" #empty> <template v-if="keyword.trim() === ''" #empty>
<div class="flex items-center justify-center p-[8px] text-[var(--color-text-4)]"> <div class="flex items-center justify-center p-[8px] text-[var(--color-text-4)]">
{{ t('caseManagement.caseReview.tableNoData') }} {{ t('caseManagement.caseReview.tableNoData') }}
<MsButton class="ml-[8px]" @click="handleAddClick"> <MsButton class="ml-[8px]" @click="() => emit('goCreate')">
{{ t('caseManagement.caseReview.create') }} {{ t('caseManagement.caseReview.create') }}
</MsButton> </MsButton>
</div> </div>
@ -213,6 +214,10 @@
moduleTree: ModuleTreeNode[]; moduleTree: ModuleTreeNode[];
}>(); }>();
const emit = defineEmits<{
(e: 'goCreate'): void;
}>();
const appStore = useAppStore(); const appStore = useAppStore();
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
@ -491,6 +496,7 @@
setLoadListParams({ setLoadListParams({
keyword: keyword.value, keyword: keyword.value,
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder],
}); });
loadList(); loadList();
} }
@ -527,7 +533,7 @@
} }
/** /**
* 拦截切换最新版确认 * 删除确认
* @param done 关闭弹窗 * @param done 关闭弹窗
*/ */
async function handleDeleteConfirm(done: (closed: boolean) => void) { async function handleDeleteConfirm(done: (closed: boolean) => void) {
@ -679,9 +685,12 @@
} }
} }
function handleAddClick() { watch(
console.log('handleAddClick'); () => props.activeFolder,
} () => {
searchReview();
}
);
function openDetail(id: string) { function openDetail(id: string) {
router.push({ router.push({

View File

@ -85,6 +85,7 @@
:search-keys="['label']" :search-keys="['label']"
allow-search allow-search
multiple multiple
:loading="reviewerLoading"
/> />
</a-form-item> </a-form-item>
<a-form-item field="tags" :label="t('caseManagement.caseReview.tag')"> <a-form-item field="tags" :label="t('caseManagement.caseReview.tag')">
@ -138,80 +139,38 @@
</div> </div>
</template> </template>
</MsCard> </MsCard>
<MsCaseAssociate <AssociateDrawer
v-model:visible="caseAssociateVisible" v-model:visible="caseAssociateVisible"
v-model:project="caseAssociateProject" v-model:project="caseAssociateProject"
:ok-button-disabled="associateForm.reviewers.length === 0"
@success="writeAssociateCases" @success="writeAssociateCases"
> />
<template #footerLeft>
<a-form ref="associateFormRef" :model="associateForm">
<a-form-item
field="reviewers"
:rules="[{ required: true, message: t('caseManagement.caseReview.reviewerRequired') }]"
class="mb-0"
>
<template #label>
<div class="inline-flex items-center">
{{ t('caseManagement.caseReview.reviewer') }}
<a-tooltip position="right">
<template #content>
<div>{{ t('caseManagement.caseReview.switchProject') }}</div>
<div>{{ t('caseManagement.caseReview.resetReviews') }}</div>
<div>
{{ t('caseManagement.caseReview.reviewsTip') }}
<span class="cursor-pointer text-[rgb(var(--primary-4))]" @click="goProjectManagement">
{{ t('menu.projectManagement') }}
</span>
{{ t('caseManagement.caseReview.reviewsTip2') }}
</div>
</template>
<icon-question-circle
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
</a-tooltip>
</div>
</template>
<MsSelect
v-model:modelValue="associateForm.reviewers"
mode="static"
:placeholder="t('caseManagement.caseReview.reviewerPlaceholder')"
:options="reviewersOptions"
:search-keys="['label']"
allow-search
allow-clear
multiple
class="w-[300px]"
>
</MsSelect>
</a-form-item>
</a-form>
</template>
</MsCaseAssociate>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
/** /**
* @description 功能测试-用例评审-创建评审 * @description 功能测试-用例评审-创建评审
*/ */
import { onBeforeMount } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue'; import { Message, SelectOptionData } from '@arco-design/web-vue';
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 MsTagsInput from '@/components/pure/ms-tags-input/index.vue'; import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import MsCaseAssociate from '@/components/business/ms-case-associate/index.vue';
import MsSelect from '@/components/business/ms-select'; import MsSelect from '@/components/business/ms-select';
import AssociateDrawer from './components/create/associateDrawer.vue';
import { getReviewUsers } from '@/api/modules/case-management/caseReview';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { CaseManagementRouteEnum, ProjectManagementRouteEnum } from '@/enums/routeEnum'; import { CaseManagementRouteEnum } from '@/enums/routeEnum';
import type { FormInstance } from '@arco-design/web-vue'; import type { FormInstance } from '@arco-design/web-vue';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const appStore = useAppStore();
const { t } = useI18n(); const { t } = useI18n();
const isEdit = ref(!!route.query.id); const isEdit = ref(!!route.query.id);
@ -239,20 +198,21 @@
value: '2', value: '2',
}, },
]); ]);
const reviewersOptions = ref([ const reviewersOptions = ref<SelectOptionData[]>([]);
{ const reviewerLoading = ref(false);
label: '张三',
value: '1', async function initReviewers() {
}, try {
{ reviewerLoading.value = true;
label: '李四', const res = await getReviewUsers(appStore.currentProjectId, '');
value: '2', reviewersOptions.value = res.map((e) => ({ label: e.name, value: e.id }));
}, } catch (error) {
{ // eslint-disable-next-line no-console
label: '王五', console.log(error);
value: '3', } finally {
}, reviewerLoading.value = false;
]); }
}
const selectedAssociateCases = ref<string[]>([]); const selectedAssociateCases = ref<string[]>([]);
@ -299,18 +259,10 @@
const caseAssociateVisible = ref<boolean>(false); const caseAssociateVisible = ref<boolean>(false);
const caseAssociateProject = ref(''); const caseAssociateProject = ref('');
const associateForm = ref({
reviewers: [],
});
const associateFormRef = ref<FormInstance>();
function goProjectManagement() { onBeforeMount(() => {
window.open( initReviewers();
`${window.location.origin}#${ });
router.resolve({ name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_PERMISSION_USER_GROUP }).fullPath
}`
);
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -16,7 +16,7 @@
</div> </div>
</template> </template>
<template #right> <template #right>
<ReviewTable :active-folder="activeFolderId" :module-tree="moduleTree" /> <ReviewTable :active-folder="activeFolderId" :module-tree="moduleTree" @go-create="goCreateReview" />
</template> </template>
</MsSplitBox> </MsSplitBox>
</div> </div>

View File

@ -78,4 +78,55 @@ export default {
'caseManagement.caseReview.reviewsTip2': 'can set permissions', 'caseManagement.caseReview.reviewsTip2': 'can set permissions',
'caseManagement.caseReview.clearSelectedCases': 'Clear selected use cases', 'caseManagement.caseReview.clearSelectedCases': 'Clear selected use cases',
'caseManagement.caseReview.selectedCases': '{count} use cases selected', 'caseManagement.caseReview.selectedCases': '{count} use cases selected',
'caseManagement.caseReview.onlyMine': 'See only Mine',
'caseManagement.caseReview.createTestPlan': 'Create test plan',
'caseManagement.caseReview.reviewedCase': 'Reviewed cases',
'caseManagement.caseReview.createCase': 'Create cases',
'caseManagement.caseReview.allCases': 'All cases',
'caseManagement.caseReview.noCases': 'No matching case data yet',
'caseManagement.caseReview.caseName': 'Case name',
'caseManagement.caseReview.reviewResult': 'Review results',
'caseManagement.caseReview.reviewResultTip':
'When "See only mine" is turned on, you can view my review results on the list',
'caseManagement.caseReview.disassociate': 'Disassociate',
'caseManagement.caseReview.disassociateConfirmTitle':
'Are you sure you want to cancel the selected {count} test cases?',
'caseManagement.caseReview.version': 'Version',
'caseManagement.caseReview.unReview': 'Unreviewed',
'caseManagement.caseReview.reviewPass': 'Review passed',
'caseManagement.caseReview.disassociateTip': 'Are you sure to cancel the association?',
'caseManagement.caseReview.disassociateTipContent':
'After cancellation, associate again, the review result is: Unreviewed',
'caseManagement.caseReview.changeReviewer': 'Modify reviewer',
'caseManagement.caseReview.batchReview': 'Batch review',
'caseManagement.caseReview.selectedCase': '{count} use cases selected',
'caseManagement.caseReview.reason': 'Reason',
'caseManagement.caseReview.reasonRequired': 'Reason cannot be empty',
'caseManagement.caseReview.reasonPlaceholder': 'Please enter the reason',
'caseManagement.caseReview.commitResult': 'Submit results',
'caseManagement.caseReview.batchReviewTip':
'Modifying the review results means modifying the personal review results.',
'caseManagement.caseReview.chooseReviewer': 'Select reviewers',
'caseManagement.caseReview.batchChangeReviewer': 'Batch modify reviewers',
'caseManagement.caseReview.append': 'Append',
'caseManagement.caseReview.appendTip1': 'Open: Add reviewer',
'caseManagement.caseReview.appendTip2': 'Close: Update reviewers',
'caseManagement.caseReview.myReview': 'My reviews',
'caseManagement.caseReview.caseLevel': 'Case level',
'caseManagement.caseReview.caseVersion': 'Case version',
'caseManagement.caseReview.caseStatus': 'Case status',
'caseManagement.caseReview.responsiblePerson': 'Responsible person',
'caseManagement.caseReview.createTime': 'Created time',
'caseManagement.caseReview.caseBaseInfo': 'Basic info',
'caseManagement.caseReview.caseDetail': 'Detail',
'caseManagement.caseReview.caseDemand': 'Demand',
'caseManagement.caseReview.startReview': 'Start review',
'caseManagement.caseReview.autoNext': 'Automatically next',
'caseManagement.caseReview.autoNextTip1': 'Open: After submitting the review results, jump to the next use case',
'caseManagement.caseReview.autoNextTip2': 'Close: After submitting the review results, it is still current',
'caseManagement.caseReview.suggestion': 'Suggestion',
'caseManagement.caseReview.suggestionTip': 'Not as a result of the review',
'caseManagement.caseReview.submitReview': 'Submit review',
'caseManagement.caseReview.reviewHistory': 'Review history',
'caseManagement.caseReview.noMatchReviewer': 'No matching handler, can be set in {menu}',
}; };

View File

@ -118,4 +118,5 @@ export default {
'caseManagement.caseReview.suggestionTip': '不作为评审结果', 'caseManagement.caseReview.suggestionTip': '不作为评审结果',
'caseManagement.caseReview.submitReview': '提交评审', 'caseManagement.caseReview.submitReview': '提交评审',
'caseManagement.caseReview.reviewHistory': '评审历史', 'caseManagement.caseReview.reviewHistory': '评审历史',
'caseManagement.caseReview.noMatchReviewer': '无匹配处理人,可在 ',
}; };

View File

@ -57,7 +57,7 @@
:field-config="{ field: renameFolderTitle }" :field-config="{ field: renameFolderTitle }"
:all-names="(nodeData.children || []).map((e: ModuleTreeNode) => e.name || '')" :all-names="(nodeData.children || []).map((e: ModuleTreeNode) => e.name || '')"
@close="resetFocusNodeKey" @close="resetFocusNodeKey"
@rename-finish="(val) => (nodeData.name = val)" @rename-finish="() => initModules()"
> >
<span :id="`renameSpan${nodeData.id}`" class="relative"></span> <span :id="`renameSpan${nodeData.id}`" class="relative"></span>
</popConfirm> </popConfirm>

View File

@ -121,6 +121,7 @@
); );
function beforeConfirm(done?: (closed: boolean) => void) { function beforeConfirm(done?: (closed: boolean) => void) {
if (loading.value) return;
formRef.value?.validate(async (errors) => { formRef.value?.validate(async (errors) => {
if (!errors) { if (!errors) {
try { try {

View File

@ -0,0 +1,141 @@
<template>
<MsCard class="mb-[16px]" :loading="loading" simple auto-height>
<div class="mb-[16px] flex justify-between">
<div class="font-medium text-[var(--color-text-000)]">{{ t('system.config.memoryCleanup') }}</div>
</div>
<a-radio-group v-model:model-value="activeType" type="button">
<a-radio value="log">{{ t('system.config.memoryCleanup.log') }}</a-radio>
<a-radio value="history">{{ t('system.config.memoryCleanup.history') }}</a-radio>
</a-radio-group>
<template v-if="activeType === 'log'">
<div class="mb-[8px] mt-[16px] flex items-center">
<div class="text-[var(--color-text-000)]">{{ t('system.config.memoryCleanup.keepTime') }}</div>
<a-tooltip :content="t('system.config.memoryCleanup.keepTimeTip')" position="right">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
</a-tooltip>
</div>
<a-input-number
v-model:model-value="timeCount"
class="w-[130px]"
:disabled="saveLoading"
@blur="() => saveConfig()"
>
<template #append>
<a-select
v-model:model-value="activeTime"
:options="timeOptions"
class="time-input-append"
:loading="saveLoading"
@change="() => saveConfig()"
/>
</template>
</a-input-number>
</template>
<template v-else>
<div class="mb-[8px] mt-[16px] flex items-center">
<div class="text-[var(--color-text-000)]">{{ t('system.config.memoryCleanup.saveCount') }}</div>
<a-tooltip :content="t('system.config.memoryCleanup.saveCountTip')" position="right">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
</a-tooltip>
</div>
<a-input-number
v-model:model-value="historyCount"
class="w-[130px]"
:disabled="saveLoading"
@blur="() => saveConfig()"
/>
</template>
</MsCard>
</template>
<script setup lang="ts">
import { Message } from '@arco-design/web-vue';
import MsCard from '@/components/pure/ms-card/index.vue';
import { getCleanupConfig, saveCleanupConfig } from '@/api/modules/setting/config';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
const loading = ref(false);
const activeType = ref('log');
const timeCount = ref(6);
const activeTime = ref('M');
const timeOptions = [
{
label: t('system.config.memoryCleanup.day'),
value: 'D',
},
{
label: t('system.config.memoryCleanup.month'),
value: 'M',
},
{
label: t('system.config.memoryCleanup.year'),
value: 'Y',
},
];
const historyCount = ref(10);
onBeforeMount(async () => {
loading.value = true;
const res = await getCleanupConfig();
if (res.operationLog) {
const matches = res.operationLog.match(/(\d+)([MDY])$/);
if (matches) {
const [, number, letter] = matches;
timeCount.value = Number(number);
activeTime.value = letter;
}
}
if (res.operationHistory) {
historyCount.value = Number(res.operationHistory);
}
loading.value = false;
});
const saveLoading = ref(false);
async function saveConfig() {
saveLoading.value = true;
await saveCleanupConfig([
{
paramKey: 'cleanConfig.operation.log',
paramValue: `${timeCount.value}${activeTime.value}`,
type: 'string',
},
{
paramKey: 'cleanConfig.operation.history',
paramValue: historyCount.value.toString(),
type: 'string',
},
]);
saveLoading.value = false;
Message.success(t('system.config.memoryCleanup.setSuccess'));
}
</script>
<style lang="less" scoped>
:deep(.arco-input-append) {
@apply border-none;
}
:deep(.time-input-append) {
@apply z-10;
margin-left: -16px !important;
border-radius: 0 4px 4px 0 !important;
background-color: var(--color-text-n8) !important;
&:hover {
border-color: rgb(var(--primary-5)) !important;
background-color: var(--color-text-n8) !important;
}
}
</style>

View File

@ -3,6 +3,7 @@
<baseConfig v-show="activeTab === 'baseConfig'" /> <baseConfig v-show="activeTab === 'baseConfig'" />
<pageConfig v-if="isInitPageConfig" v-show="activeTab === 'pageConfig'" /> <pageConfig v-if="isInitPageConfig" v-show="activeTab === 'pageConfig'" />
<authConfig v-if="isInitAuthConfig" v-show="activeTab === 'authConfig'" ref="authConfigRef" /> <authConfig v-if="isInitAuthConfig" v-show="activeTab === 'authConfig'" ref="authConfigRef" />
<memoryCleanup v-if="isInitMemoryCleanup" v-show="activeTab === 'memoryCleanup'" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -15,6 +16,7 @@
import MsTabCard from '@/components/pure/ms-tab-card/index.vue'; import MsTabCard from '@/components/pure/ms-tab-card/index.vue';
import authConfig, { AuthConfigInstance } from './components/authConfig.vue'; import authConfig, { AuthConfigInstance } from './components/authConfig.vue';
import baseConfig from './components/baseConfig.vue'; import baseConfig from './components/baseConfig.vue';
import memoryCleanup from './components/memoryCleanup.vue';
import pageConfig from './components/pageConfig.vue'; import pageConfig from './components/pageConfig.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
@ -25,11 +27,13 @@
const activeTab = ref((route.query.tab as string) || 'baseConfig'); const activeTab = ref((route.query.tab as string) || 'baseConfig');
const isInitPageConfig = ref(activeTab.value === 'pageConfig'); const isInitPageConfig = ref(activeTab.value === 'pageConfig');
const isInitAuthConfig = ref(activeTab.value === 'authConfig'); const isInitAuthConfig = ref(activeTab.value === 'authConfig');
const isInitMemoryCleanup = ref(activeTab.value === 'memoryCleanup');
const authConfigRef = ref<AuthConfigInstance | null>(); const authConfigRef = ref<AuthConfigInstance | null>();
const tabList = [ const tabList = [
{ key: 'baseConfig', title: t('system.config.baseConfig') }, { key: 'baseConfig', title: t('system.config.baseConfig') },
{ key: 'pageConfig', title: t('system.config.pageConfig') }, { key: 'pageConfig', title: t('system.config.pageConfig') },
{ key: 'authConfig', title: t('system.config.authConfig') }, { key: 'authConfig', title: t('system.config.authConfig') },
{ key: 'memoryCleanup', title: t('system.config.memoryCleanup') },
]; ];
watch( watch(
@ -39,6 +43,8 @@
isInitPageConfig.value = true; isInitPageConfig.value = true;
} else if (val === 'authConfig' && !isInitAuthConfig.value) { } else if (val === 'authConfig' && !isInitAuthConfig.value) {
isInitAuthConfig.value = true; isInitAuthConfig.value = true;
} else if (val === 'memoryCleanup' && !isInitMemoryCleanup.value) {
isInitMemoryCleanup.value = true;
} }
}, },
{ {

View File

@ -207,4 +207,16 @@ export default {
'system.config.auth.testLoginPasswordNotNull': 'LDAP login password cannot be empty', 'system.config.auth.testLoginPasswordNotNull': 'LDAP login password cannot be empty',
'system.config.auth.testLoginSuccess': 'LDAP login successful', 'system.config.auth.testLoginSuccess': 'LDAP login successful',
'system.config.auth.testLoginCancel': 'Cancel', 'system.config.auth.testLoginCancel': 'Cancel',
'system.config.memoryCleanup': 'Memory cleanup',
'system.config.memoryCleanup.log': 'Log',
'system.config.memoryCleanup.history': 'Change history',
'system.config.memoryCleanup.keepTime': 'Retention time',
'system.config.memoryCleanup.keepTimeTip': 'The system will perform cleanup at 00:00 every day',
'system.config.memoryCleanup.day': 'Day',
'system.config.memoryCleanup.month': 'Month',
'system.config.memoryCleanup.year': 'Year',
'system.config.memoryCleanup.setSuccess': 'Setup successful',
'system.config.memoryCleanup.saveCount': 'Reserved quantity',
'system.config.memoryCleanup.saveCountTip':
'It is effective for all projects in the system. Use case change history that exceeds the settings will be cleared and will take effect immediately after the update.',
}; };

View File

@ -202,4 +202,15 @@ export default {
'system.config.auth.testLoginPasswordNotNull': 'LDAP 登录密码不能为空', 'system.config.auth.testLoginPasswordNotNull': 'LDAP 登录密码不能为空',
'system.config.auth.testLoginCancel': '取消测试', 'system.config.auth.testLoginCancel': '取消测试',
'system.config.auth.testLoginSuccess': 'LDAP 登录成功', 'system.config.auth.testLoginSuccess': 'LDAP 登录成功',
'system.config.memoryCleanup': '内存清理',
'system.config.memoryCleanup.log': '日志',
'system.config.memoryCleanup.history': '变更历史',
'system.config.memoryCleanup.keepTime': '保留时长',
'system.config.memoryCleanup.keepTimeTip': '系统会在每天 00:00 执行清理',
'system.config.memoryCleanup.day': '天',
'system.config.memoryCleanup.month': '月',
'system.config.memoryCleanup.year': '年',
'system.config.memoryCleanup.setSuccess': '设置成功',
'system.config.memoryCleanup.saveCount': '保留条数',
'system.config.memoryCleanup.saveCountTip': '对系统内所有的项目生效,超出设置的用例变更历史会被清除,更新后立即生效',
}; };

View File

@ -400,6 +400,10 @@
label: 'system.log.operateType.logout', label: 'system.log.operateType.logout',
value: 'LOGOUT', value: 'LOGOUT',
}, },
{
label: 'system.log.operateType.associate',
value: 'ASSOCIATE',
},
{ {
label: 'system.log.operateType.disassociate', label: 'system.log.operateType.disassociate',
value: 'DISASSOCIATE', value: 'DISASSOCIATE',

View File

@ -30,6 +30,7 @@ export default {
'system.log.operateType.recover': 'Recover', 'system.log.operateType.recover': 'Recover',
'system.log.operateType.logout': 'Logout', 'system.log.operateType.logout': 'Logout',
'system.log.operateType.disassociate': 'Disassociate', 'system.log.operateType.disassociate': 'Disassociate',
'system.log.operateType.associate': 'Associate',
'system.log.operateType.archived': 'Archived', 'system.log.operateType.archived': 'Archived',
'system.log.log': 'Operation log', 'system.log.log': 'Operation log',
'system.log.time': 'Operation time', 'system.log.time': 'Operation time',

View File

@ -30,6 +30,7 @@ export default {
'system.log.operateType.recover': '恢复', 'system.log.operateType.recover': '恢复',
'system.log.operateType.logout': '登出', 'system.log.operateType.logout': '登出',
'system.log.operateType.disassociate': '取消关联', 'system.log.operateType.disassociate': '取消关联',
'system.log.operateType.associate': '关联',
'system.log.operateType.archived': '归档', 'system.log.operateType.archived': '归档',
'system.log.log': '操作日志', 'system.log.log': '操作日志',
'system.log.time': '操作时间', 'system.log.time': '操作时间',