feat(接口测试): 接口调试-模块树重命名&删除、部分bug修复
This commit is contained in:
parent
12071962b7
commit
b35fdf8355
|
@ -3,6 +3,7 @@ import {
|
|||
AddApiDebugUrl,
|
||||
AddDebugModuleUrl,
|
||||
DeleteDebugModuleUrl,
|
||||
DeleteDebugUrl,
|
||||
ExecuteApiDebugUrl,
|
||||
GetApiDebugDetailUrl,
|
||||
GetDebugModuleCountUrl,
|
||||
|
@ -71,3 +72,8 @@ export function updateDebug(data: UpdateDebugParams) {
|
|||
export function getDebugDetail(id: string) {
|
||||
return MSR.get<DebugDetail>({ url: GetApiDebugDetailUrl, params: id });
|
||||
}
|
||||
|
||||
// 删除接口调试
|
||||
export function deleteDebug(id: string) {
|
||||
return MSR.get({ url: DeleteDebugUrl, params: id });
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ export const ExecuteApiDebugUrl = '/api/debug/debug'; // 执行调试
|
|||
export const AddApiDebugUrl = '/api/debug/add'; // 新增调试
|
||||
export const UpdateApiDebugUrl = '/api/debug/update'; // 更新调试
|
||||
export const GetApiDebugDetailUrl = '/api/debug/get'; // 获取接口调试详情
|
||||
export const DeleteDebugUrl = '/api/debug/delete'; // 删除调试
|
||||
export const UpdateDebugModuleUrl = '/api/debug/module/update'; // 更新模块
|
||||
export const MoveDebugModuleUrl = '/api/debug/module/move'; // 移动模块
|
||||
export const GetDebugModuleCountUrl = '/api/debug/module/count'; // 模块统计数量
|
||||
|
|
|
@ -215,6 +215,10 @@
|
|||
}
|
||||
.arco-btn-size-mini {
|
||||
line-height: 16px;
|
||||
.arco-icon-loading {
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/** 输入框,选择器,文本域 **/
|
||||
|
|
|
@ -249,7 +249,7 @@
|
|||
margin-left: -16px !important;
|
||||
border-radius: 0 4px 4px 0 !important;
|
||||
background-color: var(--color-text-n8) !important;
|
||||
&:hover {
|
||||
&:hover:not(.arco-select-view-disabled) {
|
||||
border-color: rgb(var(--primary-5)) !important;
|
||||
background-color: var(--color-text-n8) !important;
|
||||
}
|
||||
|
|
|
@ -388,7 +388,7 @@
|
|||
|
||||
let mouseEnterTimer;
|
||||
// 渲染菜单项
|
||||
const renderMenuItem = (element, icon) =>
|
||||
const renderMenuItem = (element: RouteRecordRaw | null, icon) =>
|
||||
element?.name === SettingRouteEnum.SETTING_ORGANIZATION ? (
|
||||
<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">
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
<div class="flex items-center justify-between px-[16px]">
|
||||
<MsTableMoreAction :list="actions" trigger="click" @select="handleMoreActionSelect($event, item)">
|
||||
<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"
|
||||
type="outline"
|
||||
class="arco-btn-outline--secondary"
|
||||
|
@ -83,10 +83,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<div v-if="apiKeyList.length === 0" class="col-span-2 flex w-full items-center justify-center p-[44px]">
|
||||
{{ t('ms.personal.nodata') }}
|
||||
<MsButton v-permission="['SYSTEM_PERSONAL_API_KEY:READ+ADD']" type="text" class="ml-[8px]" @click="newApiKey">{{
|
||||
t('common.new')
|
||||
}}</MsButton>
|
||||
{{ 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">
|
||||
{{ t('common.new') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
|
@ -156,6 +156,7 @@
|
|||
} from '@/api/modules/user/index';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import { APIKEY } from '@/models/user';
|
||||
|
||||
|
@ -169,6 +170,7 @@
|
|||
desensitization: boolean;
|
||||
}
|
||||
const apiKeyList = ref<APIKEYItem[]>([]);
|
||||
const hasCratePermission = hasAnyPermission(['SYSTEM_PERSONAL_API_KEY:READ+ADD']);
|
||||
|
||||
async function initApiKeys() {
|
||||
try {
|
||||
|
@ -210,6 +212,7 @@
|
|||
{
|
||||
label: t('ms.personal.validTime'),
|
||||
eventTag: 'time',
|
||||
permission: ['SYSTEM_PERSONAL_API_KEY:READ+UPDATE'],
|
||||
},
|
||||
{
|
||||
isDivider: true,
|
||||
|
@ -218,6 +221,7 @@
|
|||
label: t('common.delete'),
|
||||
danger: true,
|
||||
eventTag: 'delete',
|
||||
permission: ['SYSTEM_PERSONAL_API_KEY:READ+DELETE'],
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<div v-for="config of dynamicForm" :key="config.key" class="platform-card">
|
||||
<div class="mb-[16px] flex items-center">
|
||||
<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">
|
||||
<icon-exclamation-circle
|
||||
class="mr-[8px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
|
@ -122,6 +122,7 @@
|
|||
Object.keys(res).forEach((key) => {
|
||||
dynamicForm.value[key] = {
|
||||
key,
|
||||
name: res[key].name,
|
||||
status: 0,
|
||||
formModel: {},
|
||||
formRules: res[key].formItems,
|
||||
|
|
|
@ -87,5 +87,6 @@ export default {
|
|||
'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.',
|
||||
'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',
|
||||
};
|
||||
|
|
|
@ -79,5 +79,6 @@ export default {
|
|||
'ms.personal.zendaoTip': '该信息为通过禅道提交缺陷的的用户名、密码,若未填写,则使用组织配置的默认信息',
|
||||
'ms.personal.azureTip': '该信息为通过Azure Devops提交缺陷的用户令牌信息,若未填写,则使用组织配置的默认信息',
|
||||
'ms.personal.azurePlaceholder': '请输入 Personal Access Tokens',
|
||||
'ms.personal.nodata': '暂无数据,请 ',
|
||||
'ms.personal.noData': '暂无数据,请 ',
|
||||
'ms.personal.empty': '暂无数据',
|
||||
};
|
||||
|
|
|
@ -355,8 +355,10 @@
|
|||
<style lang="less">
|
||||
.ms-tree-container {
|
||||
.ms-container--shadow-y();
|
||||
@apply h-full;
|
||||
.ms-tree {
|
||||
.ms-scroll-bar();
|
||||
@apply h-full;
|
||||
.arco-tree-node {
|
||||
border-radius: var(--border-radius-small);
|
||||
&:hover {
|
||||
|
|
|
@ -8,7 +8,12 @@
|
|||
</slot>
|
||||
<template #content>
|
||||
<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
|
||||
v-else
|
||||
:key="item.label"
|
||||
|
@ -31,6 +36,7 @@
|
|||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import type { ActionsItem, SelectedValue } from './types';
|
||||
|
||||
|
@ -42,6 +48,38 @@
|
|||
|
||||
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) {
|
||||
const item = props.list.find((e: ActionsItem) => e.eventTag === value);
|
||||
emit('select', item);
|
||||
|
|
|
@ -256,7 +256,8 @@ export default function useTableProps<T>(
|
|||
const data = await loadListFunc({ keyword: keyword.value, ...loadListParams.value });
|
||||
if (data.length === 0) {
|
||||
setTableErrorStatus('empty');
|
||||
return;
|
||||
propsRes.value.data = [];
|
||||
return data;
|
||||
}
|
||||
setTableErrorStatus(false);
|
||||
propsRes.value.data = data.map((item: MsTableDataItem<T>) => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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 绑定的数据
|
||||
*/
|
||||
function checkPermission(el: HTMLElement, binding: DirectiveBinding) {
|
||||
const { value } = binding;
|
||||
const { value, modifiers } = binding;
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length > 0) {
|
||||
const hasPermission = hasAnyPermission(value);
|
||||
// 如果有 all 修饰符,表示需要全部权限;否则只需要其中一个权限
|
||||
const hasPermission = modifiers.all ? hasAllPermission(value) : hasAnyPermission(value);
|
||||
if (!hasPermission && el.parentNode) {
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
|
|
|
@ -313,10 +313,10 @@ export interface SaveDebugParams {
|
|||
linkFileIds: string[];
|
||||
}
|
||||
// 更新接口调试入参
|
||||
export interface UpdateDebugParams extends SaveDebugParams {
|
||||
export interface UpdateDebugParams extends Partial<SaveDebugParams> {
|
||||
id: string;
|
||||
deleteFileIds: string[];
|
||||
unLinkRefIds: string[];
|
||||
deleteFileIds?: string[];
|
||||
unLinkRefIds?: string[];
|
||||
}
|
||||
// 更新模块入参
|
||||
export interface UpdateDebugModule {
|
||||
|
|
|
@ -16,6 +16,9 @@ const Setting: AppRouteRecordRaw = {
|
|||
'SYSTEM_USER_ROLE:READ',
|
||||
'SYSTEM_ORGANIZATION_PROJECT: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_AUTH:READ',
|
||||
'SYSTEM_PLUGIN:READ',
|
||||
|
@ -40,6 +43,9 @@ const Setting: AppRouteRecordRaw = {
|
|||
'SYSTEM_USER_ROLE:READ',
|
||||
'SYSTEM_ORGANIZATION_PROJECT: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_AUTH:READ',
|
||||
'SYSTEM_PLUGIN:READ',
|
||||
|
@ -84,7 +90,12 @@ const Setting: AppRouteRecordRaw = {
|
|||
component: () => import('@/views/setting/system/config/index.vue'),
|
||||
meta: {
|
||||
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,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -548,3 +548,25 @@ export function tableParamsToRequestParams(params: BatchActionQueryParams) {
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -11,10 +11,7 @@
|
|||
>
|
||||
<template #content>
|
||||
<div class="mb-[8px] font-medium">
|
||||
{{
|
||||
props.title ||
|
||||
(props.mode === 'add' ? t('project.fileManagement.addSubModule') : t('project.fileManagement.rename'))
|
||||
}}
|
||||
{{ props.title || (props.mode === 'add' ? t('project.fileManagement.addSubModule') : t('common.rename')) }}
|
||||
</div>
|
||||
<a-form ref="formRef" :model="form" layout="vertical">
|
||||
<a-form-item
|
||||
|
@ -51,7 +48,7 @@
|
|||
import { ref, watch } from '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 useAppStore from '@/store/modules/app';
|
||||
|
||||
|
@ -67,6 +64,7 @@
|
|||
|
||||
const props = defineProps<{
|
||||
mode: 'add' | 'rename';
|
||||
nodeType?: 'MODULE' | 'API';
|
||||
visible?: boolean;
|
||||
title?: string;
|
||||
allNames: string[];
|
||||
|
@ -128,16 +126,24 @@
|
|||
parentId: props.parentId || '',
|
||||
name: form.value.field,
|
||||
});
|
||||
Message.success(t('project.fileManagement.addSubModuleSuccess'));
|
||||
Message.success(t('common.addSuccess'));
|
||||
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') {
|
||||
// 模块重命名
|
||||
await updateDebugModule({
|
||||
id: props.nodeId || '',
|
||||
name: form.value.field,
|
||||
});
|
||||
Message.success(t('project.fileManagement.renameSuccess'));
|
||||
emit('renameFinish', form.value.field);
|
||||
Message.success(t('common.updateSuccess'));
|
||||
emit('renameFinish', form.value.field, props.nodeId);
|
||||
}
|
||||
if (done) {
|
||||
done(true);
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
v-model:model-value="requestVModel.url"
|
||||
:max-length="255"
|
||||
:placeholder="t('apiTestDebug.urlPlaceholder')"
|
||||
@change="handleActiveDebugChange"
|
||||
@change="handleUrlChange"
|
||||
/>
|
||||
</a-input-group>
|
||||
</div>
|
||||
|
@ -230,14 +230,14 @@
|
|||
import { getLocalConfig } from '@/api/modules/user/index';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
import { filterTree, getGenerateId } from '@/utils';
|
||||
import { filterTree, getGenerateId, parseQueryParams } from '@/utils';
|
||||
import { scrollIntoView } from '@/utils/dom';
|
||||
import { registerCatchSaveShortcut, removeCatchSaveShortcut } from '@/utils/event';
|
||||
|
||||
import { PluginConfig } from '@/models/apiTest/common';
|
||||
import { ExecuteHTTPRequestFullParams } from '@/models/apiTest/debug';
|
||||
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';
|
||||
|
||||
|
@ -250,6 +250,7 @@
|
|||
export interface RequestCustomAttr {
|
||||
isNew: boolean;
|
||||
protocol: string;
|
||||
activeTab: RequestComposition;
|
||||
}
|
||||
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 activeLayout = ref<'horizontal' | 'vertical'>('vertical');
|
||||
const splitContainerRef = ref<HTMLElement>();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="flex h-full flex-col p-[24px]">
|
||||
<div class="mb-[8px] flex items-center gap-[8px]">
|
||||
<a-input v-model:model-value="moduleKeyword" :placeholder="t('apiTestDebug.searchTip')" allow-clear />
|
||||
<a-dropdown @select="handleSelect">
|
||||
|
@ -34,7 +34,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<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
|
||||
v-model:focus-node-key="focusNodeKey"
|
||||
:data="folderTree"
|
||||
|
@ -43,8 +43,9 @@
|
|||
:default-expand-all="isExpandAll"
|
||||
:expand-all="isExpandAll"
|
||||
:empty-text="t('apiTestDebug.noMatchModule')"
|
||||
:draggable="true"
|
||||
:virtual-list-props="virtualListProps"
|
||||
:virtual-list-props="{
|
||||
height: '100%',
|
||||
}"
|
||||
:field-names="{
|
||||
title: 'name',
|
||||
key: 'id',
|
||||
|
@ -93,8 +94,9 @@
|
|||
:node-id="nodeData.id"
|
||||
:field-config="{ field: renameFolderTitle }"
|
||||
:all-names="(nodeData.children || []).map((e: ModuleTreeNode) => e.name || '')"
|
||||
:node-type="nodeData.type"
|
||||
@close="resetFocusNodeKey"
|
||||
@rename-finish="initModules"
|
||||
@rename-finish="handleRenameFinish"
|
||||
>
|
||||
<span :id="`renameSpan${nodeData.id}`" class="relative"></span>
|
||||
</popConfirm>
|
||||
|
@ -117,6 +119,7 @@
|
|||
import popConfirm from '@/views/api-test/components/popConfirm.vue';
|
||||
|
||||
import {
|
||||
deleteDebug,
|
||||
deleteDebugModule,
|
||||
getDebugModuleCount,
|
||||
getDebugModules,
|
||||
|
@ -131,7 +134,7 @@
|
|||
const props = defineProps<{
|
||||
isExpandAll?: boolean; // 是否展开所有节点
|
||||
}>();
|
||||
const emit = defineEmits(['init', 'clickApiNode', 'newApi', 'import']);
|
||||
const emit = defineEmits(['init', 'clickApiNode', 'newApi', 'import', 'renameFinish']);
|
||||
|
||||
const { t } = useI18n();
|
||||
const { openModal } = useModal();
|
||||
|
@ -150,12 +153,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
const virtualListProps = computed(() => {
|
||||
return {
|
||||
height: 'calc(100% - 180px)',
|
||||
};
|
||||
});
|
||||
|
||||
const isExpandAll = ref(props.isExpandAll);
|
||||
const rootModulesName = ref<string[]>([]); // 根模块名称列表
|
||||
|
||||
|
@ -204,9 +201,9 @@
|
|||
return {
|
||||
...e,
|
||||
hideMoreAction: e.id === 'root',
|
||||
draggable: e.id !== 'root',
|
||||
};
|
||||
});
|
||||
rootModulesName.value = folderTree.value.map((e) => e.name || '');
|
||||
emit('init', folderTree.value);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -228,7 +225,6 @@
|
|||
return {
|
||||
...node,
|
||||
count: res[node.id] || 0,
|
||||
draggable: node.id !== 'root',
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
|
@ -273,6 +269,34 @@
|
|||
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
|
||||
|
@ -280,7 +304,11 @@
|
|||
function handleFolderMoreSelect(item: ActionsItem, node: MsTreeNodeData) {
|
||||
switch (item.eventTag) {
|
||||
case 'delete':
|
||||
deleteFolder(node);
|
||||
if (node.type === 'MODULE') {
|
||||
deleteFolder(node);
|
||||
} else {
|
||||
deleteApiDebug(node);
|
||||
}
|
||||
resetFocusNodeKey();
|
||||
break;
|
||||
case 'rename':
|
||||
|
@ -319,7 +347,7 @@
|
|||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
initModules();
|
||||
await initModules();
|
||||
initModuleCount();
|
||||
}
|
||||
}
|
||||
|
@ -331,8 +359,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
function handleRenameFinish(newName: string, id: string) {
|
||||
initModules();
|
||||
emit('renameFinish', newName, id);
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await initModules();
|
||||
initModuleCount();
|
||||
});
|
||||
|
||||
|
|
|
@ -3,15 +3,14 @@
|
|||
<MsCard :loading="loading" simple no-content-padding>
|
||||
<MsSplitBox :size="0.25" :max="0.5">
|
||||
<template #first>
|
||||
<div class="p-[24px]">
|
||||
<moduleTree
|
||||
ref="moduleTreeRef"
|
||||
@init="(val) => (folderTree = val)"
|
||||
@new-api="addDebugTab"
|
||||
@click-api-node="openApiTab"
|
||||
@import="importDrawerVisible = true"
|
||||
/>
|
||||
</div>
|
||||
<moduleTree
|
||||
ref="moduleTreeRef"
|
||||
@init="(val) => (folderTree = val)"
|
||||
@new-api="addDebugTab"
|
||||
@click-api-node="openApiTab"
|
||||
@import="importDrawerVisible = true"
|
||||
@rename-finish="handleRenameFinish"
|
||||
/>
|
||||
</template>
|
||||
<template #second>
|
||||
<div class="flex h-full flex-col">
|
||||
|
@ -113,8 +112,8 @@
|
|||
const curlCode = ref('');
|
||||
const loading = ref(false);
|
||||
|
||||
function handleDebugAddDone() {
|
||||
moduleTreeRef.value?.initModules();
|
||||
async function handleDebugAddDone() {
|
||||
await moduleTreeRef.value?.initModules();
|
||||
moduleTreeRef.value?.initModuleCount();
|
||||
}
|
||||
|
||||
|
@ -289,6 +288,16 @@
|
|||
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>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
|
|
@ -96,6 +96,8 @@ export default {
|
|||
'apiTestDebug.deleteFolderTipTitle': 'Remove the `{name}` module?',
|
||||
'apiTestDebug.deleteFolderTipContent':
|
||||
'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.deleteSuccess': 'Successfully deleted',
|
||||
'apiTestDebug.moduleMoveSuccess': 'Module moved successfully',
|
||||
|
|
|
@ -88,8 +88,10 @@ export default {
|
|||
'apiTestDebug.extractParameter': '提取参数',
|
||||
'apiTestDebug.searchTip': '请输入模块/请求名称',
|
||||
'apiTestDebug.allRequest': '全部请求',
|
||||
'apiTestDebug.deleteFolderTipTitle': '是否删除 `{name}` 模块?',
|
||||
'apiTestDebug.deleteFolderTipTitle': '是否删除 {name} 模块?',
|
||||
'apiTestDebug.deleteFolderTipContent': '该操作会删除模块及其下所有资源,请谨慎操作!',
|
||||
'apiTestDebug.deleteDebugTipTitle': '是否删除 {name}?',
|
||||
'apiTestDebug.deleteDebugTipContent': '删除后无法恢复,请谨慎操作!',
|
||||
'apiTestDebug.deleteConfirm': '确认删除',
|
||||
'apiTestDebug.deleteSuccess': '删除成功',
|
||||
'apiTestDebug.moduleMoveSuccess': '模块移动成功',
|
||||
|
|
|
@ -276,6 +276,7 @@
|
|||
const innerVisible = ref(false);
|
||||
const fileDescriptions = ref<Description[]>([]);
|
||||
const detailDrawerRef = ref<InstanceType<typeof MsDetailDrawer>>();
|
||||
const innerFileId = ref(props.fileId);
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
|
@ -293,7 +294,7 @@
|
|||
|
||||
async function handleEnableIntercept(newValue: string | number | boolean) {
|
||||
try {
|
||||
await toggleJarFileStatus(props.fileId, newValue as boolean);
|
||||
await toggleJarFileStatus(innerFileId.value, newValue as boolean);
|
||||
return true;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -332,7 +333,7 @@
|
|||
fileLoading.value = true;
|
||||
await reuploadFile({
|
||||
request: {
|
||||
fileId: props.fileId,
|
||||
fileId: innerFileId.value,
|
||||
enable: false,
|
||||
},
|
||||
file: data,
|
||||
|
@ -362,7 +363,7 @@
|
|||
|
||||
async function addFileTag(val: string, item: Description) {
|
||||
await updateFile({
|
||||
id: props.fileId,
|
||||
id: innerFileId.value,
|
||||
tags: Array.isArray(item.value) ? [...item.value, val] : [item.value, val],
|
||||
});
|
||||
}
|
||||
|
@ -371,7 +372,7 @@
|
|||
try {
|
||||
const lastTags = Array.isArray(item.value) ? item.value.filter((e) => e !== tag) : [];
|
||||
await updateFile({
|
||||
id: props.fileId,
|
||||
id: innerFileId.value,
|
||||
tags: lastTags,
|
||||
});
|
||||
item.value = [...lastTags];
|
||||
|
@ -387,7 +388,7 @@
|
|||
async function upgradeRepositoryFile() {
|
||||
try {
|
||||
fileLoading.value = true;
|
||||
await updateRepositoryFile(props.fileId);
|
||||
await updateRepositoryFile(innerFileId.value);
|
||||
Message.success(t('common.updateSuccess'));
|
||||
detailDrawerRef.value?.initDetail();
|
||||
} catch (error) {
|
||||
|
@ -534,18 +535,19 @@
|
|||
function loadTable() {
|
||||
if (activeTab.value === 'case') {
|
||||
setLoadListParams({
|
||||
id: props.fileId,
|
||||
id: innerFileId.value,
|
||||
});
|
||||
loadCaseList();
|
||||
} else {
|
||||
setVersionLoadListParams({
|
||||
id: props.fileId,
|
||||
id: innerFileId.value,
|
||||
});
|
||||
loadVersionList();
|
||||
}
|
||||
}
|
||||
|
||||
function loadedFile(detail: FileDetail) {
|
||||
innerFileId.value = detail.id;
|
||||
fileType.value = detail.fileType;
|
||||
renameTitle.value = detail.name;
|
||||
fileDescriptions.value = [
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<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="text-[var(--color-text-000)]">{{ t('system.config.baseInfo') }}</div>
|
||||
<a-button
|
||||
|
@ -12,7 +12,7 @@
|
|||
{{ t('system.config.update') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<MsDescription :descriptions="baseInfoDescs" class="no-bottom" :column="2" />
|
||||
<MsDescription :descriptions="baseInfoDesc" class="no-bottom" :column="2" />
|
||||
</MsCard>
|
||||
<MsCard class="mb-[16px]" :loading="emailLoading" simple auto-height>
|
||||
<div class="mb-[16px] flex justify-between">
|
||||
|
@ -26,7 +26,7 @@
|
|||
{{ t('system.config.update') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<MsDescription :descriptions="emailInfoDescs" :column="2">
|
||||
<MsDescription :descriptions="emailInfoDesc" :column="2">
|
||||
<template #value="{ item }">
|
||||
<template v-if="item.key && ['ssl', 'tsl'].includes(item.key)">
|
||||
<div v-if="item.value === 'true'" class="flex items-center">
|
||||
|
@ -230,7 +230,7 @@
|
|||
|
||||
const { t } = useI18n();
|
||||
|
||||
const baseloading = ref(false);
|
||||
const baseLoading = ref(false);
|
||||
const baseDrawerLoading = ref(false);
|
||||
const baseInfoDrawerVisible = ref(false);
|
||||
const baseFormRef = ref<FormInstance>();
|
||||
|
@ -239,7 +239,7 @@
|
|||
prometheusHost: 'http://prometheus:9090',
|
||||
});
|
||||
const baseInfoForm = ref({ ...baseInfo.value });
|
||||
const baseInfoDescs = ref<Description[]>([]);
|
||||
const baseInfoDesc = ref<Description[]>([]);
|
||||
// 默认示例
|
||||
const defaultUrl = 'https://metersphere.com';
|
||||
const defaultPrometheus = 'http://prometheus:9090';
|
||||
|
@ -259,12 +259,12 @@
|
|||
const licenseStore = useLicenseStore();
|
||||
async function initBaseInfo() {
|
||||
try {
|
||||
baseloading.value = true;
|
||||
baseLoading.value = true;
|
||||
const res = await getBaseInfo();
|
||||
baseInfo.value = { ...res };
|
||||
baseInfoForm.value = { ...res };
|
||||
if (licenseStore.hasLicense()) {
|
||||
baseInfoDescs.value = [
|
||||
baseInfoDesc.value = [
|
||||
{
|
||||
label: t('system.config.pageUrl'),
|
||||
value: res.url,
|
||||
|
@ -275,7 +275,7 @@
|
|||
},
|
||||
];
|
||||
} else {
|
||||
baseInfoDescs.value = [
|
||||
baseInfoDesc.value = [
|
||||
{
|
||||
label: t('system.config.pageUrl'),
|
||||
value: res.url,
|
||||
|
@ -283,9 +283,10 @@
|
|||
];
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
baseloading.value = false;
|
||||
baseLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -313,6 +314,7 @@
|
|||
baseInfoDrawerVisible.value = false;
|
||||
initBaseInfo();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
baseDrawerLoading.value = false;
|
||||
|
@ -343,7 +345,7 @@
|
|||
});
|
||||
const emailConfigForm = ref({ ...emailConfig.value });
|
||||
const emailFormRef = ref<FormInstance>();
|
||||
const emailInfoDescs = ref<Description[]>([]);
|
||||
const emailInfoDesc = ref<Description[]>([]);
|
||||
|
||||
const pswInVisible = ref(false); // 是否展示未脱敏密码
|
||||
|
||||
|
@ -366,12 +368,12 @@
|
|||
try {
|
||||
emailLoading.value = true;
|
||||
const res = await getEmailInfo();
|
||||
const _ssl = Boolean(res.ssl);
|
||||
const _tsl = Boolean(res.tsl);
|
||||
emailConfig.value = { ...res, ssl: _ssl, tsl: _tsl };
|
||||
emailConfigForm.value = { ...res, ssl: _ssl, tsl: _tsl };
|
||||
const { host, port, account, password, from, recipient, ssl, tsl } = res;
|
||||
emailInfoDescs.value = [
|
||||
const ssl = res.ssl === 'true';
|
||||
const tsl = res.tsl === 'true';
|
||||
emailConfig.value = { ...res, ssl, tsl };
|
||||
emailConfigForm.value = { ...res, ssl, tsl };
|
||||
const { host, port, account, password, from, recipient } = res;
|
||||
emailInfoDesc.value = [
|
||||
{
|
||||
label: t('system.config.email.host'),
|
||||
value: host,
|
||||
|
@ -399,16 +401,17 @@
|
|||
},
|
||||
{
|
||||
label: t('system.config.email.ssl'),
|
||||
value: ssl,
|
||||
value: ssl.toString(),
|
||||
key: 'ssl',
|
||||
},
|
||||
{
|
||||
label: t('system.config.email.tsl'),
|
||||
value: tsl,
|
||||
value: tsl.toString(),
|
||||
key: 'tsl',
|
||||
},
|
||||
];
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
emailLoading.value = false;
|
||||
|
@ -445,6 +448,7 @@
|
|||
emailConfigDrawerVisible.value = false;
|
||||
initEmailInfo();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
emailDrawerLoading.value = false;
|
||||
|
@ -509,6 +513,7 @@
|
|||
await testEmail(params);
|
||||
Message.success(t('system.config.email.testSuccess'));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
testLoading.value = false;
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<a-input-number
|
||||
v-model:model-value="timeCount"
|
||||
class="w-[130px]"
|
||||
:disabled="saveLoading || !isHasAdminPermission"
|
||||
:disabled="saveLoading || !hasPermission"
|
||||
:min="0"
|
||||
@blur="() => saveConfig()"
|
||||
>
|
||||
|
@ -30,6 +30,7 @@
|
|||
v-model:model-value="activeTime"
|
||||
:options="timeOptions"
|
||||
class="select-input-append"
|
||||
:disabled="!hasPermission"
|
||||
:loading="saveLoading"
|
||||
@change="() => saveConfig()"
|
||||
/>
|
||||
|
@ -49,7 +50,7 @@
|
|||
<a-input-number
|
||||
v-model:model-value="historyCount"
|
||||
class="w-[130px]"
|
||||
:disabled="saveLoading || !isHasAdminPermission"
|
||||
:disabled="saveLoading || !hasPermission"
|
||||
:min="0"
|
||||
@blur="() => saveConfig()"
|
||||
/>
|
||||
|
@ -65,9 +66,8 @@
|
|||
|
||||
import { getCleanupConfig, saveCleanupConfig } from '@/api/modules/setting/config';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useUserStore } from '@/store';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { t } = useI18n();
|
||||
const loading = ref(false);
|
||||
|
||||
|
@ -114,14 +114,14 @@
|
|||
}
|
||||
});
|
||||
|
||||
const isHasAdminPermission = computed(() => {
|
||||
return userStore.isAdmin;
|
||||
const hasPermission = computed(() => {
|
||||
return hasAnyPermission(['SYSTEM_PARAMETER_SETTING_MEMORY_CLEAN:READ+UPDATE']);
|
||||
});
|
||||
|
||||
const saveLoading = ref(false);
|
||||
|
||||
async function saveConfig() {
|
||||
if (!isHasAdminPermission) {
|
||||
if (!hasPermission) {
|
||||
return;
|
||||
}
|
||||
saveLoading.value = true;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<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'" />
|
||||
<authConfig v-if="isInitAuthConfig" v-show="activeTab === 'authConfig'" ref="authConfigRef" />
|
||||
<memoryCleanup v-if="isInitMemoryCleanup" v-show="activeTab === 'memoryCleanup'" />
|
||||
|
@ -14,13 +14,17 @@
|
|||
import { useRoute } from 'vue-router';
|
||||
|
||||
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 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 route = useRoute();
|
||||
|
@ -33,12 +37,11 @@
|
|||
const tabList = ref([
|
||||
{ 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'] },
|
||||
// TODO 后台目前没有写 第一版不上
|
||||
{ key: 'authConfig', title: t('system.config.authConfig'), permission: ['SYSTEM_PARAMETER_SETTING_AUTH:READ'] },
|
||||
{
|
||||
key: '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));
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
getXpackTab();
|
||||
const firstHasPermissionTab = tabList.value.find((item: any) => hasAnyPermission(item.permission));
|
||||
activeTab.value = firstHasPermissionTab?.key || 'baseConfig';
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (route.query.tab === 'authConfig' && route.query.id) {
|
||||
authConfigRef.value?.openAuthDetail(route.query.id as string);
|
||||
}
|
||||
getXpackTab();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -105,11 +105,21 @@
|
|||
</div>
|
||||
<ms-base-table v-bind="propsRes" no-disable sticky-header v-on="propsEvent">
|
||||
<template #range="{ record }">
|
||||
{{
|
||||
record.organizationId === 'SYSTEM'
|
||||
? t('system.log.system')
|
||||
: `${record.organizationName}${record.projectName ? `/${record.projectName}` : ''}`
|
||||
}}
|
||||
<a-tooltip
|
||||
:content="
|
||||
record.organizationId === 'SYSTEM'
|
||||
? 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 #module="{ record }">
|
||||
{{ getModuleLocale(record.module) }}
|
||||
|
@ -447,22 +457,26 @@
|
|||
{
|
||||
title: 'system.log.operator',
|
||||
dataIndex: 'userName',
|
||||
width: 100,
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'system.log.operateRange',
|
||||
dataIndex: 'operateRange',
|
||||
slotName: 'range',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'system.log.operateTarget',
|
||||
dataIndex: 'module',
|
||||
slotName: 'module',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'system.log.operateType',
|
||||
dataIndex: 'type',
|
||||
slotName: 'type',
|
||||
width: 120,
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: 'system.log.operateName',
|
||||
|
@ -475,7 +489,7 @@
|
|||
title: 'system.log.time',
|
||||
dataIndex: 'createTime',
|
||||
fixed: 'right',
|
||||
width: 180,
|
||||
width: 100,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
|
@ -487,6 +501,7 @@
|
|||
const { propsRes, propsEvent, loadList, setLoadListParams, resetPagination } = useTable(
|
||||
requestFuncMap[props.mode].listFunc,
|
||||
{
|
||||
scroll: { x: 1100 },
|
||||
tableKey: TableKeyEnum.SYSTEM_LOG,
|
||||
columns,
|
||||
selectable: false,
|
||||
|
|
|
@ -3,14 +3,19 @@
|
|||
<div class="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<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"
|
||||
type="primary"
|
||||
@click="showUserModal('create')"
|
||||
>
|
||||
{{ t('system.user.createUser') }}
|
||||
</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') }}
|
||||
</a-button>
|
||||
<a-button v-permission="['SYSTEM_USER:READ+IMPORT']" class="mr-3" type="outline" @click="showImportModal">
|
||||
|
@ -586,7 +591,12 @@
|
|||
{
|
||||
label: 'system.user.batchActionAddProject',
|
||||
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',
|
||||
|
@ -596,7 +606,12 @@
|
|||
{
|
||||
label: 'system.user.batchActionAddOrganization',
|
||||
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: [
|
||||
|
|
Loading…
Reference in New Issue