feat(接口测试): 接口调试-模块树重命名&删除、部分bug修复

This commit is contained in:
baiqi 2024-02-19 16:02:17 +08:00 committed by Craftsman
parent 12071962b7
commit b35fdf8355
28 changed files with 328 additions and 110 deletions

View File

@ -3,6 +3,7 @@ import {
AddApiDebugUrl, AddApiDebugUrl,
AddDebugModuleUrl, AddDebugModuleUrl,
DeleteDebugModuleUrl, DeleteDebugModuleUrl,
DeleteDebugUrl,
ExecuteApiDebugUrl, ExecuteApiDebugUrl,
GetApiDebugDetailUrl, GetApiDebugDetailUrl,
GetDebugModuleCountUrl, GetDebugModuleCountUrl,
@ -71,3 +72,8 @@ export function updateDebug(data: UpdateDebugParams) {
export function getDebugDetail(id: string) { export function getDebugDetail(id: string) {
return MSR.get<DebugDetail>({ url: GetApiDebugDetailUrl, params: id }); return MSR.get<DebugDetail>({ url: GetApiDebugDetailUrl, params: id });
} }
// 删除接口调试
export function deleteDebug(id: string) {
return MSR.get({ url: DeleteDebugUrl, params: id });
}

View File

@ -2,6 +2,7 @@ export const ExecuteApiDebugUrl = '/api/debug/debug'; // 执行调试
export const AddApiDebugUrl = '/api/debug/add'; // 新增调试 export const AddApiDebugUrl = '/api/debug/add'; // 新增调试
export const UpdateApiDebugUrl = '/api/debug/update'; // 更新调试 export const UpdateApiDebugUrl = '/api/debug/update'; // 更新调试
export const GetApiDebugDetailUrl = '/api/debug/get'; // 获取接口调试详情 export const GetApiDebugDetailUrl = '/api/debug/get'; // 获取接口调试详情
export const DeleteDebugUrl = '/api/debug/delete'; // 删除调试
export const UpdateDebugModuleUrl = '/api/debug/module/update'; // 更新模块 export const UpdateDebugModuleUrl = '/api/debug/module/update'; // 更新模块
export const MoveDebugModuleUrl = '/api/debug/module/move'; // 移动模块 export const MoveDebugModuleUrl = '/api/debug/module/move'; // 移动模块
export const GetDebugModuleCountUrl = '/api/debug/module/count'; // 模块统计数量 export const GetDebugModuleCountUrl = '/api/debug/module/count'; // 模块统计数量

View File

@ -215,6 +215,10 @@
} }
.arco-btn-size-mini { .arco-btn-size-mini {
line-height: 16px; line-height: 16px;
.arco-icon-loading {
font-size: 14px;
line-height: 16px;
}
} }
/** 输入框,选择器,文本域 **/ /** 输入框,选择器,文本域 **/

View File

@ -249,7 +249,7 @@
margin-left: -16px !important; margin-left: -16px !important;
border-radius: 0 4px 4px 0 !important; border-radius: 0 4px 4px 0 !important;
background-color: var(--color-text-n8) !important; background-color: var(--color-text-n8) !important;
&:hover { &:hover:not(.arco-select-view-disabled) {
border-color: rgb(var(--primary-5)) !important; border-color: rgb(var(--primary-5)) !important;
background-color: var(--color-text-n8) !important; background-color: var(--color-text-n8) !important;
} }

View File

