feat(系统设置): 授权管理到期需求添加&环境bug&优化用例管理菜单空白问题

This commit is contained in:
xinxin.wu 2024-04-19 22:38:15 +08:00 committed by Craftsman
parent 08b638b0d6
commit c4744ceeb2
34 changed files with 264 additions and 678 deletions

View File

@ -5,6 +5,7 @@ import {
AddDemandUrl, AddDemandUrl,
AddDependOnRelationUrl, AddDependOnRelationUrl,
AssociatedDebuggerUrl, AssociatedDebuggerUrl,
associatedProjectOptionsUrl,
BatchAssociationDemandUrl, BatchAssociationDemandUrl,
BatchCopyCaseUrl, BatchCopyCaseUrl,
BatchDeleteCaseUrl, BatchDeleteCaseUrl,
@ -94,6 +95,7 @@ import type {
UpdateModule, UpdateModule,
} from '@/models/caseManagement/featureCase'; } from '@/models/caseManagement/featureCase';
import type { CommonList, ModuleTreeNode, MoveModules, TableQueryParams } from '@/models/common'; import type { CommonList, ModuleTreeNode, MoveModules, TableQueryParams } from '@/models/common';
import { ProjectListItem } from '@/models/setting/project';
// 获取模块树 // 获取模块树
export function getCaseModuleTree(params: TableQueryParams) { export function getCaseModuleTree(params: TableQueryParams) {
@ -421,5 +423,9 @@ export function dragSort(data: DragCase) {
export function getChangeHistoryList(data: TableQueryParams) { export function getChangeHistoryList(data: TableQueryParams) {
return MSR.post<CommonList<ChangeHistoryItem>>({ url: getChangeHistoryListUrl, data }); return MSR.post<CommonList<ChangeHistoryItem>>({ url: getChangeHistoryListUrl, data });
} }
// 获取已关联缺陷列表
export function getAssociatedProjectOptions(orgId: string, module: string) {
return MSR.get<ProjectListItem[]>({ url: `${associatedProjectOptionsUrl}/${orgId}/${module}` });
}
export default {}; export default {};

View File

@ -149,3 +149,5 @@ export const dragSortUrl = '/functional/case/edit/pos';
export const getChangeHistoryListUrl = '/functional/case/operation-history'; export const getChangeHistoryListUrl = '/functional/case/operation-history';
// 取消关联用例 // 取消关联用例
export const cancelDisassociate = '/functional/case/test/disassociate/case'; export const cancelDisassociate = '/functional/case/test/disassociate/case';
// 关联用例关联功能用例项目下拉
export const associatedProjectOptionsUrl = '/project/list/options';

View File

@ -363,7 +363,7 @@
import { TableColumnData, TableData } from '@arco-design/web-vue'; import { TableColumnData, TableData } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { statusCodeOptions } from '@/components/pure/ms-advance-filter'; import { EQUAL, statusCodeOptions } from '@/components/pure/ms-advance-filter';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types'; import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import { TableOperationColumn } from '@/components/business/ms-user-group-comp/authTable.vue'; import { TableOperationColumn } from '@/components/business/ms-user-group-comp/authTable.vue';
import fastExtraction from '@/views/api-test/components/fastExtraction/index.vue'; import fastExtraction from '@/views/api-test/components/fastExtraction/index.vue';
@ -513,6 +513,7 @@
variableType: RequestExtractEnvType.TEMPORARY, variableType: RequestExtractEnvType.TEMPORARY,
extractScope: RequestExtractScope.BODY, extractScope: RequestExtractScope.BODY,
expression: '', expression: '',
condition: EQUAL.value,
extractType: RequestExtractExpressionEnum.JSON_PATH, extractType: RequestExtractExpressionEnum.JSON_PATH,
expressionMatchingRule: RequestExtractExpressionRuleType.EXPRESSION, expressionMatchingRule: RequestExtractExpressionRuleType.EXPRESSION,
resultMatchingRule: RequestExtractResultMatchingRule.RANDOM, resultMatchingRule: RequestExtractResultMatchingRule.RANDOM,

View File

@ -24,11 +24,24 @@
<div class="flex h-full"> <div class="flex h-full">
<div class="w-[292px] border-r border-[var(--color-text-n8)] p-[16px]"> <div class="w-[292px] border-r border-[var(--color-text-n8)] p-[16px]">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<MsProjectSelect <div v-if="!props.hideProjectSelect" class="flex w-full flex-1">
v-if="innerProject && !props.hideProjectSelect" <a-select
v-model:project="innerProject" v-model="innerProject"
class="mb-[16px]" class="mb-[16px] flex-1"
/> :default-value="innerProject"
allow-search
:placeholder="t('common.pleaseSelect')"
>
<template #arrow-icon>
<icon-caret-down />
</template>
<a-tooltip v-for="item of projectList" :key="item.id" :mouse-enter-delay="500" :content="item.name">
<a-option :value="item.id" :class="item.id === innerProject ? 'arco-select-option-selected' : ''">
{{ item.name }}
</a-option>
</a-tooltip>
</a-select>
</div>
<a-select v-if="caseType === 'API'" v-model="protocolType" class="mb-[16px] ml-2 max-w-[90px]"> <a-select v-if="caseType === 'API'" v-model="protocolType" class="mb-[16px] ml-2 max-w-[90px]">
<a-option v-for="item of protocolOptions" :key="item" :value="item">{{ item }}</a-option> <a-option v-for="item of protocolOptions" :key="item" :value="item">{{ item }}</a-option>
</a-select> </a-select>
@ -159,13 +172,15 @@
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 { getCustomFieldsTable } from '@/api/modules/case-management/featureCase'; import { getAssociatedProjectOptions, getCustomFieldsTable } from '@/api/modules/case-management/featureCase';
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';
import type { CaseManagementTable } from '@/models/caseManagement/featureCase'; import type { CaseManagementTable } from '@/models/caseManagement/featureCase';
import type { CommonList, ModuleTreeNode, TableQueryParams } from '@/models/common'; import type { CommonList, ModuleTreeNode, TableQueryParams } from '@/models/common';
import type { ProjectListItem } from '@/models/setting/project';
import { CaseLinkEnum } from '@/enums/caseEnum';
import { CaseManagementRouteEnum } from '@/enums/routeEnum'; import { CaseManagementRouteEnum } from '@/enums/routeEnum';
import type { CaseLevel } from './types'; import type { CaseLevel } from './types';
@ -185,7 +200,7 @@
getTableFunc: (params: TableQueryParams) => Promise<CommonList<CaseManagementTable>>; // getTableFunc: (params: TableQueryParams) => Promise<CommonList<CaseManagementTable>>; //
tableParams?: TableQueryParams; // tableParams?: TableQueryParams; //
okButtonDisabled?: boolean; // okButtonDisabled?: boolean; //
currentSelectCase: string | number | Record<string, any> | undefined; // currentSelectCase: keyof typeof CaseLinkEnum; //
moduleOptions?: { label: string; value: string }[]; // moduleOptions?: { label: string; value: string }[]; //
confirmLoading: boolean; confirmLoading: boolean;
associatedIds: string[]; // id associatedIds: string[]; // id
@ -239,7 +254,7 @@
} }
const innerVisible = useVModel(props, 'visible', emit); const innerVisible = useVModel(props, 'visible', emit);
const innerProject = useVModel(props, 'projectId', emit); const innerProject = ref<string | undefined>(props.projectId);
const protocolType = ref('HTTP'); // const protocolType = ref('HTTP'); //
const protocolOptions = ref(['HTTP']); const protocolOptions = ref(['HTTP']);
@ -323,6 +338,7 @@
const keyword = ref(''); const keyword = ref('');
const version = ref(''); const version = ref('');
const projectList = ref<ProjectListItem[]>([]);
function getCaseLevelColumn() { function getCaseLevelColumn() {
if (!props.isHiddenCaseLevel) { if (!props.isHiddenCaseLevel) {
@ -576,6 +592,17 @@
} }
} }
async function initProjectList(setDefault: boolean) {
try {
projectList.value = await getAssociatedProjectOptions(appStore.currentOrgId, caseType.value);
if (setDefault) {
innerProject.value = projectList.value[0].id;
}
} catch (error) {
console.log(error);
}
}
function searchCase() { function searchCase() {
getLoadListParams(); getLoadListParams();
loadList(); loadList();
@ -631,10 +658,14 @@
() => props.visible, () => props.visible,
(val) => { (val) => {
if (val) { if (val) {
if (!props.hideProjectSelect) {
initProjectList(true);
} else {
resetSelector(); resetSelector();
initModules(); initModules();
searchCase(); searchCase();
initFilter(); initFilter();
}
} else { } else {
cancel(); cancel();
} }
@ -655,10 +686,12 @@
watch( watch(
() => innerProject.value, () => innerProject.value,
() => { (val) => {
if (innerVisible.value) { if (val) {
searchCase(); resetSelector();
initModules(); initModules();
searchCase();
initFilter();
} }
} }
); );

View File

@ -128,7 +128,7 @@ function replaceRestParams(path: string, restMap: Record<string, any>) {
} }
// 返回最终groovyCode 代码模板片段 // 返回最终groovyCode 代码模板片段
function _groovyCodeTemplate(obj: Record<string, any>) { export function _groovyCodeTemplate(obj: Record<string, any>) {
const { requestUrl, requestMethod, headers, body } = obj; const { requestUrl, requestMethod, headers, body } = obj;
const params = `[ const params = `[
'url': '${requestUrl}', 'url': '${requestUrl}',

View File

@ -26,8 +26,12 @@
import MsSelect from '@/components/business/ms-select/index'; import MsSelect from '@/components/business/ms-select/index';
import { useUserStore } from '@/store';
import initOptionsFunc, { UserRequestTypeEnum } from './utils'; import initOptionsFunc, { UserRequestTypeEnum } from './utils';
const userStore = useUserStore();
defineOptions({ name: 'MsUserSelector' }); defineOptions({ name: 'MsUserSelector' });
export interface MsUserSelectorOption { export interface MsUserSelectorOption {

View File

@ -2,12 +2,14 @@ import { App } from 'vue';
import outerClick from './outerClick'; import outerClick from './outerClick';
import permission from './permission'; import permission from './permission';
import validateExpiration from './validateExpiration';
import validateLicense from './validateLicense'; import validateLicense from './validateLicense';
export default { export default {
install(Vue: App) { install(Vue: App) {
Vue.directive('permission', permission); Vue.directive('permission', permission);
Vue.directive('xpack', validateLicense); Vue.directive('xpack', validateLicense);
Vue.directive('expire', validateExpiration);
Vue.directive('outer', outerClick); Vue.directive('outer', outerClick);
}, },
}; };

View File

@ -0,0 +1,26 @@
import { useAppStore } from '@/store';
import useLicenseStore from '@/store/modules/setting/license';
/**
* ,TODO:校验license
* @param el dom
*/
function checkHasLicenseExpiration(el: HTMLElement) {
const licenseStore = useLicenseStore();
const appStore = useAppStore();
const isValid = licenseStore.expiredDuring && appStore.packageType === 'enterprise';
if (!isValid && el.parentNode) {
el.parentNode.removeChild(el);
}
}
export default {
mounted(el: HTMLElement) {
checkHasLicenseExpiration(el);
},
updated(el: HTMLElement) {
checkHasLicenseExpiration(el);
},
};

View File

@ -20,4 +20,12 @@ export enum LastExecuteResults {
FAILED = 'FAILED', FAILED = 'FAILED',
} }
export enum CaseLinkEnum {
API = 'API',
SCENARIO = 'SCENARIO',
UI = 'UI',
PERFORMANCE = 'PERFORMANCE',
FUNCTIONAL = 'FUNCTIONAL',
}
export default {}; export default {};

View File

@ -1,11 +1,14 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import dayjs from 'dayjs';
import { getLicenseInfo } from '@/api/modules/setting/authorizedManagement'; import { getLicenseInfo } from '@/api/modules/setting/authorizedManagement';
const useLicenseStore = defineStore('license', { const useLicenseStore = defineStore('license', {
persist: true, persist: true,
state: (): { status: string | null } => ({ state: (): { status: string | null; expiredDuring: boolean; expiredDays: number } => ({
status: '', status: '',
expiredDuring: false,
expiredDays: 0,
}), }),
actions: { actions: {
setLicenseStatus(status: string) { setLicenseStatus(status: string) {
@ -17,6 +20,21 @@ const useLicenseStore = defineStore('license', {
hasLicense() { hasLicense() {
return this.status && this.status === 'valid'; return this.status && this.status === 'valid';
}, },
getExpirationTime(resTime: string) {
const today = Date.now();
const startDate = dayjs(today).format('YYYY-MM-DD');
const endDate = dayjs(resTime);
const daysDifference = endDate.diff(startDate, 'day');
this.expiredDays = daysDifference;
if (daysDifference <= 30 && daysDifference >= 0) {
this.expiredDuring = true;
} else if (daysDifference <= 0 && daysDifference >= -30) {
this.expiredDuring = true;
} else {
this.expiredDuring = false;
}
},
// license校验 // license校验
async getValidateLicense() { async getValidateLicense() {
try { try {
@ -25,6 +43,8 @@ const useLicenseStore = defineStore('license', {
return; return;
} }
this.setLicenseStatus(result.status); this.setLicenseStatus(result.status);
// 计算license时间
this.getExpirationTime(result.license.expired);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }

View File

@ -30,7 +30,7 @@
<transition name="fade"> <transition name="fade">
<div v-show="!expandIds.includes(item.value) && isShowContent(item.value)" class="expandContent"> <div v-show="!expandIds.includes(item.value) && isShowContent(item.value)" class="expandContent">
<div v-if="item.value === ResponseComposition.BODY" class="res-item"> <div v-if="item.value === ResponseComposition.BODY" class="res-item">
<ResBody :request-result="props.requestResult" @copy="copyScript" /> <ResBody ref="resBodyRef" :request-result="props.requestResult" @copy="copyScript" />
</div> </div>
<div v-if="!expandIds.includes(item.value) && item.value === ResponseComposition.CONSOLE" class="res-item"> <div v-if="!expandIds.includes(item.value) && item.value === ResponseComposition.CONSOLE" class="res-item">
<ResConsole :console="props.console?.trim()" /> <ResConsole :console="props.console?.trim()" />
@ -92,9 +92,12 @@
} }
} }
const { copy, isSupported } = useClipboard({ legacy: true }); const { copy, isSupported } = useClipboard({ legacy: true });
const resBodyRef = ref();
function copyScript() { function copyScript() {
const encodingFormatValue = resBodyRef.value.responseEditorRef.getEncodingCode();
if (isSupported) { if (isSupported) {
copy(props.requestResult?.responseResult.body || ''); copy(encodingFormatValue || '');
Message.success(t('common.copySuccess')); Message.success(t('common.copySuccess'));
} else { } else {
Message.warning(t('apiTestDebug.copyNotSupport')); Message.warning(t('apiTestDebug.copyNotSupport'));

View File

@ -70,7 +70,7 @@
/> />
{{ t('caseManagement.featureCase.follow') }} {{ t('caseManagement.featureCase.follow') }}
</MsButton> </MsButton>
<MsButton type="icon" status="secondary" class="!rounded-[var(--border-radius-small)]"> <MsButton type="icon" status="secondary" class="mr-2 !rounded-[var(--border-radius-small)]">
<a-dropdown position="br" :hide-on-select="false"> <a-dropdown position="br" :hide-on-select="false">
<div> <div>
<icon-more class="mr-1" /> <icon-more class="mr-1" />

View File

@ -111,6 +111,7 @@
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
import type { TableQueryParams } from '@/models/common'; import type { TableQueryParams } from '@/models/common';
import { CaseLinkEnum } from '@/enums/caseEnum';
import { CaseManagementRouteEnum } from '@/enums/routeEnum'; import { CaseManagementRouteEnum } from '@/enums/routeEnum';
import Message from '@arco-design/web-vue/es/message'; import Message from '@arco-design/web-vue/es/message';
@ -201,7 +202,7 @@
const associatedIds = ref<string[]>([]); const associatedIds = ref<string[]>([]);
const currentSelectCase = ref<string>(''); const currentSelectCase = ref<keyof typeof CaseLinkEnum>('FUNCTIONAL');
const modulesTreeParams = ref<TableQueryParams>({}); const modulesTreeParams = ref<TableQueryParams>({});
@ -233,7 +234,12 @@
} }
} }
const caseTypeOptions = ref<{ label: string; value: string }[]>([]); const caseTypeOptions = ref<{ label: string; value: string }[]>([
{
label: 'menu.caseManagement.featureCase',
value: 'FUNCTIONAL',
},
]);
const modulesCount = ref<Record<string, any>>({}); const modulesCount = ref<Record<string, any>>({});
@ -255,25 +261,6 @@
} }
} }
const moduleMaps: Record<string, { label: string; value: string }[]> = {
caseManagement: [
{
value: 'FUNCTIONAL',
label: t('menu.caseManagement.featureCase'),
},
],
};
async function getEnabledModules() {
const result = await postTabletList({ projectId: currentProjectId.value });
const caseArr = result.filter((item) => Object.keys(moduleMaps).includes(item.module));
caseArr.forEach((item: any) => {
const currentModule = moduleMaps[item.module];
caseTypeOptions.value.push(...currentModule);
});
currentSelectCase.value = caseTypeOptions.value[0].value;
}
async function searchCase() { async function searchCase() {
setKeyword(keyword.value); setKeyword(keyword.value);
setLoadListParams({ setLoadListParams({
@ -300,14 +287,12 @@
} }
onMounted(async () => { onMounted(async () => {
getEnabledModules();
getFetch(); getFetch();
}); });
watch( watch(
() => props.bugId, () => props.bugId,
() => { () => {
getEnabledModules();
getFetch(); getFetch();
} }
); );

View File

@ -803,7 +803,10 @@
function renameCopyBug() { function renameCopyBug() {
if (isCopy.value) { if (isCopy.value) {
form.value.title = `copy_${form.value.title}`; const copyName = `copy_${form.value.title}`;
if (copyName.length > 255) {
form.value.title = copyName.slice(0, 255);
}
} }
} }

View File

@ -63,7 +63,7 @@
/> />
{{ t('caseManagement.featureCase.follow') }} {{ t('caseManagement.featureCase.follow') }}
</MsButton> </MsButton>
<MsButton type="icon" status="secondary" class="!rounded-[var(--border-radius-small)]"> <MsButton type="icon" status="secondary" class="mr-2 !rounded-[var(--border-radius-small)]">
<a-dropdown position="br" :hide-on-select="false"> <a-dropdown position="br" :hide-on-select="false">
<div class="flex items-center"> <div class="flex items-center">
<icon-more class="mr-2" /> <icon-more class="mr-2" />
@ -238,7 +238,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { computed, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@ -395,14 +395,14 @@
historyCount, historyCount,
} = detail; } = detail;
const countMap: Record<string, any> = { const countMap: Record<string, any> = {
case: caseCount, case: caseCount || '0',
dependency: relateEdgeCount, dependency: relateEdgeCount || '0',
caseReview: caseReviewCount, caseReview: caseReviewCount || '0',
testPlan: testPlanCount, testPlan: testPlanCount || '0',
bug: bugCount, bug: bugCount || '0',
requirement: demandCount, requirement: demandCount || '0',
comments: commentCount, comments: commentCount || '0',
changeHistory: historyCount, changeHistory: historyCount || '0',
}; };
featureCaseStore.initCountMap(countMap); featureCaseStore.initCountMap(countMap);
} }
@ -639,6 +639,9 @@
canHide: true, canHide: true,
isShow: true, isShow: true,
}, },
];
const caseTab: TabItemType[] = [
{ {
value: 'dependency', value: 'dependency',
label: t('caseManagement.featureCase.dependency'), label: t('caseManagement.featureCase.dependency'),
@ -664,9 +667,8 @@
isShow: true, isShow: true,
}, },
]; ];
let buggerTab: TabItemType[] = [];
const moduleTabMap: Record<string, TabItemType[]> = { const buggerTab: TabItemType[] = [
bugManagement: [
{ {
value: 'requirement', value: 'requirement',
label: t('caseManagement.featureCase.requirement'), label: t('caseManagement.featureCase.requirement'),
@ -679,28 +681,17 @@
canHide: true, canHide: true,
isShow: true, isShow: true,
}, },
], ];
};
let newTabDefaultSettingList: TabItemType[] = []; //
/** const newTabDefaultSettingList = computed(() => {
* 获取开启的模块 if (appStore.currentMenuConfig.includes('bugManagement')) {
*/ return [...tabDefaultSettingList, ...buggerTab, ...caseTab];
async function getTabModule() {
buggerTab = [];
const result = await postTabletList({ projectId: currentProjectId.value });
const enableModuleArr = result.filter((item: any) => item.module === 'bugManagement');
enableModuleArr.forEach((item) => {
if (item.module === 'bugManagement') {
buggerTab.push(...moduleTabMap[item.module]);
} }
return [...tabDefaultSettingList, ...caseTab];
}); });
newTabDefaultSettingList = [...tabDefaultSettingList.slice(0, 2), ...buggerTab, ...tabDefaultSettingList.slice(2)];
}
await getTabModule(); featureCaseStore.initContentTabList([...newTabDefaultSettingList.value]);
featureCaseStore.initContentTabList(newTabDefaultSettingList);
tabSetting.value = ((await featureCaseStore.getContentTabList()) || []).filter((item) => item.isShow); tabSetting.value = ((await featureCaseStore.getContentTabList()) || []).filter((item) => item.isShow);
async function handleUploadImage(file: File) { async function handleUploadImage(file: File) {

View File

@ -51,7 +51,6 @@
</ms-base-table> </ms-base-table>
<MsCaseAssociate <MsCaseAssociate
v-model:visible="innerVisible" v-model:visible="innerVisible"
v-model:project-id="innerProject"
v-model:currentSelectCase="currentSelectCase" v-model:currentSelectCase="currentSelectCase"
:ok-button-disabled="associateForm.reviewers.length === 0" :ok-button-disabled="associateForm.reviewers.length === 0"
:get-modules-func="getPublicLinkModuleTree" :get-modules-func="getPublicLinkModuleTree"
@ -95,6 +94,7 @@
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
import type { TableQueryParams } from '@/models/common'; import type { TableQueryParams } from '@/models/common';
import { CaseLinkEnum } from '@/enums/caseEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
import Message from '@arco-design/web-vue/es/message'; import Message from '@arco-design/web-vue/es/message';
@ -190,19 +190,17 @@
const associatedIds = ref<string[]>([]); const associatedIds = ref<string[]>([]);
const currentSelectCase = ref<string>(''); const currentSelectCase = ref<keyof typeof CaseLinkEnum>('API');
const modulesTreeParams = ref<TableQueryParams>({}); const modulesTreeParams = ref<TableQueryParams>({});
const getTableParams = ref<TableQueryParams>({}); const getTableParams = ref<TableQueryParams>({});
function handleSelect(value: string | number | Record<string, any> | undefined) { function handleSelect(value: string | number | Record<string, any> | undefined) {
currentSelectCase.value = value as string; currentSelectCase.value = value as keyof typeof CaseLinkEnum;
innerVisible.value = true; innerVisible.value = true;
} }
const caseTypeOptions = ref<{ label: string; value: string }[]>([]);
const modulesCount = ref<Record<string, any>>({}); const modulesCount = ref<Record<string, any>>({});
const confirmLoading = ref<boolean>(false); const confirmLoading = ref<boolean>(false);
@ -235,8 +233,8 @@
} }
} }
const moduleMaps: Record<string, { label: string; value: string }[]> = { // @desc 使
apiTest: [ const caseTypeOptions = ref<{ label: string; value: keyof typeof CaseLinkEnum }[]>([
{ {
value: 'API', value: 'API',
label: t('caseManagement.featureCase.apiCase'), label: t('caseManagement.featureCase.apiCase'),
@ -245,34 +243,16 @@
value: 'SCENARIO', value: 'SCENARIO',
label: t('caseManagement.featureCase.sceneCase'), label: t('caseManagement.featureCase.sceneCase'),
}, },
], // TODO
// uiTest: [
// { // {
// value: 'UI', // value: 'UI',
// label: t('caseManagement.featureCase.uiCase'), // label: t('caseManagement.featureCase.uiCase'),
// }, // },
// ],
// loadTest: [
// { // {
// value: 'PERFORMANCE', // value: 'PERFORMANCE',
// label: t('caseManagement.featureCase.propertyCase'), // label: t('caseManagement.featureCase.propertyCase'),
// }, // },
// ], ]);
};
async function getEnabledModules() {
// const result = await postTabletList({ projectId: currentProjectId.value });
// const caseArr = result.filter((item) => Object.keys(moduleMaps).includes(item.module));
// caseArr.forEach((item: any) => {
// const currentModule = moduleMaps[item.module];
// caseTypeOptions.value.push(...currentModule);
// });
Object.keys(moduleMaps).forEach((item: any) => {
const currentModule = moduleMaps[item];
caseTypeOptions.value.push(...currentModule);
});
currentSelectCase.value = caseTypeOptions.value[0].value;
}
async function cancelLink(record: any) { async function cancelLink(record: any) {
try { try {
@ -292,20 +272,8 @@
setKeyword(keyword.value); setKeyword(keyword.value);
await loadList(); await loadList();
} }
const activeTab = computed(() => featureCaseStore.activeTab);
// watch(
// () => activeTab.value,
// (val) => {
// if (val === 'case') {
// getEnabledModules();
// getFetch();
// }
// }
// );
onMounted(async () => { onMounted(async () => {
getEnabledModules();
getFetch(); getFetch();
}); });
</script> </script>

View File

@ -88,6 +88,7 @@
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import { BaseAssociateCaseRequest } from '@/models/caseManagement/caseReview'; import { BaseAssociateCaseRequest } from '@/models/caseManagement/caseReview';
import { CaseLinkEnum } from '@/enums/caseEnum';
import { ProjectManagementRouteEnum } from '@/enums/routeEnum'; import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
const props = defineProps<{ const props = defineProps<{
@ -142,7 +143,7 @@
} }
} }
const currentSelectCase = ref<string | number | Record<string, any> | undefined>(''); const currentSelectCase = ref<keyof typeof CaseLinkEnum>('FUNCTIONAL');
// const associatedIds = useVModel(props, 'associatedIds', emit); // const associatedIds = useVModel(props, 'associatedIds', emit);
const confirmLoading = ref<boolean>(false); const confirmLoading = ref<boolean>(false);

View File

@ -1,527 +0,0 @@
import { Language, LanguageEnum } from '@/components/pure/ms-code-editor/types';
import { useI18n } from '@/hooks/useI18n';
import type { CommonScriptMenu } from '@/models/projectManagement/commonScript';
const { t } = useI18n();
export const SCRIPT_MENU: CommonScriptMenu[] = [
{
title: t('project.code_segment.importApiTest'),
value: 'api_definition',
command: 'api_definition',
},
{
title: t('project.code_segment.newApiTest'),
value: 'new_api_request',
command: 'new_api_request',
},
{
title: t('project.processor.codeTemplateGetVariable'),
value: 'vars.get("variable_name")',
},
{
title: t('project.processor.codeTemplateSetVariable'),
value: 'vars.put("variable_name", "variable_value")',
},
{
title: t('project.processor.codeTemplateGetResponseHeader'),
value: 'prev.getResponseHeaders()',
},
{
title: t('project.processor.codeTemplateGetResponseCode'),
value: 'prev.getResponseCode()',
},
{
title: t('project.processor.codeTemplateGetResponseResult'),
value: 'prev.getResponseDataAsString()',
},
{
title: t('project.processor.paramEnvironmentSetGlobalVariable'),
value: `vars.put(\${__metersphere_env_id}+"key","value");\nvars.put("key","value");`,
},
{
title: t('project.processor.insertPublicScript'),
value: 'custom_function',
command: 'custom_function',
},
{
title: t('project.processor.terminationTest'),
value: 'api_stop',
command: 'api_stop',
},
];
// 处理groovyCode 请求头
function getGroovyHeaders(requestHeaders: Record<string, any>) {
let headers = '[';
let index = 1;
// for (const [k, v] of requestHeaders) {
// if (index !== 1) {
// headers += ',';
// }
// // 拼装
// headers += `'${k}':'${v}'`;
// index++;
// }
requestHeaders.forEach(([k, v]: any[]) => {
if (index !== 1) {
headers += ',';
}
headers += `'${k}':'${v}'`;
index++;
});
headers += ']';
return headers;
}
// 解析请求url
function getRequestPath(requestArgs: any, requestPath: string) {
if (requestArgs.size > 0) {
requestPath += '?';
let index = 1;
requestArgs.forEach(([k, v]: any[]) => {
if (index !== 1) {
requestPath += '&';
}
requestPath = `${requestPath + k}=${v}`;
index++;
});
}
return requestPath;
}
// 处理mockPath
function getMockPath(domain: string, port: string, socket: string) {
if (domain === socket || !port) {
return '';
}
const str = `${domain}:${port}`;
// 获取socket之后的路径
return socket.substring(str.length);
}
// 处理请求参数
function replaceRestParams(path: string, restMap: Map<string, string>) {
if (!path) {
return path;
}
let arr: any[] | null = path.match(/{([\w]+)}/g);
if (Array.isArray(arr) && arr.length > 0) {
arr = Array.from(new Set(arr));
arr.forEach((str) => {
try {
const temp = str.substr(1);
const param = temp.substring(0, temp.length - 1);
if (str && restMap.has(param)) {
path = path.replace(new RegExp(str, 'g'), restMap.get(param) || '');
}
} catch (e) {
// nothing
}
});
}
return path;
}
// 返回最终groovyCode 代码模板片段
function _groovyCodeTemplate(obj: Record<string, any>) {
const { requestUrl, requestMethod, headers, body } = obj;
const params = `[
'url': '${requestUrl}',
'method': '${requestMethod}', // POST/GET
'headers': ${headers}, // 请求headers 例:{'Content-type':'application/json'}
'data': ${body} // 参数
]`;
return `import groovy.json.JsonOutput
import groovy.json.JsonSlurper
def params = ${params}
def headers = params['headers']
// json数据
def data = params['data']
def conn = new URL(params['url']).openConnection()
conn.setRequestMethod(params['method'])
if (headers) {
headers.each {
k,v -> conn.setRequestProperty(k, v);
}
}
if (data) {
// 输出请求参数
log.info(data)
conn.doOutput = true
def writer = new OutputStreamWriter(conn.outputStream)
writer.write(data)
writer.flush()
writer.close()
}
log.info(conn.content.text)
`;
}
// 处理groovyCode语言
function groovyCode(requestObj: Record<string, any>) {
const {
requestHeaders = new Map(),
requestBody = '',
domain = '',
port = '',
requestMethod = '',
host = '',
protocol = '',
requestArguments = new Map(),
requestRest = new Map(),
requestBodyKvs = new Map(),
bodyType,
} = requestObj;
let { requestPath = '' } = requestObj;
let requestUrl = '';
if (requestMethod.toLowerCase() === 'get' && requestBodyKvs) {
// 如果是get方法要将kv值加入argument中
requestBodyKvs.forEach(([k, v]: any[]) => {
requestArguments.set(k, v);
});
}
requestPath = getRequestPath(requestArguments, requestPath);
const path = getMockPath(domain, port, host);
requestPath = path + replaceRestParams(requestPath, requestRest);
if (protocol && host && requestPath) {
requestUrl = `${protocol}://${domain}${port ? `:${port}` : ''}${requestPath}`;
}
let body = JSON.stringify(requestBody);
if (requestMethod === 'POST' && bodyType === 'kvs') {
body = '"';
requestBodyKvs.forEach(([k, v]: any[]) => {
if (body !== '"') {
body += '&';
}
body += `${k}=${v}`;
});
body += '"';
}
if (bodyType && bodyType.toUpperCase() === 'RAW') {
requestHeaders.set('Content-type', 'text/plain');
}
const headers = getGroovyHeaders(requestHeaders);
const obj = { requestUrl, requestMethod, headers, body };
return _groovyCodeTemplate(obj);
}
// 获取请求头
function getHeaders(requestHeaders: Map<string, string>) {
let headers = '{';
let index = 1;
requestHeaders.forEach(([k, v]) => {
if (index !== 1) {
headers += ',';
}
// 拼装
headers += `'${k}':'${v}'`;
index++;
});
headers += '}';
return headers;
}
// 获取pythonCode 模板
function _pythonCodeTemplate(obj: Record<string, any>) {
const { requestBodyKvs, requestPath, requestMethod, connType, domain, port } = obj;
let { headers } = obj;
let reqBody = obj.requestBody;
if (requestMethod.toLowerCase() === 'post' && obj.bodyType === 'kvs' && obj.requestBodyKvs) {
reqBody = 'urllib.urlencode({';
requestBodyKvs.forEach(([k, v]: any[]) => {
reqBody += `'${k}':'${v}'`;
});
reqBody += `})`;
if (headers === '{}') {
headers = "{'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'text/plain'}";
}
}
const host = domain + (port ? `:${port}` : '');
return `import httplib,urllib
params = ${reqBody} # {'username':'test'}
headers = ${headers} # {'Content-Type':'application/json'} {'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'text/plain'}
host = '${host}'
path = '${requestPath}'
method = '${requestMethod}' # POST/GET
conn = httplib.${connType}(host)
conn.request(method, path, params, headers)
res = conn.getresponse()
data = unicode(res.read(), 'utf-8')
log.info(data)
`;
}
// 处理pythonCode语言
function pythonCode(requestObj: Record<string, any>) {
const {
requestHeaders = new Map(),
requestMethod = '',
host = '',
domain = '',
port = '',
protocol = 'http',
requestArguments = new Map(),
requestBodyKvs = new Map(),
bodyType,
requestRest = new Map(),
} = requestObj;
let { requestBody = '', requestPath = '/' } = requestObj;
let connType = 'HTTPConnection';
if (protocol === 'https') {
connType = 'HTTPSConnection';
}
const headers = getHeaders(requestHeaders);
requestBody = requestBody ? JSON.stringify(requestBody) : '{}';
if (requestMethod.toLowerCase() === 'get' && requestBodyKvs) {
requestBodyKvs.forEach(([k, v]: any[]) => {
requestArguments.set(k, v);
});
}
requestPath = getRequestPath(requestArguments, requestPath);
const path = getMockPath(domain, port, host);
requestPath = path + replaceRestParams(requestPath, requestRest);
const obj = { requestBody, headers, requestPath, requestMethod, requestBodyKvs, bodyType, connType, domain, port };
return _pythonCodeTemplate(obj);
}
// 获取javaBeanshell代码模板
function _beanshellTemplate(obj: Record<string, any>) {
const {
requestHeaders = new Map(),
requestBodyKvs = new Map(),
bodyType = '',
requestMethod = 'GET',
protocol = 'http',
requestArguments = new Map(),
domain = '',
host = '',
port = '',
requestRest = new Map(),
} = obj;
let { requestPath = '/', requestBody = '' } = obj;
const path = getMockPath(domain, port, host);
requestPath = path + replaceRestParams(requestPath, requestRest);
let uri = `new URIBuilder()
.setScheme("${protocol}")
.setHost("${domain}")
.setPath("${requestPath}")
`;
// http 请求类型
const method = requestMethod.toLowerCase().replace(/^\S/, (s: string) => s.toUpperCase());
const httpMethodCode = `Http${method} request = new Http${method}(uri);`;
// 设置参数
requestArguments.forEach(([k, v]: any[]) => {
uri += `.setParameter("${k}", "${v}")`;
});
if (method === 'Get' && requestBodyKvs) {
requestBodyKvs.forEach(([k, v]: any[]) => {
uri += `.setParameter("${k}", "${v}")`;
});
}
let postKvsParam = '';
if (method === 'Post') {
// 设置post参数
requestBodyKvs.forEach(([k, v]: any[]) => {
postKvsParam += `nameValueList.add(new BasicNameValuePair("${k}", "${v}"));\r\n`;
});
if (postKvsParam !== '') {
postKvsParam = `List nameValueList = new ArrayList();\r\n${postKvsParam}`;
}
}
if (port) {
uri += `.setPort(${port}) // int类型端口
`;
uri += ` .build();`;
} else {
uri += `// .setPort(${port}) // int类型端口
`;
uri += ` .build();`;
}
// 设置请求头
let setHeader = '';
requestHeaders.forEach(([k, v]: any[]) => {
setHeader = `${setHeader}request.setHeader("${k}", "${v}");\n`;
});
try {
requestBody = JSON.stringify(requestBody);
if (!requestBody || requestBody === 'null') {
requestBody = '';
}
} catch (e) {
requestBody = '';
}
let postMethodCode = '';
if (requestMethod === 'POST') {
if (bodyType === 'kvs') {
postMethodCode = `${postKvsParam}\r\n request.setEntity(new UrlEncodedFormEntity(nameValueList, "UTF-8"));`;
} else {
postMethodCode = `request.setEntity(new StringEntity(StringEscapeUtils.unescapeJava(payload)));`;
}
}
return `import java.net.URI;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.*;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.http.entity.StringEntity;
import java.util.*;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
// 参数
String payload = ${requestBody};
// 定义请求的参数
URI uri = ${uri}
// 创建http请求
${httpMethodCode}
${setHeader}
${postMethodCode}
log.info(uri.toString());
//response 对象
CloseableHttpResponse response = null;
response = httpclient.execute(request);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
String content = EntityUtils.toString(response.getEntity(), "UTF-8");
log.info(content);
}`;
}
// 处理java语言
function javaCode(requestObj: Record<string, any>) {
return _beanshellTemplate(requestObj);
}
// 获取js语言代码模板
function _jsTemplate(obj: Record<string, any>) {
const {
requestHeaders = new Map(),
requestMethod = 'GET',
protocol = 'http',
requestArguments = new Map(),
host = '',
domain = '',
port = '',
requestBodyKvs = new Map(),
bodyType = '',
requestRest = new Map(),
} = obj;
let url = '';
let { requestBody = '', requestPath = '/' } = obj;
requestPath = replaceRestParams(requestPath, requestRest);
if (protocol && domain && port) {
const path = getMockPath(domain, port, host);
requestPath = path + requestPath;
url = `${protocol}://${domain}${port ? `:${port}` : ''}${requestPath}`;
} else if (protocol && domain) {
url = `${protocol}://${domain}${requestPath}`;
}
if (requestMethod.toLowerCase() === 'get' && requestBodyKvs) {
// 如果是get方法要将kv值加入argument中
requestBodyKvs.forEach(([k, v]: any[]) => {
requestArguments.set(k, v);
});
}
url = getRequestPath(requestArguments, url);
try {
requestBody = JSON.stringify(requestBody);
} catch (e) {
requestBody = '';
}
let connStr = '';
if (bodyType && bodyType.toUpperCase() === 'RAW') {
requestHeaders.set('Content-type', 'text/plain');
}
requestHeaders.forEach(([k, v]: any[]) => {
connStr += `conn.setRequestProperty("${k}","${v}");\n`;
});
if (requestMethod === 'POST' && bodyType === 'kvs') {
requestBody = '"';
requestBodyKvs.forEach(([k, v]: any[]) => {
if (requestBody !== '"') {
requestBody += '&';
}
requestBody += `${k}=${v}`;
});
requestBody += '"';
}
let postParamExecCode = '';
if (requestBody && requestBody !== '' && requestBody !== '""') {
postParamExecCode = `
var opt = new java.io.DataOutputStream(conn.getOutputStream());
var t = (new java.lang.String(parameterData)).getBytes("utf-8");
opt.write(t);
opt.flush();
opt.close();
`;
}
return `var urlStr = "${url}"; // 请求地址
var requestMethod = "${requestMethod}"; // 请求类型
var parameterData = ${requestBody}; // 请求参数
var url = new java.net.URL(urlStr);
var conn = url.openConnection();
conn.setRequestMethod(requestMethod);
conn.setDoOutput(true);
${connStr}conn.connect();
${postParamExecCode}
var res = "";
var rspCode = conn.getResponseCode();
if (rspCode == 200) {
var ipt = conn.getInputStream();
var reader = new java.io.BufferedReader(new java.io.InputStreamReader(ipt, "UTF-8"));
var lines;
while((lines = reader.readLine()) !== null) {
res += lines;
}
}
log.info(res);
`;
}
// 处理js语言
function jsCode(requestObj: Record<string, any>) {
return _jsTemplate(requestObj);
}
export function getCodeTemplate(language: Language, requestObj: any) {
switch (language) {
case LanguageEnum.GROOVY:
return groovyCode(requestObj);
case LanguageEnum.PYTHON:
return pythonCode(requestObj);
case LanguageEnum.BEANSHELL:
return javaCode(requestObj);
case LanguageEnum.NASHORNSCRIPT:
return jsCode(requestObj);
case LanguageEnum.RHINOSCRIPT:
return jsCode(requestObj);
case LanguageEnum.JAVASCRIPT:
return jsCode(requestObj);
default:
return '';
}
}
export default {};

View File

@ -8,6 +8,7 @@
:on-before-ok="beforeConfirm" :on-before-ok="beforeConfirm"
:cancel-button-props="{ disabled: loading }" :cancel-button-props="{ disabled: loading }"
@popup-visible-change="reset" @popup-visible-change="reset"
@cancel="handleCancel()"
> >
<template #content> <template #content>
<div class="mb-[8px] text-[14px] font-medium text-[var(--color-text-1)]">{{ <div class="mb-[8px] text-[14px] font-medium text-[var(--color-text-1)]">{{

View File

@ -348,8 +348,8 @@
}; };
try { try {
await batchModalRef.value.batchRequestFun(addProjectUserGroup, params); await batchModalRef.value.batchRequestFun(addProjectUserGroup, params);
initData();
resetSelector(); resetSelector();
initData();
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);

View File

@ -345,8 +345,8 @@
params.userRoleIds = target; params.userRoleIds = target;
} }
if (currentType) await batchModalRef.value.batchRequestFun(currentType.request, params); if (currentType) await batchModalRef.value.batchRequestFun(currentType.request, params);
initData();
resetSelector(); resetSelector();
initData();
}; };
// //

View File

@ -619,10 +619,15 @@
loading.value = true; loading.value = true;
const res = await templateApiMaps[props.mode].detail(route.query.id as string); const res = await templateApiMaps[props.mode].detail(route.query.id as string);
const { name, customFields, systemFields } = res; const { name, customFields, systemFields } = res;
let copyName = `copy_${name}`;
if (copyName.length > 255) {
copyName = copyName.slice(0, 255);
}
templateForm.value = { templateForm.value = {
...res, ...res,
name: route.params.mode === 'copy' ? `copy_${name}` : name, name: route.params.mode === 'copy' ? copyName : name,
}; };
if (route.params.mode === 'copy') { if (route.params.mode === 'copy') {
templateForm.value.id = undefined; templateForm.value.id = undefined;
} }

View File

@ -0,0 +1,21 @@
<template>
<div v-expire class="mb-4">
<a-alert type="warning">{{
licenseStore.expiredDays >= 0 && licenseStore.expiredDays <= 30
? t('system.authorized.LicenseExpirationPromptLessThanThirty', { day: licenseStore.expiredDays })
: t('system.authorized.LicenseExpirationPromptGreaterThanThirty')
}}</a-alert>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import useLicenseStore from '@/store/modules/setting/license';
const licenseStore = useLicenseStore();
const { t } = useI18n();
</script>
<style scoped></style>

View File

@ -1,5 +1,6 @@
<template> <template>
<MsCard :loading="loading" simple> <MsCard :loading="loading" simple>
<ExpireAlert />
<div class="wrapper"> <div class="wrapper">
<div class="content-wrapper"> <div class="content-wrapper">
<div class="authorized_logo"> <div class="authorized_logo">
@ -119,6 +120,7 @@
import MsCard from '@/components/pure/ms-card/index.vue'; import MsCard from '@/components/pure/ms-card/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsUpload from '@/components/pure/ms-upload/index.vue'; import MsUpload from '@/components/pure/ms-upload/index.vue';
import ExpireAlert from './components/expireAlert.vue';
import { addLicense, getLicenseInfo } from '@/api/modules/setting/authorizedManagement'; import { addLicense, getLicenseInfo } from '@/api/modules/setting/authorizedManagement';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
@ -143,6 +145,7 @@
const result = await getLicenseInfo(); const result = await getLicenseInfo();
licenseInfo.value = result; licenseInfo.value = result;
licenseStore.setLicenseStatus(licenseInfo.value?.status); licenseStore.setLicenseStatus(licenseInfo.value?.status);
licenseStore.getExpirationTime(licenseInfo.value.license.expired);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {

View File

@ -15,4 +15,8 @@ export default {
'system.authorized.licenseCode': 'License Code', 'system.authorized.licenseCode': 'License Code',
'system.authorized.licenseSuccessTip': 'Authorized Successfully', 'system.authorized.licenseSuccessTip': 'Authorized Successfully',
'system.authorized.LicenseIsRequired': 'License Code is required', 'system.authorized.LicenseIsRequired': 'License Code is required',
'system.authorized.LicenseExpirationPromptLessThanThirty':
'The remaining {day} of system authorization expires. In order not to affect your use, please contact the staff as soon as possible',
'system.authorized.LicenseExpirationPromptGreaterThanThirty':
'System authorization has expired, if you need help, please contact the working staff',
}; };

View File

@ -15,4 +15,7 @@ export default {
'system.authorized.licenseCode': 'License Code', 'system.authorized.licenseCode': 'License Code',
'system.authorized.licenseSuccessTip': '授权成功', 'system.authorized.licenseSuccessTip': '授权成功',
'system.authorized.LicenseIsRequired': 'License Code 是必填项', 'system.authorized.LicenseIsRequired': 'License Code 是必填项',
'system.authorized.LicenseExpirationPromptLessThanThirty':
'系统授权剩余 {day} 天到期,为了不影响您的使用,请尽快联系工作人员',
'system.authorized.LicenseExpirationPromptGreaterThanThirty': '系统授权已过期,如需帮助,请联系工作人员',
}; };

View File

@ -1,4 +1,5 @@
<template> <template>
<ExpireAlert />
<MsTabCard v-model:active-tab="activeTab" :title="t('system.config.parameterConfig')" :tab-list="tabList" /> <MsTabCard v-model:active-tab="activeTab" :title="t('system.config.parameterConfig')" :tab-list="tabList" />
<baseConfig v-if="activeTab === 'baseConfig'" v-show="activeTab === 'baseConfig'" /> <baseConfig v-if="activeTab === 'baseConfig'" v-show="activeTab === 'baseConfig'" />
<pageConfig v-if="isInitPageConfig" v-show="activeTab === 'pageConfig'" /> <pageConfig v-if="isInitPageConfig" v-show="activeTab === 'pageConfig'" />
@ -13,6 +14,7 @@
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import MsTabCard from '@/components/pure/ms-tab-card/index.vue'; import MsTabCard from '@/components/pure/ms-tab-card/index.vue';
import ExpireAlert from '@/views/setting/system/authorizedManagement/components/expireAlert.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useLicenseStore from '@/store/modules/setting/license'; import useLicenseStore from '@/store/modules/setting/license';

View File

@ -1,4 +1,5 @@
<template> <template>
<ExpireAlert />
<logCards mode="SYSTEM"></logCards> <logCards mode="SYSTEM"></logCards>
</template> </template>
@ -7,6 +8,7 @@
* @description 系统设置-日志 * @description 系统设置-日志
*/ */
import logCards from './components/logCards.vue'; import logCards from './components/logCards.vue';
import ExpireAlert from '@/views/setting/system/authorizedManagement/components/expireAlert.vue';
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -1,5 +1,6 @@
<template> <template>
<MsCard simple> <MsCard simple>
<ExpireAlert />
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<div> <div>
<a-button <a-button
@ -52,6 +53,7 @@
import AddProjectModal from './components/addProjectModal.vue'; import AddProjectModal from './components/addProjectModal.vue';
import SystemOrganization from './components/systemOrganization.vue'; import SystemOrganization from './components/systemOrganization.vue';
import SystemProject from './components/systemProject.vue'; import SystemProject from './components/systemProject.vue';
import ExpireAlert from '@/views/setting/system/authorizedManagement/components/expireAlert.vue';
import { getOrgAndProjectCount } from '@/api/modules/setting/organizationAndProject'; import { getOrgAndProjectCount } from '@/api/modules/setting/organizationAndProject';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';

View File

@ -1,6 +1,7 @@
/* stylelint-disable order/properties-order */ /* stylelint-disable order/properties-order */
<template> <template>
<MsCard simple> <MsCard simple>
<ExpireAlert />
<div class="wrapper"> <div class="wrapper">
<a-alert :closable="true" class="mb-4"> <a-alert :closable="true" class="mb-4">
<div> <div>
@ -19,6 +20,7 @@
*/ */
import MsCard from '@/components/pure/ms-card/index.vue'; import MsCard from '@/components/pure/ms-card/index.vue';
import pluginTable from './components/pluginTable.vue'; import pluginTable from './components/pluginTable.vue';
import ExpireAlert from '@/views/setting/system/authorizedManagement/components/expireAlert.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';

View File

@ -1,5 +1,6 @@
<template> <template>
<MsCard :loading="loading" simple> <MsCard :loading="loading" simple>
<ExpireAlert />
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<a-button v-permission="['SYSTEM_TEST_RESOURCE_POOL:READ+ADD']" v-xpack type="primary" @click="addPool"> <a-button v-permission="['SYSTEM_TEST_RESOURCE_POOL:READ+ADD']" v-xpack type="primary" @click="addPool">
{{ t('system.resourcePool.createPool') }} {{ t('system.resourcePool.createPool') }}
@ -91,6 +92,7 @@
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types'; import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import { TagType, Theme } from '@/components/pure/ms-tag/ms-tag.vue'; import { TagType, Theme } from '@/components/pure/ms-tag/ms-tag.vue';
import JobTemplateDrawer from './components/jobTemplateDrawer.vue'; import JobTemplateDrawer from './components/jobTemplateDrawer.vue';
import ExpireAlert from '@/views/setting/system/authorizedManagement/components/expireAlert.vue';
import { delPoolInfo, getPoolInfo, getPoolList, togglePoolStatus } from '@/api/modules/setting/resourcePool'; import { delPoolInfo, getPoolInfo, getPoolList, togglePoolStatus } from '@/api/modules/setting/resourcePool';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';

View File

@ -1,5 +1,8 @@
<template> <template>
<MsCard simple no-content-padding> <MsCard simple no-content-padding>
<div class="p-4 pb-0">
<ExpireAlert />
</div>
<TaskCenter group="system" /> <TaskCenter group="system" />
</MsCard> </MsCard>
</template> </template>
@ -9,6 +12,7 @@
import MsCard from '@/components/pure/ms-card/index.vue'; import MsCard from '@/components/pure/ms-card/index.vue';
import TaskCenter from '@/views/project-management/taskCenter/component/taskCom.vue'; import TaskCenter from '@/views/project-management/taskCenter/component/taskCom.vue';
import ExpireAlert from '@/views/setting/system/authorizedManagement/components/expireAlert.vue';
</script> </script>
<style scoped lang="less"> <style scoped lang="less">

View File

@ -1,5 +1,6 @@
<template> <template>
<MsCard simple> <MsCard simple>
<ExpireAlert />
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<div> <div>
<a-button <a-button
@ -18,7 +19,12 @@
> >
{{ t('system.user.emailInvite') }} {{ t('system.user.emailInvite') }}
</a-button> </a-button>
<a-button v-permission="['SYSTEM_USER:READ+IMPORT']" class="mr-3" type="outline" @click="showImportModal"> <a-button
v-permission="['SYSTEM_USER:READ+IMPORT', 'SYSTEM_USER_ROLE:READ']"
class="mr-3"
type="outline"
@click="showImportModal"
>
{{ t('system.user.importUser') }} {{ t('system.user.importUser') }}
</a-button> </a-button>
</div> </div>
@ -306,6 +312,7 @@
import MsSelect from '@/components/business/ms-select'; import MsSelect from '@/components/business/ms-select';
import batchModal from './components/batchModal.vue'; import batchModal from './components/batchModal.vue';
import inviteModal from './components/inviteModal.vue'; import inviteModal from './components/inviteModal.vue';
import ExpireAlert from '@/views/setting/system/authorizedManagement/components/expireAlert.vue';
import { import {
batchCreateUser, batchCreateUser,

View File

@ -12,8 +12,9 @@
/> />
</template> </template>
<template #second> <template #second>
<div class="flex h-full flex-col gap-[16px] overflow-hidden pt-[24px]"> <div class="flex h-full flex-col overflow-hidden pt-[24px]">
<div class="flex flex-row items-center justify-between px-[24px]"> <ExpireAlert class="px-4" />
<div class="mb-4 flex flex-row items-center justify-between px-[24px]">
<a-tooltip :content="currentUserGroupItem.name"> <a-tooltip :content="currentUserGroupItem.name">
<div class="one-line-text max-w-[300px] font-medium">{{ currentUserGroupItem.name }}</div> <div class="one-line-text max-w-[300px] font-medium">{{ currentUserGroupItem.name }}</div>
</a-tooltip> </a-tooltip>
@ -72,6 +73,7 @@
import AuthTable from '@/components/business/ms-user-group-comp/authTable.vue'; import AuthTable from '@/components/business/ms-user-group-comp/authTable.vue';
import UserGroupLeft from '@/components/business/ms-user-group-comp/msUserGroupLeft.vue'; import UserGroupLeft from '@/components/business/ms-user-group-comp/msUserGroupLeft.vue';
import UserTable from '@/components/business/ms-user-group-comp/userTable.vue'; import UserTable from '@/components/business/ms-user-group-comp/userTable.vue';
import ExpireAlert from '@/views/setting/system/authorizedManagement/components/expireAlert.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';