@ -388,7 +388,7 @@
let mouseEnterTimer; let mouseEnterTimer;
// //
const renderMenuItem = (element, icon) => const renderMenuItem = (element: RouteRecordRaw | null, icon) =>
element?.name === SettingRouteEnum.SETTING_ORGANIZATION ? ( element?.name === SettingRouteEnum.SETTING_ORGANIZATION ? (
<a-menu-item key={element?.name} v-slots={{ icon }} onClick={() => goto(element)}> <a-menu-item key={element?.name} v-slots={{ icon }} onClick={() => goto(element)}>
<div class="inline-flex w-[calc(100%-34px)] items-center justify-between !bg-transparent"> <div class="inline-flex w-[calc(100%-34px)] items-center justify-between !bg-transparent">

View File

@ -65,7 +65,7 @@
<div class="flex items-center justify-between px-[16px]"> <div class="flex items-center justify-between px-[16px]">
<MsTableMoreAction :list="actions" trigger="click" @select="handleMoreActionSelect($event, item)"> <MsTableMoreAction :list="actions" trigger="click" @select="handleMoreActionSelect($event, item)">
<a-button <a-button
v-permission="['SYSTEM_PERSONAL_API_KEY:READ+UPDATE']" v-permission="['SYSTEM_PERSONAL_API_KEY:READ+UPDATE', 'SYSTEM_PERSONAL_API_KEY:READ+DELETE']"
size="mini" size="mini"
type="outline" type="outline"
class="arco-btn-outline--secondary" class="arco-btn-outline--secondary"
@ -83,10 +83,10 @@
</div> </div>
</div> </div>
<div v-if="apiKeyList.length === 0" class="col-span-2 flex w-full items-center justify-center p-[44px]"> <div v-if="apiKeyList.length === 0" class="col-span-2 flex w-full items-center justify-center p-[44px]">
{{ t('ms.personal.nodata') }} {{ hasCratePermission ? t('ms.personal.noData') : t('ms.personal.empty') }}
<MsButton v-permission="['SYSTEM_PERSONAL_API_KEY:READ+ADD']" type="text" class="ml-[8px]" @click="newApiKey">{{ <MsButton v-permission="['SYSTEM_PERSONAL_API_KEY:READ+ADD']" type="text" class="ml-[8px]" @click="newApiKey">
t('common.new') {{ t('common.new') }}
}}</MsButton> </MsButton>
</div> </div>
</a-spin> </a-spin>
</div> </div>
@ -156,6 +156,7 @@
} from '@/api/modules/user/index'; } from '@/api/modules/user/index';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import { hasAnyPermission } from '@/utils/permission';
import { APIKEY } from '@/models/user'; import { APIKEY } from '@/models/user';
@ -169,6 +170,7 @@
desensitization: boolean; desensitization: boolean;
} }
const apiKeyList = ref<APIKEYItem[]>([]); const apiKeyList = ref<APIKEYItem[]>([]);
const hasCratePermission = hasAnyPermission(['SYSTEM_PERSONAL_API_KEY:READ+ADD']);
async function initApiKeys() { async function initApiKeys() {
try { try {
@ -210,6 +212,7 @@
{ {
label: t('ms.personal.validTime'), label: t('ms.personal.validTime'),
eventTag: 'time', eventTag: 'time',
permission: ['SYSTEM_PERSONAL_API_KEY:READ+UPDATE'],
}, },
{ {
isDivider: true, isDivider: true,
@ -218,6 +221,7 @@
label: t('common.delete'), label: t('common.delete'),
danger: true, danger: true,
eventTag: 'delete', eventTag: 'delete',
permission: ['SYSTEM_PERSONAL_API_KEY:READ+DELETE'],
}, },
]; ];

View File

@ -14,7 +14,7 @@
<div v-for="config of dynamicForm" :key="config.key" class="platform-card"> <div v-for="config of dynamicForm" :key="config.key" class="platform-card">
<div class="mb-[16px] flex items-center"> <div class="mb-[16px] flex items-center">
<a-image :src="`/plugin/image/${config.key}?imagePath=static/${config.key}.jpg`" width="24"></a-image> <a-image :src="`/plugin/image/${config.key}?imagePath=static/${config.key}.jpg`" width="24"></a-image>
<div class="ml-[8px] mr-[4px] font-medium text-[var(--color-text-1)]">{{ config.key }}</div> <div class="ml-[8px] mr-[4px] font-medium text-[var(--color-text-1)]">{{ config.name }}</div>
<a-tooltip v-if="config.tooltip" :content="config.tooltip" position="right"> <a-tooltip v-if="config.tooltip" :content="config.tooltip" position="right">
<icon-exclamation-circle <icon-exclamation-circle
class="mr-[8px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]" class="mr-[8px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
@ -122,6 +122,7 @@
Object.keys(res).forEach((key) => { Object.keys(res).forEach((key) => {
dynamicForm.value[key] = { dynamicForm.value[key] = {
key, key,
name: res[key].name,
status: 0, status: 0,
formModel: {}, formModel: {},
formRules: res[key].formItems, formRules: res[key].formItems,

View File

@ -87,5 +87,6 @@ export default {
'ms.personal.azureTip': 'ms.personal.azureTip':
'This information is the user token information for submitting defects through Azure Devops. If not filled in, the default information configured by the organization will be used.', 'This information is the user token information for submitting defects through Azure Devops. If not filled in, the default information configured by the organization will be used.',
'ms.personal.azurePlaceholder': 'Please enter Personal Access Tokens', 'ms.personal.azurePlaceholder': 'Please enter Personal Access Tokens',
'ms.personal.nodata': 'No data yet, please ', 'ms.personal.noData': 'No data yet, please ',
'ms.personal.empty': 'No data',
}; };

View File

@ -79,5 +79,6 @@ export default {
'ms.personal.zendaoTip': '该信息为通过禅道提交缺陷的的用户名、密码,若未填写,则使用组织配置的默认信息', 'ms.personal.zendaoTip': '该信息为通过禅道提交缺陷的的用户名、密码,若未填写,则使用组织配置的默认信息',
'ms.personal.azureTip': '该信息为通过Azure Devops提交缺陷的用户令牌信息若未填写则使用组织配置的默认信息', 'ms.personal.azureTip': '该信息为通过Azure Devops提交缺陷的用户令牌信息若未填写则使用组织配置的默认信息',
'ms.personal.azurePlaceholder': '请输入 Personal Access Tokens', 'ms.personal.azurePlaceholder': '请输入 Personal Access Tokens',
'ms.personal.nodata': '暂无数据,请 ', 'ms.personal.noData': '暂无数据,请 ',
'ms.personal.empty': '暂无数据',
}; };

View File

@ -355,8 +355,10 @@
<style lang="less"> <style lang="less">
.ms-tree-container { .ms-tree-container {
.ms-container--shadow-y(); .ms-container--shadow-y();
@apply h-full;
.ms-tree { .ms-tree {
.ms-scroll-bar(); .ms-scroll-bar();
@apply h-full;
.arco-tree-node { .arco-tree-node {
border-radius: var(--border-radius-small); border-radius: var(--border-radius-small);
&:hover { &:hover {

View File

@ -8,7 +8,12 @@
</slot> </slot>
<template #content> <template #content>
<template v-for="item of props.list"> <template v-for="item of props.list">
<a-divider v-if="item.isDivider" :key="`${item.label}-divider`" margin="4px" /> <a-divider
v-if="item.isDivider"
:key="`${item.label}-divider`"
:class="beforeDividerHasAction && afterDividerHasAction ? '' : 'hidden'"
margin="4px"
/>
<a-doption <a-doption
v-else v-else
:key="item.label" :key="item.label"
@ -31,6 +36,7 @@
import MsIcon from '@/components/pure/ms-icon-font/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { hasAnyPermission } from '@/utils/permission';
import type { ActionsItem, SelectedValue } from './types'; import type { ActionsItem, SelectedValue } from './types';
@ -42,6 +48,38 @@
const emit = defineEmits(['select', 'close']); const emit = defineEmits(['select', 'close']);
// 线action
const beforeDividerHasAction = computed(() => {
let result = false;
for (let i = 0; i < props.list.length; i++) {
const item = props.list[i];
if (!item.isDivider) {
result = hasAnyPermission(item.permission || []);
if (result) {
return true;
}
} else {
return false;
}
}
});
// 线action
const afterDividerHasAction = computed(() => {
let result = false;
for (let i = props.list.length - 1; i > 0; i--) {
const item = props.list[i];
if (!item.isDivider) {
result = hasAnyPermission(item.permission || []);
if (result) {
return true;
}
} else {
return false;
}
}
});
function selectHandler(value: SelectedValue) { function selectHandler(value: SelectedValue) {
const item = props.list.find((e: ActionsItem) => e.eventTag === value); const item = props.list.find((e: ActionsItem) => e.eventTag === value);
emit('select', item); emit('select', item);

View File

@ -256,7 +256,8 @@ export default function useTableProps<T>(
const data = await loadListFunc({ keyword: keyword.value, ...loadListParams.value }); const data = await loadListFunc({ keyword: keyword.value, ...loadListParams.value });
if (data.length === 0) { if (data.length === 0) {
setTableErrorStatus('empty'); setTableErrorStatus('empty');
return; propsRes.value.data = [];
return data;
} }
setTableErrorStatus(false); setTableErrorStatus(false);
propsRes.value.data = data.map((item: MsTableDataItem<T>) => { propsRes.value.data = data.map((item: MsTableDataItem<T>) => {

View File

@ -1,6 +1,6 @@
import { DirectiveBinding } from 'vue'; import { DirectiveBinding } from 'vue';
import { hasAnyPermission } from '@/utils/permission'; import { hasAllPermission, hasAnyPermission } from '@/utils/permission';
/** /**
* *
@ -8,10 +8,11 @@ import { hasAnyPermission } from '@/utils/permission';
* @param binding vue * @param binding vue
*/ */
function checkPermission(el: HTMLElement, binding: DirectiveBinding) { function checkPermission(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding; const { value, modifiers } = binding;
if (Array.isArray(value)) { if (Array.isArray(value)) {
if (value.length > 0) { if (value.length > 0) {
const hasPermission = hasAnyPermission(value); // 如果有 all 修饰符,表示需要全部权限;否则只需要其中一个权限
const hasPermission = modifiers.all ? hasAllPermission(value) : hasAnyPermission(value);
if (!hasPermission && el.parentNode) { if (!hasPermission && el.parentNode) {
el.parentNode.removeChild(el); el.parentNode.removeChild(el);
} }

View File

@ -313,10 +313,10 @@ export interface SaveDebugParams {
linkFileIds: string[]; linkFileIds: string[];
} }
// 更新接口调试入参 // 更新接口调试入参
export interface UpdateDebugParams extends SaveDebugParams { export interface UpdateDebugParams extends Partial<SaveDebugParams> {
id: string; id: string;
deleteFileIds: string[]; deleteFileIds?: string[];
unLinkRefIds: string[]; unLinkRefIds?: string[];
} }
// 更新模块入参 // 更新模块入参
export interface UpdateDebugModule { export interface UpdateDebugModule {

View File

@ -16,6 +16,9 @@ const Setting: AppRouteRecordRaw = {
'SYSTEM_USER_ROLE:READ', 'SYSTEM_USER_ROLE:READ',
'SYSTEM_ORGANIZATION_PROJECT:READ', 'SYSTEM_ORGANIZATION_PROJECT:READ',
'SYSTEM_PARAMETER_SETTING_BASE:READ', 'SYSTEM_PARAMETER_SETTING_BASE:READ',
'SYSTEM_PARAMETER_SETTING_DISPLAY:READ',
'SYSTEM_PARAMETER_SETTING_AUTH:READ',
'SYSTEM_PARAMETER_SETTING_MEMORY_CLEAN:READ',
'SYSTEM_TEST_RESOURCE_POOL:READ', 'SYSTEM_TEST_RESOURCE_POOL:READ',
'SYSTEM_AUTH:READ', 'SYSTEM_AUTH:READ',
'SYSTEM_PLUGIN:READ', 'SYSTEM_PLUGIN:READ',
@ -40,6 +43,9 @@ const Setting: AppRouteRecordRaw = {
'SYSTEM_USER_ROLE:READ', 'SYSTEM_USER_ROLE:READ',
'SYSTEM_ORGANIZATION_PROJECT:READ', 'SYSTEM_ORGANIZATION_PROJECT:READ',
'SYSTEM_PARAMETER_SETTING_BASE:READ', 'SYSTEM_PARAMETER_SETTING_BASE:READ',
'SYSTEM_PARAMETER_SETTING_DISPLAY:READ',
'SYSTEM_PARAMETER_SETTING_AUTH:READ',
'SYSTEM_PARAMETER_SETTING_MEMORY_CLEAN:READ',
'SYSTEM_TEST_RESOURCE_POOL:READ', 'SYSTEM_TEST_RESOURCE_POOL:READ',
'SYSTEM_AUTH:READ', 'SYSTEM_AUTH:READ',
'SYSTEM_PLUGIN:READ', 'SYSTEM_PLUGIN:READ',
@ -84,7 +90,12 @@ const Setting: AppRouteRecordRaw = {
component: () => import('@/views/setting/system/config/index.vue'), component: () => import('@/views/setting/system/config/index.vue'),
meta: { meta: {
locale: 'menu.settings.system.parameter', locale: 'menu.settings.system.parameter',
roles: ['SYSTEM_PARAMETER_SETTING_BASE:READ'], roles: [
'SYSTEM_PARAMETER_SETTING_BASE:READ',
'SYSTEM_PARAMETER_SETTING_DISPLAY:READ',
'SYSTEM_PARAMETER_SETTING_AUTH:READ',
'SYSTEM_PARAMETER_SETTING_MEMORY_CLEAN:READ',
],
isTopMenu: true, isTopMenu: true,
}, },
}, },

View File

@ -548,3 +548,25 @@ export function tableParamsToRequestParams(params: BatchActionQueryParams) {
condition, condition,
}; };
} }
/**
* URL
* @param url URL
*/
interface QueryParam {
key: string;
value: string;
}
export function parseQueryParams(url: string): QueryParam[] {
const queryParams: QueryParam[] = [];
// 从 URL 中提取查询参数部分
const queryString = url.split('?')[1];
if (queryString) {
const params = new URLSearchParams(queryString);
// 遍历查询参数,将每个参数添加到数组中
params.forEach((value, key) => {
queryParams.push({ key, value });
});
}
return queryParams;
}

View File

@ -11,10 +11,7 @@
> >
<template #content> <template #content>
<div class="mb-[8px] font-medium"> <div class="mb-[8px] font-medium">
{{ {{ props.title || (props.mode === 'add' ? t('project.fileManagement.addSubModule') : t('common.rename')) }}
props.title ||
(props.mode === 'add' ? t('project.fileManagement.addSubModule') : t('project.fileManagement.rename'))
}}
</div> </div>
<a-form ref="formRef" :model="form" layout="vertical"> <a-form ref="formRef" :model="form" layout="vertical">
<a-form-item <a-form-item
@ -51,7 +48,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 { addDebugModule, updateDebugModule } from '@/api/modules/api-test/debug'; import { addDebugModule, updateDebug, updateDebugModule } from '@/api/modules/api-test/debug';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
@ -67,6 +64,7 @@
const props = defineProps<{ const props = defineProps<{
mode: 'add' | 'rename'; mode: 'add' | 'rename';
nodeType?: 'MODULE' | 'API';
visible?: boolean; visible?: boolean;
title?: string; title?: string;
allNames: string[]; allNames: string[];
@ -128,16 +126,24 @@
parentId: props.parentId || '', parentId: props.parentId || '',
name: form.value.field, name: form.value.field,
}); });
Message.success(t('project.fileManagement.addSubModuleSuccess')); Message.success(t('common.addSuccess'));
emit('addFinish', form.value.field); emit('addFinish', form.value.field);
} else if (props.mode === 'rename' && props.nodeType === 'API') {
//
await updateDebug({
id: props.nodeId || '',
name: form.value.field,
});
Message.success(t('common.updateSuccess'));
emit('renameFinish', form.value.field, props.nodeId);
} else if (props.mode === 'rename') { } else if (props.mode === 'rename') {
// //
await updateDebugModule({ await updateDebugModule({
id: props.nodeId || '', id: props.nodeId || '',
name: form.value.field, name: form.value.field,
}); });
Message.success(t('project.fileManagement.renameSuccess')); Message.success(t('common.updateSuccess'));
emit('renameFinish', form.value.field); emit('renameFinish', form.value.field, props.nodeId);
} }
if (done) { if (done) {
done(true); done(true);

View File

@ -21,7 +21,7 @@
v-model:model-value="requestVModel.url" v-model:model-value="requestVModel.url"
:max-length="255" :max-length="255"
:placeholder="t('apiTestDebug.urlPlaceholder')" :placeholder="t('apiTestDebug.urlPlaceholder')"
@change="handleActiveDebugChange" @change="handleUrlChange"
/> />
</a-input-group> </a-input-group>
</div> </div>
@ -230,14 +230,14 @@
import { getLocalConfig } from '@/api/modules/user/index'; import { getLocalConfig } from '@/api/modules/user/index';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import { filterTree, getGenerateId } from '@/utils'; import { filterTree, getGenerateId, parseQueryParams } from '@/utils';
import { scrollIntoView } from '@/utils/dom'; import { scrollIntoView } from '@/utils/dom';
import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event'; import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event';
import { PluginConfig } from '@/models/apiTest/common'; import { PluginConfig } from '@/models/apiTest/common';
import { ExecuteHTTPRequestFullParams } from '@/models/apiTest/debug'; import { ExecuteHTTPRequestFullParams } from '@/models/apiTest/debug';
import { ModuleTreeNode } from '@/models/common'; import { ModuleTreeNode } from '@/models/common';
import { RequestComposition, RequestMethods } from '@/enums/apiEnum'; import { RequestComposition, RequestMethods, RequestParamsType } from '@/enums/apiEnum';
import { Api } from '@form-create/arco-design'; import { Api } from '@form-create/arco-design';
@ -250,6 +250,7 @@
export interface RequestCustomAttr { export interface RequestCustomAttr {
isNew: boolean; isNew: boolean;
protocol: string; protocol: string;
activeTab: RequestComposition;
} }
export type RequestParam = ExecuteHTTPRequestFullParams & RequestCustomAttr & TabItem; export type RequestParam = ExecuteHTTPRequestFullParams & RequestCustomAttr & TabItem;
@ -479,6 +480,32 @@
} }
); );
/**
* 处理url输入框变化解析成参数表格
*/
function handleUrlChange(val: string) {
const params = parseQueryParams(val.trim());
if (params.length > 0) {
requestVModel.value.query.splice(
0,
requestVModel.value.query.length - 2,
...params.map((e, i) => ({
id: (new Date().getTime() + i).toString(),
paramType: RequestParamsType.STRING,
description: '',
required: false,
maxLength: undefined,
minLength: undefined,
encode: false,
enable: true,
...e,
}))
);
requestVModel.value.activeTab = RequestComposition.QUERY;
}
handleActiveDebugChange();
}
const splitBoxSize = ref<string | number>(0.6); const splitBoxSize = ref<string | number>(0.6);
const activeLayout = ref<'horizontal' | 'vertical'>('vertical'); const activeLayout = ref<'horizontal' | 'vertical'>('vertical');
const splitContainerRef = ref<HTMLElement>(); const splitContainerRef = ref<HTMLElement>();

View File

@ -1,5 +1,5 @@
<template> <template>
<div> <div class="flex h-full flex-col p-[24px]">
<div class="mb-[8px] flex items-center gap-[8px]"> <div class="mb-[8px] flex items-center gap-[8px]">
<a-input v-model:model-value="moduleKeyword" :placeholder="t('apiTestDebug.searchTip')" allow-clear /> <a-input v-model:model-value="moduleKeyword" :placeholder="t('apiTestDebug.searchTip')" allow-clear />
<a-dropdown @select="handleSelect"> <a-dropdown @select="handleSelect">
@ -34,7 +34,7 @@
</div> </div>
</div> </div>
<a-divider class="my-[8px]" /> <a-divider class="my-[8px]" />
<a-spin class="min-h-[400px] w-full" :loading="loading"> <a-spin class="h-[calc(100%-98px)] w-full" :loading="loading">
<MsTree <MsTree
v-model:focus-node-key="focusNodeKey" v-model:focus-node-key="focusNodeKey"
:data="folderTree" :data="folderTree"
@ -43,8 +43,9 @@
:default-expand-all="isExpandAll" :default-expand-all="isExpandAll"
:expand-all="isExpandAll" :expand-all="isExpandAll"
:empty-text="t('apiTestDebug.noMatchModule')" :empty-text="t('apiTestDebug.noMatchModule')"
:draggable="true" :virtual-list-props="{
:virtual-list-props="virtualListProps" height: '100%',
}"
:field-names="{ :field-names="{
title: 'name', title: 'name',
key: 'id', key: 'id',
@ -93,8 +94,9 @@
:node-id="nodeData.id" :node-id="nodeData.id"
: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 || '')"
:node-type="nodeData.type"
@close="resetFocusNodeKey" @close="resetFocusNodeKey"
@rename-finish="initModules" @rename-finish="handleRenameFinish"
> >
<span :id="`renameSpan${nodeData.id}`" class="relative"></span> <span :id="`renameSpan${nodeData.id}`" class="relative"></span>
</popConfirm> </popConfirm>
@ -117,6 +119,7 @@
import popConfirm from '@/views/api-test/components/popConfirm.vue'; import popConfirm from '@/views/api-test/components/popConfirm.vue';
import { import {
deleteDebug,
deleteDebugModule, deleteDebugModule,
getDebugModuleCount, getDebugModuleCount,
getDebugModules, getDebugModules,
@ -131,7 +134,7 @@
const props = defineProps<{ const props = defineProps<{
isExpandAll?: boolean; // isExpandAll?: boolean; //
}>(); }>();
const emit = defineEmits(['init', 'clickApiNode', 'newApi', 'import']); const emit = defineEmits(['init', 'clickApiNode', 'newApi', 'import', 'renameFinish']);
const { t } = useI18n(); const { t } = useI18n();
const { openModal } = useModal(); const { openModal } = useModal();
@ -150,12 +153,6 @@
} }
} }
const virtualListProps = computed(() => {
return {
height: 'calc(100% - 180px)',
};
});
const isExpandAll = ref(props.isExpandAll); const isExpandAll = ref(props.isExpandAll);
const rootModulesName = ref<string[]>([]); // const rootModulesName = ref<string[]>([]); //
@ -204,9 +201,9 @@
return { return {
...e, ...e,
hideMoreAction: e.id === 'root', hideMoreAction: e.id === 'root',
draggable: e.id !== 'root',
}; };
}); });
rootModulesName.value = folderTree.value.map((e) => e.name || '');
emit('init', folderTree.value); emit('init', folderTree.value);
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -228,7 +225,6 @@
return { return {
...node, ...node,
count: res[node.id] || 0, count: res[node.id] || 0,
draggable: node.id !== 'root',
}; };
}); });
} catch (error) { } catch (error) {
@ -273,6 +269,34 @@
renameFolderTitle.value = ''; renameFolderTitle.value = '';
} }
/**
* 删除接口调试
* @param node 节点信息
*/
function deleteApiDebug(node: MsTreeNodeData) {
openModal({
type: 'error',
title: t('apiTestDebug.deleteDebugTipTitle', { name: node.name }),
content: t('apiTestDebug.deleteDebugTipContent'),
okText: t('apiTestDebug.deleteConfirm'),
okButtonProps: {
status: 'danger',
},
maskClosable: false,
onBeforeOk: async () => {
try {
await deleteDebug(node.id);
Message.success(t('apiTestDebug.deleteSuccess'));
initModules();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
hideCancel: false,
});
}
/** /**
* 处理树节点更多按钮事件 * 处理树节点更多按钮事件
* @param item * @param item
@ -280,7 +304,11 @@
function handleFolderMoreSelect(item: ActionsItem, node: MsTreeNodeData) { function handleFolderMoreSelect(item: ActionsItem, node: MsTreeNodeData) {
switch (item.eventTag) { switch (item.eventTag) {
case 'delete': case 'delete':
deleteFolder(node); if (node.type === 'MODULE') {
deleteFolder(node);
} else {
deleteApiDebug(node);
}
resetFocusNodeKey(); resetFocusNodeKey();
break; break;
case 'rename': case 'rename':
@ -319,7 +347,7 @@
console.log(error); console.log(error);
} finally { } finally {
loading.value = false; loading.value = false;
initModules(); await initModules();
initModuleCount(); initModuleCount();
} }
} }
@ -331,8 +359,13 @@
} }
} }
onBeforeMount(() => { function handleRenameFinish(newName: string, id: string) {
initModules(); initModules();
emit('renameFinish', newName, id);
}
onBeforeMount(async () => {
await initModules();
initModuleCount(); initModuleCount();
}); });

View File

@ -3,15 +3,14 @@
<MsCard :loading="loading" simple no-content-padding> <MsCard :loading="loading" simple no-content-padding>
<MsSplitBox :size="0.25" :max="0.5"> <MsSplitBox :size="0.25" :max="0.5">
<template #first> <template #first>
<div class="p-[24px]"> <moduleTree
<moduleTree ref="moduleTreeRef"
ref="moduleTreeRef" @init="(val) => (folderTree = val)"
@init="(val) => (folderTree = val)" @new-api="addDebugTab"
@new-api="addDebugTab" @click-api-node="openApiTab"
@click-api-node="openApiTab" @import="importDrawerVisible = true"
@import="importDrawerVisible = true" @rename-finish="handleRenameFinish"
/> />
</div>
</template> </template>
<template #second> <template #second>
<div class="flex h-full flex-col"> <div class="flex h-full flex-col">
@ -113,8 +112,8 @@
const curlCode = ref(''); const curlCode = ref('');
const loading = ref(false); const loading = ref(false);
function handleDebugAddDone() { async function handleDebugAddDone() {
moduleTreeRef.value?.initModules(); await moduleTreeRef.value?.initModules();
moduleTreeRef.value?.initModuleCount(); moduleTreeRef.value?.initModuleCount();
} }
@ -289,6 +288,16 @@
handleActiveDebugChange(); handleActiveDebugChange();
}); });
} }
function handleRenameFinish(name: string, id: string) {
debugTabs.value = debugTabs.value.map((tab) => {
if (tab.id === id) {
tab.label = name;
tab.name = name;
}
return tab;
});
}
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -96,6 +96,8 @@ export default {
'apiTestDebug.deleteFolderTipTitle': 'Remove the `{name}` module?', 'apiTestDebug.deleteFolderTipTitle': 'Remove the `{name}` module?',
'apiTestDebug.deleteFolderTipContent': 'apiTestDebug.deleteFolderTipContent':
'This operation will delete the module and all resources under it, please operate with caution!', 'This operation will delete the module and all resources under it, please operate with caution!',
'apiTestDebug.deleteDebugTipTitle': 'Remove the {name}?',
'apiTestDebug.deleteDebugTipContent': 'Deletion cannot be restored, please proceed with caution!',
'apiTestDebug.deleteConfirm': 'Confirm delete', 'apiTestDebug.deleteConfirm': 'Confirm delete',
'apiTestDebug.deleteSuccess': 'Successfully deleted', 'apiTestDebug.deleteSuccess': 'Successfully deleted',
'apiTestDebug.moduleMoveSuccess': 'Module moved successfully', 'apiTestDebug.moduleMoveSuccess': 'Module moved successfully',

View File

@ -88,8 +88,10 @@ export default {
'apiTestDebug.extractParameter': '提取参数', 'apiTestDebug.extractParameter': '提取参数',
'apiTestDebug.searchTip': '请输入模块/请求名称', 'apiTestDebug.searchTip': '请输入模块/请求名称',
'apiTestDebug.allRequest': '全部请求', 'apiTestDebug.allRequest': '全部请求',
'apiTestDebug.deleteFolderTipTitle': '是否删除 `{name}` 模块?', 'apiTestDebug.deleteFolderTipTitle': '是否删除 {name} 模块?',
'apiTestDebug.deleteFolderTipContent': '该操作会删除模块及其下所有资源,请谨慎操作!', 'apiTestDebug.deleteFolderTipContent': '该操作会删除模块及其下所有资源,请谨慎操作!',
'apiTestDebug.deleteDebugTipTitle': '是否删除 {name}',
'apiTestDebug.deleteDebugTipContent': '删除后无法恢复,请谨慎操作!',
'apiTestDebug.deleteConfirm': '确认删除', 'apiTestDebug.deleteConfirm': '确认删除',
'apiTestDebug.deleteSuccess': '删除成功', 'apiTestDebug.deleteSuccess': '删除成功',
'apiTestDebug.moduleMoveSuccess': '模块移动成功', 'apiTestDebug.moduleMoveSuccess': '模块移动成功',

View File

@ -276,6 +276,7 @@
const innerVisible = ref(false); const innerVisible = ref(false);
const fileDescriptions = ref<Description[]>([]); const fileDescriptions = ref<Description[]>([]);
const detailDrawerRef = ref<InstanceType<typeof MsDetailDrawer>>(); const detailDrawerRef = ref<InstanceType<typeof MsDetailDrawer>>();
const innerFileId = ref(props.fileId);
watch( watch(
() => props.visible, () => props.visible,
@ -293,7 +294,7 @@
async function handleEnableIntercept(newValue: string | number | boolean) { async function handleEnableIntercept(newValue: string | number | boolean) {
try { try {
await toggleJarFileStatus(props.fileId, newValue as boolean); await toggleJarFileStatus(innerFileId.value, newValue as boolean);
return true; return true;
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -332,7 +333,7 @@
fileLoading.value = true; fileLoading.value = true;
await reuploadFile({ await reuploadFile({
request: { request: {
fileId: props.fileId, fileId: innerFileId.value,
enable: false, enable: false,
}, },
file: data, file: data,
@ -362,7 +363,7 @@
async function addFileTag(val: string, item: Description) { async function addFileTag(val: string, item: Description) {
await updateFile({ await updateFile({
id: props.fileId, id: innerFileId.value,
tags: Array.isArray(item.value) ? [...item.value, val] : [item.value, val], tags: Array.isArray(item.value) ? [...item.value, val] : [item.value, val],
}); });
} }
@ -371,7 +372,7 @@
try { try {
const lastTags = Array.isArray(item.value) ? item.value.filter((e) => e !== tag) : []; const lastTags = Array.isArray(item.value) ? item.value.filter((e) => e !== tag) : [];
await updateFile({ await updateFile({
id: props.fileId, id: innerFileId.value,
tags: lastTags, tags: lastTags,
}); });
item.value = [...lastTags]; item.value = [...lastTags];
@ -387,7 +388,7 @@
async function upgradeRepositoryFile() { async function upgradeRepositoryFile() {
try { try {
fileLoading.value = true; fileLoading.value = true;
await updateRepositoryFile(props.fileId); await updateRepositoryFile(innerFileId.value);
Message.success(t('common.updateSuccess')); Message.success(t('common.updateSuccess'));
detailDrawerRef.value?.initDetail(); detailDrawerRef.value?.initDetail();
} catch (error) { } catch (error) {
@ -534,18 +535,19 @@
function loadTable() { function loadTable() {
if (activeTab.value === 'case') { if (activeTab.value === 'case') {
setLoadListParams({ setLoadListParams({
id: props.fileId, id: innerFileId.value,
}); });
loadCaseList(); loadCaseList();
} else { } else {
setVersionLoadListParams({ setVersionLoadListParams({
id: props.fileId, id: innerFileId.value,
}); });
loadVersionList(); loadVersionList();
} }
} }
function loadedFile(detail: FileDetail) { function loadedFile(detail: FileDetail) {
innerFileId.value = detail.id;
fileType.value = detail.fileType; fileType.value = detail.fileType;
renameTitle.value = detail.name; renameTitle.value = detail.name;
fileDescriptions.value = [ fileDescriptions.value = [

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<MsCard class="mb-[16px]" :loading="baseloading" simple auto-height> <MsCard class="mb-[16px]" :loading="baseLoading" simple auto-height>
<div class="mb-[16px] flex justify-between"> <div class="mb-[16px] flex justify-between">
<div class="text-[var(--color-text-000)]">{{ t('system.config.baseInfo') }}</div> <div class="text-[var(--color-text-000)]">{{ t('system.config.baseInfo') }}</div>
<a-button <a-button
@ -12,7 +12,7 @@
{{ t('system.config.update') }} {{ t('system.config.update') }}
</a-button> </a-button>
</div> </div>
<MsDescription :descriptions="baseInfoDescs" class="no-bottom" :column="2" /> <MsDescription :descriptions="baseInfoDesc" class="no-bottom" :column="2" />
</MsCard> </MsCard>
<MsCard class="mb-[16px]" :loading="emailLoading" simple auto-height> <MsCard class="mb-[16px]" :loading="emailLoading" simple auto-height>
<div class="mb-[16px] flex justify-between"> <div class="mb-[16px] flex justify-between">
@ -26,7 +26,7 @@
{{ t('system.config.update') }} {{ t('system.config.update') }}
</a-button> </a-button>
</div> </div>
<MsDescription :descriptions="emailInfoDescs" :column="2"> <MsDescription :descriptions="emailInfoDesc" :column="2">
<template #value="{ item }"> <template #value="{ item }">
<template v-if="item.key && ['ssl', 'tsl'].includes(item.key)"> <template v-if="item.key && ['ssl', 'tsl'].includes(item.key)">
<div v-if="item.value === 'true'" class="flex items-center"> <div v-if="item.value === 'true'" class="flex items-center">
@ -230,7 +230,7 @@
const { t } = useI18n(); const { t } = useI18n();
const baseloading = ref(false); const baseLoading = ref(false);
const baseDrawerLoading = ref(false); const baseDrawerLoading = ref(false);
const baseInfoDrawerVisible = ref(false); const baseInfoDrawerVisible = ref(false);
const baseFormRef = ref<FormInstance>(); const baseFormRef = ref<FormInstance>();
@ -239,7 +239,7 @@
prometheusHost: 'http://prometheus:9090', prometheusHost: 'http://prometheus:9090',
}); });
const baseInfoForm = ref({ ...baseInfo.value }); const baseInfoForm = ref({ ...baseInfo.value });
const baseInfoDescs = ref<Description[]>([]); const baseInfoDesc = ref<Description[]>([]);
// //
const defaultUrl = 'https://metersphere.com'; const defaultUrl = 'https://metersphere.com';
const defaultPrometheus = 'http://prometheus:9090'; const defaultPrometheus = 'http://prometheus:9090';
@ -259,12 +259,12 @@
const licenseStore = useLicenseStore(); const licenseStore = useLicenseStore();
async function initBaseInfo() { async function initBaseInfo() {
try { try {
baseloading.value = true; baseLoading.value = true;
const res = await getBaseInfo(); const res = await getBaseInfo();
baseInfo.value = { ...res }; baseInfo.value = { ...res };
baseInfoForm.value = { ...res }; baseInfoForm.value = { ...res };
if (licenseStore.hasLicense()) { if (licenseStore.hasLicense()) {
baseInfoDescs.value = [ baseInfoDesc.value = [
{ {
label: t('system.config.pageUrl'), label: t('system.config.pageUrl'),
value: res.url, value: res.url,
@ -275,7 +275,7 @@
}, },
]; ];
} else { } else {
baseInfoDescs.value = [ baseInfoDesc.value = [
{ {
label: t('system.config.pageUrl'), label: t('system.config.pageUrl'),
value: res.url, value: res.url,
@ -283,9 +283,10 @@
]; ];
} }
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console
console.log(error); console.log(error);
} finally { } finally {
baseloading.value = false; baseLoading.value = false;
} }
} }
@ -313,6 +314,7 @@
baseInfoDrawerVisible.value = false; baseInfoDrawerVisible.value = false;
initBaseInfo(); initBaseInfo();
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console
console.log(error); console.log(error);
} finally { } finally {
baseDrawerLoading.value = false; baseDrawerLoading.value = false;
@ -343,7 +345,7 @@
}); });
const emailConfigForm = ref({ ...emailConfig.value }); const emailConfigForm = ref({ ...emailConfig.value });
const emailFormRef = ref<FormInstance>(); const emailFormRef = ref<FormInstance>();
const emailInfoDescs = ref<Description[]>([]); const emailInfoDesc = ref<Description[]>([]);
const pswInVisible = ref(false); // const pswInVisible = ref(false); //
@ -366,12 +368,12 @@
try { try {
emailLoading.value = true; emailLoading.value = true;
const res = await getEmailInfo(); const res = await getEmailInfo();
const _ssl = Boolean(res.ssl); const ssl = res.ssl === 'true';
const _tsl = Boolean(res.tsl); const tsl = res.tsl === 'true';
emailConfig.value = { ...res, ssl: _ssl, tsl: _tsl }; emailConfig.value = { ...res, ssl, tsl };
emailConfigForm.value = { ...res, ssl: _ssl, tsl: _tsl }; emailConfigForm.value = { ...res, ssl, tsl };
const { host, port, account, password, from, recipient, ssl, tsl } = res; const { host, port, account, password, from, recipient } = res;
emailInfoDescs.value = [ emailInfoDesc.value = [
{ {
label: t('system.config.email.host'), label: t('system.config.email.host'),
value: host, value: host,
@ -399,16 +401,17 @@
}, },
{ {
label: t('system.config.email.ssl'), label: t('system.config.email.ssl'),
value: ssl, value: ssl.toString(),
key: 'ssl', key: 'ssl',
}, },
{ {
label: t('system.config.email.tsl'), label: t('system.config.email.tsl'),
value: tsl, value: tsl.toString(),
key: 'tsl', key: 'tsl',
}, },
]; ];
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console
console.log(error); console.log(error);
} finally { } finally {
emailLoading.value = false; emailLoading.value = false;
@ -445,6 +448,7 @@
emailConfigDrawerVisible.value = false; emailConfigDrawerVisible.value = false;
initEmailInfo(); initEmailInfo();
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console
console.log(error); console.log(error);
} finally { } finally {
emailDrawerLoading.value = false; emailDrawerLoading.value = false;
@ -509,6 +513,7 @@
await testEmail(params); await testEmail(params);
Message.success(t('system.config.email.testSuccess')); Message.success(t('system.config.email.testSuccess'));
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console
console.log(error); console.log(error);
} finally { } finally {
testLoading.value = false; testLoading.value = false;

View File

@ -21,7 +21,7 @@
<a-input-number <a-input-number
v-model:model-value="timeCount" v-model:model-value="timeCount"
class="w-[130px]" class="w-[130px]"
:disabled="saveLoading || !isHasAdminPermission" :disabled="saveLoading || !hasPermission"
:min="0" :min="0"
@blur="() => saveConfig()" @blur="() => saveConfig()"
> >
@ -30,6 +30,7 @@
v-model:model-value="activeTime" v-model:model-value="activeTime"
:options="timeOptions" :options="timeOptions"
class="select-input-append" class="select-input-append"
:disabled="!hasPermission"
:loading="saveLoading" :loading="saveLoading"
@change="() => saveConfig()" @change="() => saveConfig()"
/> />
@ -49,7 +50,7 @@
<a-input-number <a-input-number
v-model:model-value="historyCount" v-model:model-value="historyCount"
class="w-[130px]" class="w-[130px]"
:disabled="saveLoading || !isHasAdminPermission" :disabled="saveLoading || !hasPermission"
:min="0" :min="0"
@blur="() => saveConfig()" @blur="() => saveConfig()"
/> />
@ -65,9 +66,8 @@
import { getCleanupConfig, saveCleanupConfig } from '@/api/modules/setting/config'; import { getCleanupConfig, saveCleanupConfig } from '@/api/modules/setting/config';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useUserStore } from '@/store'; import { hasAnyPermission } from '@/utils/permission';
const userStore = useUserStore();
const { t } = useI18n(); const { t } = useI18n();
const loading = ref(false); const loading = ref(false);
@ -114,14 +114,14 @@
} }
}); });
const isHasAdminPermission = computed(() => { const hasPermission = computed(() => {
return userStore.isAdmin; return hasAnyPermission(['SYSTEM_PARAMETER_SETTING_MEMORY_CLEAN:READ+UPDATE']);
}); });
const saveLoading = ref(false); const saveLoading = ref(false);
async function saveConfig() { async function saveConfig() {
if (!isHasAdminPermission) { if (!hasPermission) {
return; return;
} }
saveLoading.value = true; saveLoading.value = true;

View File

@ -1,6 +1,6 @@
<template> <template>
<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-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'" />
<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'" /> <memoryCleanup v-if="isInitMemoryCleanup" v-show="activeTab === 'memoryCleanup'" />
@ -14,13 +14,17 @@
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 authConfig, { AuthConfigInstance } from './components/authConfig.vue';
import baseConfig from './components/baseConfig.vue';
import memoryCleanup from './components/memoryCleanup.vue';
import pageConfig from './components/pageConfig.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';
import { hasAnyPermission } from '@/utils/permission';
import type { AuthConfigInstance } from './components/authConfig.vue';
//
const baseConfig = defineAsyncComponent(() => import('./components/baseConfig.vue'));
const pageConfig = defineAsyncComponent(() => import('./components/pageConfig.vue'));
const authConfig = defineAsyncComponent(() => import('./components/authConfig.vue'));
const memoryCleanup = defineAsyncComponent(() => import('./components/memoryCleanup.vue'));
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -33,12 +37,11 @@
const tabList = ref([ const tabList = ref([
{ key: 'baseConfig', title: t('system.config.baseConfig'), permission: ['SYSTEM_PARAMETER_SETTING_BASE:READ'] }, { key: 'baseConfig', title: t('system.config.baseConfig'), permission: ['SYSTEM_PARAMETER_SETTING_BASE:READ'] },
{ key: 'pageConfig', title: t('system.config.pageConfig'), permission: ['SYSTEM_PARAMETER_SETTING_DISPLAY:READ'] }, { key: 'pageConfig', title: t('system.config.pageConfig'), permission: ['SYSTEM_PARAMETER_SETTING_DISPLAY:READ'] },
// TODO
{ key: 'authConfig', title: t('system.config.authConfig'), permission: ['SYSTEM_PARAMETER_SETTING_AUTH:READ'] }, { key: 'authConfig', title: t('system.config.authConfig'), permission: ['SYSTEM_PARAMETER_SETTING_AUTH:READ'] },
{ {
key: 'memoryCleanup', key: 'memoryCleanup',
title: t('system.config.memoryCleanup'), title: t('system.config.memoryCleanup'),
permission: ['SYSTEM_PARAMETER_SETTING_MEMORY_CLEAN:READ+UPDATE'], permission: ['SYSTEM_PARAMETER_SETTING_MEMORY_CLEAN:READ'],
}, },
]); ]);
@ -66,11 +69,17 @@
tabList.value = tabList.value.filter((item: any) => excludes.includes(item.key)); tabList.value = tabList.value.filter((item: any) => excludes.includes(item.key));
} }
} }
onBeforeMount(() => {
getXpackTab();
const firstHasPermissionTab = tabList.value.find((item: any) => hasAnyPermission(item.permission));
activeTab.value = firstHasPermissionTab?.key || 'baseConfig';
});
onMounted(() => { onMounted(() => {
if (route.query.tab === 'authConfig' && route.query.id) { if (route.query.tab === 'authConfig' && route.query.id) {
authConfigRef.value?.openAuthDetail(route.query.id as string); authConfigRef.value?.openAuthDetail(route.query.id as string);
} }
getXpackTab();
}); });
</script> </script>

View File

@ -105,11 +105,21 @@
</div> </div>
<ms-base-table v-bind="propsRes" no-disable sticky-header v-on="propsEvent"> <ms-base-table v-bind="propsRes" no-disable sticky-header v-on="propsEvent">
<template #range="{ record }"> <template #range="{ record }">
{{ <a-tooltip
record.organizationId === 'SYSTEM' :content="
? t('system.log.system') record.organizationId === 'SYSTEM'
: `${record.organizationName}${record.projectName ? `/${record.projectName}` : ''}` ? t('system.log.system')
}} : `${record.organizationName}${record.projectName ? `/${record.projectName}` : ''}`
"
>
<div class="one-line-text">
{{
record.organizationId === 'SYSTEM'
? t('system.log.system')
: `${record.organizationName}${record.projectName ? `/${record.projectName}` : ''}`
}}
</div>
</a-tooltip>
</template> </template>
<template #module="{ record }"> <template #module="{ record }">
{{ getModuleLocale(record.module) }} {{ getModuleLocale(record.module) }}
@ -447,22 +457,26 @@
{ {
title: 'system.log.operator', title: 'system.log.operator',
dataIndex: 'userName', dataIndex: 'userName',
width: 100,
showTooltip: true,
}, },
{ {
title: 'system.log.operateRange', title: 'system.log.operateRange',
dataIndex: 'operateRange', dataIndex: 'operateRange',
slotName: 'range', slotName: 'range',
width: 100,
}, },
{ {
title: 'system.log.operateTarget', title: 'system.log.operateTarget',
dataIndex: 'module', dataIndex: 'module',
slotName: 'module', slotName: 'module',
width: 100,
}, },
{ {
title: 'system.log.operateType', title: 'system.log.operateType',
dataIndex: 'type', dataIndex: 'type',
slotName: 'type', slotName: 'type',
width: 120, width: 80,
}, },
{ {
title: 'system.log.operateName', title: 'system.log.operateName',
@ -475,7 +489,7 @@
title: 'system.log.time', title: 'system.log.time',
dataIndex: 'createTime', dataIndex: 'createTime',
fixed: 'right', fixed: 'right',
width: 180, width: 100,
sortable: { sortable: {
sortDirections: ['ascend', 'descend'], sortDirections: ['ascend', 'descend'],
sorter: true, sorter: true,
@ -487,6 +501,7 @@
const { propsRes, propsEvent, loadList, setLoadListParams, resetPagination } = useTable( const { propsRes, propsEvent, loadList, setLoadListParams, resetPagination } = useTable(
requestFuncMap[props.mode].listFunc, requestFuncMap[props.mode].listFunc,
{ {
scroll: { x: 1100 },
tableKey: TableKeyEnum.SYSTEM_LOG, tableKey: TableKeyEnum.SYSTEM_LOG,
columns, columns,
selectable: false, selectable: false,

View File

@ -3,14 +3,19 @@
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<div> <div>
<a-button <a-button
v-permission="['SYSTEM_USER:READ+ADD', 'SYSTEM_USER_ROLE:READ']" v-permission.all="['SYSTEM_USER:READ+ADD', 'SYSTEM_USER_ROLE:READ']"
class="mr-3" class="mr-3"
type="primary" type="primary"
@click="showUserModal('create')" @click="showUserModal('create')"
> >
{{ t('system.user.createUser') }} {{ t('system.user.createUser') }}
</a-button> </a-button>
<a-button v-permission="['SYSTEM_USER_INVITE']" class="mr-3" type="outline" @click="showEmailInviteModal"> <a-button
v-permission.all="['SYSTEM_USER:READ+INVITE', 'SYSTEM_USER_ROLE:READ']"
class="mr-3"
type="outline"
@click="showEmailInviteModal"
>
{{ 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']" class="mr-3" type="outline" @click="showImportModal">
@ -586,7 +591,12 @@
{ {
label: 'system.user.batchActionAddProject', label: 'system.user.batchActionAddProject',
eventTag: 'batchAddProject', eventTag: 'batchAddProject',
permission: ['SYSTEM_USER:READ+UPDATE', 'SYSTEM_ORGANIZATION_PROJECT:READ'], permission: [
'SYSTEM_USER:READ+UPDATE',
'SYSTEM_USER_ROLE:READ',
'SYSTEM_ORGANIZATION_PROJECT:READ',
'SYSTEM_ORGANIZATION_PROJECT_MEMBER:ADD',
],
}, },
{ {
label: 'system.user.batchActionAddUserGroup', label: 'system.user.batchActionAddUserGroup',
@ -596,7 +606,12 @@
{ {
label: 'system.user.batchActionAddOrganization', label: 'system.user.batchActionAddOrganization',
eventTag: 'batchAddOrganization', eventTag: 'batchAddOrganization',
permission: ['SYSTEM_USER:READ+UPDATE', 'SYSTEM_ORGANIZATION_PROJECT:READ'], permission: [
'SYSTEM_USER:READ+UPDATE',
'SYSTEM_USER_ROLE:READ',
'SYSTEM_ORGANIZATION_PROJECT:READ',
'SYSTEM_ORGANIZATION_PROJECT_MEMBER:ADD',
],
}, },
], ],
moreAction: [ moreAction: [