feat: 页面及按钮权限&环境管理新增环境接口对接
This commit is contained in:
parent
8ca4f27432
commit
09b4b6756a
|
@ -67,6 +67,10 @@
|
||||||
const checkIsLogin = async () => {
|
const checkIsLogin = async () => {
|
||||||
const isLogin = await userStore.isLogin();
|
const isLogin = await userStore.isLogin();
|
||||||
const isLoginPage = route.name === 'login';
|
const isLoginPage = route.name === 'login';
|
||||||
|
if (isLogin && appStore.currentProjectId) {
|
||||||
|
// 当前为登陆状态,且已经选择了项目,初始化当前项目配置
|
||||||
|
appStore.setCurrentMenuConfig();
|
||||||
|
}
|
||||||
if (isLoginPage && isLogin) {
|
if (isLoginPage && isLogin) {
|
||||||
// 当前页面为登录页面,且已经登录,跳转到首页
|
// 当前页面为登录页面,且已经登录,跳转到首页
|
||||||
router.push(WorkbenchRouteEnum.WORKBENCH);
|
router.push(WorkbenchRouteEnum.WORKBENCH);
|
||||||
|
|
|
@ -12,8 +12,8 @@ import type {
|
||||||
} from '@/models/projectManagement/environmental';
|
} from '@/models/projectManagement/environmental';
|
||||||
import { OptionsItem } from '@/models/setting/log';
|
import { OptionsItem } from '@/models/setting/log';
|
||||||
|
|
||||||
export function updateEnv(data: EnvListItem) {
|
export function updateOrAddEnv(data: EnvDetailItem) {
|
||||||
return MSR.post<EnvListItem>({ url: envURL.updateEnvUrl, data });
|
return MSR.post<EnvDetailItem>({ url: data.id ? envURL.updateEnvUrl : envURL.addEnvUrl, data });
|
||||||
}
|
}
|
||||||
export function listEnv(data: { projectId: string; keyword: string }) {
|
export function listEnv(data: { projectId: string; keyword: string }) {
|
||||||
return MSR.post<EnvListItem[]>({ url: envURL.listEnvUrl, data });
|
return MSR.post<EnvListItem[]>({ url: envURL.listEnvUrl, data });
|
||||||
|
@ -24,8 +24,8 @@ export function importEnv(data: { request: EnvListItem; fileList: FileItem[] })
|
||||||
export function getEntryEnv(data: EnvListItem) {
|
export function getEntryEnv(data: EnvListItem) {
|
||||||
return MSR.post<EnvListItem>({ url: envURL.getEntryEnvUrl, data });
|
return MSR.post<EnvListItem>({ url: envURL.getEntryEnvUrl, data });
|
||||||
}
|
}
|
||||||
export function exportEnv(data: EnvListItem) {
|
export function exportEnv(id: string) {
|
||||||
return MSR.post<EnvListItem>({ url: envURL.exportEnvUrl, data });
|
return MSR.get<EnvListItem>({ url: envURL.exportEnvUrl + id, responseType: 'blob' }, { isTransformResponse: false });
|
||||||
}
|
}
|
||||||
export function editPosEnv(data: EnvListItem) {
|
export function editPosEnv(data: EnvListItem) {
|
||||||
return MSR.post<EnvListItem>({ url: envURL.editPosEnvUrl, data });
|
return MSR.post<EnvListItem>({ url: envURL.editPosEnvUrl, data });
|
||||||
|
@ -85,5 +85,8 @@ export function getGlobalParamDetail(id: string) {
|
||||||
}
|
}
|
||||||
/** 项目管理-环境-全局参数-导出 */
|
/** 项目管理-环境-全局参数-导出 */
|
||||||
export function exportGlobalParam(id: string) {
|
export function exportGlobalParam(id: string) {
|
||||||
return MSR.get<BlobPart>({ url: envURL.exportGlobalParamUrl + id });
|
return MSR.get<BlobPart>(
|
||||||
|
{ url: envURL.exportGlobalParamUrl + id, responseType: 'blob' },
|
||||||
|
{ isTransformResponse: false }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,3 +10,7 @@ export function getProjectList(organizationId: string) {
|
||||||
export function switchProject(data: { projectId: string; userId: string }) {
|
export function switchProject(data: { projectId: string; userId: string }) {
|
||||||
return MSR.post({ url: ProjectSwitchUrl, data });
|
return MSR.post({ url: ProjectSwitchUrl, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getProjectInfo(projectId: string) {
|
||||||
|
return MSR.get<ProjectListItem>({ url: `/project/get/${projectId}` });
|
||||||
|
}
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
export const ProjectListUrl = '/project/list/options'; // 项目列表
|
export const ProjectListUrl = '/project/list/options'; // 项目列表
|
||||||
export const ProjectSwitchUrl = '/project/switch'; // 切换项目
|
export const ProjectSwitchUrl = '/project/switch'; // 切换项目
|
||||||
|
export const projectModuleInfoUrl = '/project/get/'; // 获取项目模块信息
|
||||||
|
|
|
@ -0,0 +1,246 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex h-full w-full flex-col">
|
||||||
|
<a-radio-group v-model:model-value="condition.scriptType" size="small" class="mb-[16px]">
|
||||||
|
<a-radio value="manual">{{ t('apiTestDebug.manual') }}</a-radio>
|
||||||
|
<a-radio value="quote">{{ t('apiTestDebug.quote') }}</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
<div
|
||||||
|
v-if="scriptType === 'manual'"
|
||||||
|
class="relative h-full w-full rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[12px]"
|
||||||
|
>
|
||||||
|
<div v-if="isShowEditScriptNameInput" class="absolute left-[12px] z-10 w-[calc(100%-24px)]">
|
||||||
|
<a-input
|
||||||
|
ref="scriptNameInputRef"
|
||||||
|
v-model:model-value="condition.name"
|
||||||
|
:placeholder="t('apiTestDebug.preconditionScriptNamePlaceholder')"
|
||||||
|
:max-length="255"
|
||||||
|
show-word-limit
|
||||||
|
size="small"
|
||||||
|
@press-enter="isShowEditScriptNameInput = false"
|
||||||
|
@blur="isShowEditScriptNameInput = false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<a-tooltip :content="condition.name">
|
||||||
|
<div class="script-name-container">
|
||||||
|
<div class="one-line-text mr-[4px] max-w-[110px] font-medium text-[var(--color-text-1)]">
|
||||||
|
{{ condition.name }}
|
||||||
|
</div>
|
||||||
|
<MsIcon type="icon-icon_edit_outlined" class="edit-script-name-icon" @click="showEditScriptNameInput" />
|
||||||
|
</div>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-popover class="h-auto" position="top">
|
||||||
|
<div class="text-[rgb(var(--primary-5))]">{{ t('apiTestDebug.scriptEx') }}</div>
|
||||||
|
<template #content>
|
||||||
|
<div class="mb-[8px] flex items-center justify-between">
|
||||||
|
<div class="text-[14px] font-medium text-[var(--color-text-1)]">
|
||||||
|
{{ t('apiTestDebug.scriptEx') }}
|
||||||
|
</div>
|
||||||
|
<a-button
|
||||||
|
type="outline"
|
||||||
|
class="arco-btn-outline--secondary p-[0_8px]"
|
||||||
|
size="mini"
|
||||||
|
@click="copyScriptEx"
|
||||||
|
>
|
||||||
|
{{ t('common.copy') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<div class="flex h-[412px]">
|
||||||
|
<MsCodeEditor
|
||||||
|
v-model:model-value="scriptEx"
|
||||||
|
class="flex-1"
|
||||||
|
theme="MS-text"
|
||||||
|
width="500px"
|
||||||
|
height="388px"
|
||||||
|
:show-full-screen="false"
|
||||||
|
:show-theme-change="false"
|
||||||
|
read-only
|
||||||
|
>
|
||||||
|
</MsCodeEditor>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-[8px]">
|
||||||
|
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini">
|
||||||
|
<template #icon>
|
||||||
|
<MsIcon type="icon-icon_undo_outlined" class="text-var(--color-text-4)" size="12" />
|
||||||
|
</template>
|
||||||
|
{{ t('common.revoke') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini" @click="clearScript">
|
||||||
|
<template #icon>
|
||||||
|
<MsIcon type="icon-icon_clear" class="text-var(--color-text-4)" size="12" />
|
||||||
|
</template>
|
||||||
|
{{ t('common.clear') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini" @click="copyCondition">
|
||||||
|
{{ t('common.copy') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini" @click="deleteCondition">
|
||||||
|
{{ t('common.delete') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex h-[calc(100%-47px)] flex-col">
|
||||||
|
<div class="mb-[16px] flex w-full items-center bg-[var(--color-text-n9)] p-[12px]">
|
||||||
|
<div class="text-[var(--color-text-2)]">
|
||||||
|
{{ condition.quoteScript.name || '-' }}
|
||||||
|
</div>
|
||||||
|
<a-divider margin="8px" direction="vertical" />
|
||||||
|
<MsButton type="text" class="font-medium">
|
||||||
|
{{ t('apiTestDebug.quote') }}
|
||||||
|
</MsButton>
|
||||||
|
</div>
|
||||||
|
<a-radio-group v-model:model-value="commonScriptShowType" size="small" type="button" class="mb-[8px] w-fit">
|
||||||
|
<a-radio value="parameters">{{ t('apiTestDebug.parameters') }}</a-radio>
|
||||||
|
<a-radio value="scriptContent">{{ t('apiTestDebug.scriptContent') }}</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
<MsBaseTable v-show="commonScriptShowType === 'parameters'" v-bind="propsRes" v-on="propsEvent">
|
||||||
|
<template #value="{ record }">
|
||||||
|
<a-tooltip :content="t(record.required ? 'apiTestDebug.paramRequired' : 'apiTestDebug.paramNotRequired')">
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
record.required ? '!text-[rgb(var(--danger-5))]' : '!text-[var(--color-text-brand)]',
|
||||||
|
'!mr-[4px] !p-[4px]',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div>*</div>
|
||||||
|
</div>
|
||||||
|
</a-tooltip>
|
||||||
|
{{ record.type }}
|
||||||
|
</template>
|
||||||
|
</MsBaseTable>
|
||||||
|
<div v-show="commonScriptShowType === 'scriptContent'" class="h-[calc(100%-76px)]">
|
||||||
|
<MsCodeEditor
|
||||||
|
v-model:model-value="condition.quoteScript.script"
|
||||||
|
theme="MS-text"
|
||||||
|
height="100%"
|
||||||
|
:show-full-screen="false"
|
||||||
|
:show-theme-change="false"
|
||||||
|
read-only
|
||||||
|
>
|
||||||
|
</MsCodeEditor>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useClipboard } from '@vueuse/core';
|
||||||
|
import { InputInstance, Message } from '@arco-design/web-vue';
|
||||||
|
|
||||||
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
|
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||||
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
|
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||||
|
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||||
|
import useTable from '@/components/pure/ms-table/useTable';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const scriptType = defineModel('scriptType', {
|
||||||
|
type: String,
|
||||||
|
default: 'manual',
|
||||||
|
});
|
||||||
|
|
||||||
|
const condition = defineModel('modelValue', {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
scriptType: 'manual',
|
||||||
|
name: '',
|
||||||
|
script: '',
|
||||||
|
quoteScript: {
|
||||||
|
name: '',
|
||||||
|
script: '',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns: MsTableColumn = [
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.paramName',
|
||||||
|
dataIndex: 'name',
|
||||||
|
showTooltip: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.paramValue',
|
||||||
|
dataIndex: 'value',
|
||||||
|
slotName: 'value',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.desc',
|
||||||
|
dataIndex: 'desc',
|
||||||
|
showTooltip: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { propsRes, propsEvent } = useTable(() => Promise.resolve([]), {
|
||||||
|
scroll: { x: '100%' },
|
||||||
|
columns,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:data', data: Record<string, any>): void;
|
||||||
|
(e: 'copy'): void;
|
||||||
|
(e: 'delete', id: string): void;
|
||||||
|
(e: 'change'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// 是否显示脚本名称编辑框
|
||||||
|
const isShowEditScriptNameInput = ref(false);
|
||||||
|
const scriptNameInputRef = ref<InputInstance>();
|
||||||
|
|
||||||
|
function showEditScriptNameInput() {
|
||||||
|
isShowEditScriptNameInput.value = true;
|
||||||
|
nextTick(() => {
|
||||||
|
scriptNameInputRef.value?.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const scriptEx = ref(`2023-12-04 11:19:28 INFO 9026fd6a 1-1 Thread started: 9026fd6a 1-1
|
||||||
|
2023-12-04 11:19:28 ERROR 9026fd6a 1-1 Problem in JSR223 script JSR223Sampler, message: {}
|
||||||
|
In file: inline evaluation of: prev.getResponseCode() import java.net.URI; import org.apache.http.client.method . . . '' Encountered "import" at line 2, column 1.
|
||||||
|
in inline evaluation of: prev.getResponseCode() import java.net.URI; import org.apache.http.client.method . . . '' at line number 2
|
||||||
|
javax.script.ScriptException '' at line number 2
|
||||||
|
javax.script.ScriptException '' at line number 2
|
||||||
|
javax.script.ScriptException '' at line number 2
|
||||||
|
javax.script.ScriptException '' at line number 2
|
||||||
|
javax.script.ScriptException '' at line number 2
|
||||||
|
javax.script.ScriptException
|
||||||
|
org.apache.http.client.method . . . '' at line number 2
|
||||||
|
`);
|
||||||
|
const { copy, isSupported } = useClipboard();
|
||||||
|
const commonScriptShowType = ref<'parameters' | 'scriptContent'>('parameters');
|
||||||
|
function copyScriptEx() {
|
||||||
|
if (isSupported) {
|
||||||
|
copy(scriptEx.value);
|
||||||
|
Message.success(t('apiTestDebug.scriptExCopySuccess'));
|
||||||
|
} else {
|
||||||
|
Message.warning(t('apiTestDebug.copyNotSupport'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function clearScript() {
|
||||||
|
condition.value.script = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制条件
|
||||||
|
*/
|
||||||
|
function copyCondition() {
|
||||||
|
emit('copy');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除条件
|
||||||
|
*/
|
||||||
|
function deleteCondition() {
|
||||||
|
emit('delete', condition.value.id);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -63,6 +63,7 @@
|
||||||
<ResponseHeaderTab v-if="valueKey === 'responseHeader'" />
|
<ResponseHeaderTab v-if="valueKey === 'responseHeader'" />
|
||||||
<ResponseTimeTab v-if="valueKey === 'responseTime'" />
|
<ResponseTimeTab v-if="valueKey === 'responseTime'" />
|
||||||
<VariableTab v-if="valueKey === 'variable'" />
|
<VariableTab v-if="valueKey === 'variable'" />
|
||||||
|
<ScriptTab v-if="valueKey === 'script'" />
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -77,6 +78,7 @@
|
||||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||||
import ResponseHeaderTab from './comp/ResponseHeaderTab.vue';
|
import ResponseHeaderTab from './comp/ResponseHeaderTab.vue';
|
||||||
import ResponseTimeTab from './comp/ResponseTimeTab.vue';
|
import ResponseTimeTab from './comp/ResponseTimeTab.vue';
|
||||||
|
import ScriptTab from './comp/ScriptTab.vue';
|
||||||
import StatusCodeTab from './comp/StatusCodeTab.vue';
|
import StatusCodeTab from './comp/StatusCodeTab.vue';
|
||||||
import VariableTab from './comp/VariableTab.vue';
|
import VariableTab from './comp/VariableTab.vue';
|
||||||
|
|
||||||
|
|
|
@ -89,6 +89,7 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
menuTree.value?.forEach((el: RouteRecordRaw | null) => {
|
menuTree.value?.forEach((el: RouteRecordRaw | null) => {
|
||||||
if (isFind) return; // 节省性能
|
if (isFind) return; // 节省性能
|
||||||
backtrack(el, [el?.name as string]);
|
backtrack(el, [el?.name as string]);
|
||||||
|
@ -336,6 +337,7 @@
|
||||||
}
|
}
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
return travel(menuTree.value);
|
return travel(menuTree.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,9 @@
|
||||||
(item) => name && item?.name && (name as string).includes(item.name as string)
|
(item) => name && item?.name && (name as string).includes(item.name as string)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
appStore.setTopMenus(currentParent?.children?.filter((item) => item.meta?.isTopMenu));
|
appStore.setTopMenus(
|
||||||
|
currentParent?.children?.filter((item) => permission.accessRouter(item) && item.meta?.isTopMenu)
|
||||||
|
);
|
||||||
setCurrentTopMenu(name as string);
|
setCurrentTopMenu(name as string);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,17 @@
|
||||||
import { DirectiveBinding } from 'vue';
|
import { DirectiveBinding } from 'vue';
|
||||||
|
|
||||||
import { useUserStore } from '@/store';
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 权限指令,TODO:权限判定按权限点来
|
* 权限指令
|
||||||
* @param el dom 节点
|
* @param el dom 节点
|
||||||
* @param binding vue 绑定的数据
|
* @param binding vue 绑定的数据
|
||||||
*/
|
*/
|
||||||
function checkPermission(el: HTMLElement, binding: DirectiveBinding) {
|
function checkPermission(el: HTMLElement, binding: DirectiveBinding) {
|
||||||
const { value } = binding;
|
const { value } = binding;
|
||||||
const userStore = useUserStore();
|
|
||||||
const { role } = userStore;
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
if (value.length > 0) {
|
if (value.length > 0) {
|
||||||
const permissionValues = value;
|
const hasPermission = hasAnyPermission(value);
|
||||||
|
|
||||||
const hasPermission = permissionValues.includes(role);
|
|
||||||
if (!hasPermission && el.parentNode) {
|
if (!hasPermission && el.parentNode) {
|
||||||
el.parentNode.removeChild(el);
|
el.parentNode.removeChild(el);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ export enum CaseManagementRouteEnum {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PerformanceTestRouteEnum {
|
export enum PerformanceTestRouteEnum {
|
||||||
PERFORMANCE_TEST = 'performanceTest',
|
PERFORMANCE_TEST = 'loadTest',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectManagementRouteEnum {
|
export enum ProjectManagementRouteEnum {
|
||||||
|
@ -57,7 +57,7 @@ export enum UITestRouteEnum {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum WorkbenchRouteEnum {
|
export enum WorkbenchRouteEnum {
|
||||||
WORKBENCH = 'workbench',
|
WORKBENCH = 'workstation',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SettingRouteEnum {
|
export enum SettingRouteEnum {
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import { RouteLocationNormalized, RouteRecordRaw } from 'vue-router';
|
import { RouteLocationNormalized, RouteRecordRaw } from 'vue-router';
|
||||||
|
import { includes } from 'lodash-es';
|
||||||
|
|
||||||
import { useUserStore } from '@/store';
|
import { hasAnyPermission, hasFirstMenuPermission } from '@/utils/permission';
|
||||||
|
|
||||||
|
const firstLevelMenu = ['workstation', 'testPlan', 'bugManagement', 'caseManagement', 'apiTest', 'uiTest', 'loadTest'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户权限
|
* 用户权限
|
||||||
* @returns 调用方法
|
* @returns 调用方法
|
||||||
*/
|
*/
|
||||||
export default function usePermission() {
|
export default function usePermission() {
|
||||||
const userStore = useUserStore();
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* 是否为允许访问的路由
|
* 是否为允许访问的路由
|
||||||
|
@ -15,35 +17,17 @@ export default function usePermission() {
|
||||||
* @returns 是否
|
* @returns 是否
|
||||||
*/
|
*/
|
||||||
accessRouter(route: RouteLocationNormalized | RouteRecordRaw) {
|
accessRouter(route: RouteLocationNormalized | RouteRecordRaw) {
|
||||||
|
if (includes(firstLevelMenu, route.name)) {
|
||||||
|
// 一级菜单
|
||||||
|
return hasFirstMenuPermission(route.name as string);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
route.meta?.requiresAuth === false ||
|
route.meta?.requiresAuth === false ||
|
||||||
!route.meta?.roles ||
|
!route.meta?.roles ||
|
||||||
route.meta?.roles?.includes('*') ||
|
route.meta?.roles?.includes('*') ||
|
||||||
route.meta?.roles?.includes(userStore.role)
|
hasAnyPermission(route.meta?.roles || [])
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* 查找第一个允许访问的路由
|
|
||||||
* @param _routers 路由数组
|
|
||||||
* @param role 用户角色
|
|
||||||
* @returns 路由信息 or null
|
|
||||||
*/
|
|
||||||
findFirstPermissionRoute(_routers: any, role = 'admin') {
|
|
||||||
const cloneRouters = [..._routers];
|
|
||||||
while (cloneRouters.length) {
|
|
||||||
const firstElement = cloneRouters.shift();
|
|
||||||
if (
|
|
||||||
firstElement?.meta?.roles?.find((el: string[]) => {
|
|
||||||
return el.includes('*') || el.includes(role);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
return { name: firstElement.name };
|
|
||||||
if (firstElement?.children) {
|
|
||||||
cloneRouters.push(...firstElement.children);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
// You can add any rules you want
|
// You can add any rules you want
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,16 +38,16 @@ export interface EnvConfig {
|
||||||
dataSource?: DataSourceItem[];
|
dataSource?: DataSourceItem[];
|
||||||
hostConfig?: EnvConfigItem;
|
hostConfig?: EnvConfigItem;
|
||||||
authConfig?: EnvConfigItem;
|
authConfig?: EnvConfigItem;
|
||||||
preScript?: EnvConfigItem;
|
preScript?: EnvConfigItem[];
|
||||||
postScript?: EnvConfigItem;
|
postScript?: EnvConfigItem[];
|
||||||
assertions?: EnvConfigItem;
|
assertions?: EnvConfigItem[];
|
||||||
}
|
}
|
||||||
export interface EnvDetailItem {
|
export interface EnvDetailItem {
|
||||||
id?: string;
|
id?: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
name: string;
|
name: string;
|
||||||
config: EnvConfig;
|
config: EnvConfig;
|
||||||
mock?: string;
|
mock?: boolean;
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
export interface GlobalParamsItem {
|
export interface GlobalParamsItem {
|
||||||
|
|
|
@ -13,4 +13,5 @@ export interface ProjectListItem {
|
||||||
deleted: boolean;
|
deleted: boolean;
|
||||||
deleteUser: string;
|
deleteUser: string;
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
|
moduleIds: string[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { UserState } from '@/store/modules/user/types';
|
||||||
|
|
||||||
// 登录信息
|
// 登录信息
|
||||||
export interface LoginData {
|
export interface LoginData {
|
||||||
username: string;
|
username: string;
|
||||||
|
@ -5,37 +7,11 @@ export interface LoginData {
|
||||||
authenticate: string;
|
authenticate: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserRole {
|
|
||||||
id: string;
|
|
||||||
createTime: number;
|
|
||||||
createUser: string;
|
|
||||||
roleId: string;
|
|
||||||
sourceId: string;
|
|
||||||
userId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 登录返回
|
// 登录返回
|
||||||
export interface LoginRes {
|
export interface LoginRes extends UserState {
|
||||||
csrfToken: string;
|
csrfToken: string;
|
||||||
createTime: number;
|
|
||||||
createUser: string;
|
|
||||||
email: string;
|
|
||||||
enabled: boolean;
|
|
||||||
id: string;
|
|
||||||
language: string;
|
|
||||||
lastOrganizationId: string;
|
|
||||||
lastProjectId: string;
|
|
||||||
name: string;
|
|
||||||
phone: string;
|
|
||||||
platformInfo: string;
|
|
||||||
seleniumServer: string;
|
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
source: string;
|
token: string;
|
||||||
updateTime: number;
|
|
||||||
updateUser: string;
|
|
||||||
userRolePermissions: UserRole[];
|
|
||||||
userRoleRelations: UserRole[];
|
|
||||||
userRoles: UserRole[];
|
|
||||||
}
|
}
|
||||||
// 更新本地执行配置
|
// 更新本地执行配置
|
||||||
export interface UpdateLocalConfigParams {
|
export interface UpdateLocalConfigParams {
|
||||||
|
@ -94,23 +70,6 @@ export interface Resource {
|
||||||
name: string;
|
name: string;
|
||||||
license: boolean;
|
license: boolean;
|
||||||
}
|
}
|
||||||
export interface UserRolePermission {
|
|
||||||
resource: Resource;
|
|
||||||
permissions: Permission[];
|
|
||||||
type: string;
|
|
||||||
userRole: UserRole;
|
|
||||||
userRolePermissions: Permission[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserRoleRelation {
|
|
||||||
id: string;
|
|
||||||
userId: string;
|
|
||||||
roleId: string;
|
|
||||||
sourceId: string;
|
|
||||||
organizationId: string;
|
|
||||||
createTime: number;
|
|
||||||
createUser: string;
|
|
||||||
}
|
|
||||||
// 个人信息
|
// 个人信息
|
||||||
export interface PersonalOrganization {
|
export interface PersonalOrganization {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
@ -270,7 +270,7 @@ const ProjectManagement: AppRouteRecordRaw = {
|
||||||
component: () => import('@/views/project-management/environmental/index.vue'),
|
component: () => import('@/views/project-management/environmental/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
locale: 'menu.projectManagement.environmentManagement',
|
locale: 'menu.projectManagement.environmentManagement',
|
||||||
roles: ['*'],
|
roles: ['PROJECT_ENVIRONMENT:READ'],
|
||||||
isTopMenu: true,
|
isTopMenu: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -50,7 +50,7 @@ const Setting: AppRouteRecordRaw = {
|
||||||
component: () => import('@/views/setting/system/organizationAndProject/index.vue'),
|
component: () => import('@/views/setting/system/organizationAndProject/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
locale: 'menu.settings.system.organizationAndProject',
|
locale: 'menu.settings.system.organizationAndProject',
|
||||||
roles: ['*'],
|
roles: ['SYSTEM_ORGANIZATION_PROJECT:READ'],
|
||||||
isTopMenu: true,
|
isTopMenu: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import type { BreadcrumbItem } from '@/components/business/ms-breadcrumb/types';
|
import type { BreadcrumbItem } from '@/components/business/ms-breadcrumb/types';
|
||||||
|
|
||||||
|
import { getProjectInfo } from '@/api/modules/project-management/basicInfo';
|
||||||
import { getPageConfig } from '@/api/modules/setting/config';
|
import { getPageConfig } from '@/api/modules/setting/config';
|
||||||
import { getSystemVersion } from '@/api/modules/system';
|
import { getSystemVersion } from '@/api/modules/system';
|
||||||
import { getMenuList } from '@/api/modules/user';
|
import { getMenuList } from '@/api/modules/user';
|
||||||
|
@ -51,6 +52,7 @@ const useAppStore = defineStore('app', {
|
||||||
defaultLoginConfig,
|
defaultLoginConfig,
|
||||||
defaultPlatformConfig,
|
defaultPlatformConfig,
|
||||||
innerHeight: 0,
|
innerHeight: 0,
|
||||||
|
currentMenuConfig: [],
|
||||||
pageConfig: {
|
pageConfig: {
|
||||||
...defaultThemeConfig,
|
...defaultThemeConfig,
|
||||||
...defaultLoginConfig,
|
...defaultLoginConfig,
|
||||||
|
@ -273,6 +275,18 @@ const useAppStore = defineStore('app', {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置当前项目菜单配置
|
||||||
|
*/
|
||||||
|
async setCurrentMenuConfig() {
|
||||||
|
try {
|
||||||
|
const res = await getProjectInfo(this.currentProjectId);
|
||||||
|
this.currentMenuConfig = res.moduleIds;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
persist: {
|
persist: {
|
||||||
paths: ['currentOrgId', 'currentProjectId', 'pageConfig'],
|
paths: ['currentOrgId', 'currentProjectId', 'pageConfig'],
|
||||||
|
|
|
@ -2,6 +2,7 @@ import type { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||||
import type { BreadcrumbItem } from '@/components/business/ms-breadcrumb/types';
|
import type { BreadcrumbItem } from '@/components/business/ms-breadcrumb/types';
|
||||||
|
|
||||||
import type { LoginConfig, PageConfig, PlatformConfig, ThemeConfig } from '@/models/setting/config';
|
import type { LoginConfig, PageConfig, PlatformConfig, ThemeConfig } from '@/models/setting/config';
|
||||||
|
import { UserGroupAuthSetting } from '@/models/setting/usergroup';
|
||||||
|
|
||||||
import type { RouteRecordNormalized, RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordNormalized, RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
|
@ -35,6 +36,7 @@ export interface AppState {
|
||||||
defaultPlatformConfig: PlatformConfig;
|
defaultPlatformConfig: PlatformConfig;
|
||||||
pageConfig: PageConfig;
|
pageConfig: PageConfig;
|
||||||
innerHeight: number;
|
innerHeight: number;
|
||||||
|
currentMenuConfig: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UploadFileTaskState {
|
export interface UploadFileTaskState {
|
||||||
|
|
|
@ -12,7 +12,8 @@ const useProjectEnvStore = defineStore(
|
||||||
'projectEnv',
|
'projectEnv',
|
||||||
() => {
|
() => {
|
||||||
const currentId = ref<string>(ALL_PARAM); // 当前选中的key值
|
const currentId = ref<string>(ALL_PARAM); // 当前选中的key值
|
||||||
const currentEnvDetailInfo = ref<EnvDetailItem>(); // 当前选中的环境详情
|
const currentEnvDetailInfo = ref<EnvDetailItem>({ projectId: '', name: '', config: {} }); // 当前选中的环境详情
|
||||||
|
const backupEnvDetailInfo = ref<EnvDetailItem>({ projectId: '', name: '', config: {} }); // 当前选中的环境详情-备份
|
||||||
const allParamDetailInfo = ref<GlobalParams>(); // 全局参数详情
|
const allParamDetailInfo = ref<GlobalParams>(); // 全局参数详情
|
||||||
const httpNoWarning = ref(true);
|
const httpNoWarning = ref(true);
|
||||||
const getHttpNoWarning = computed(() => httpNoWarning.value);
|
const getHttpNoWarning = computed(() => httpNoWarning.value);
|
||||||
|
@ -25,10 +26,6 @@ const useProjectEnvStore = defineStore(
|
||||||
function setHttpNoWarning(noWarning: boolean) {
|
function setHttpNoWarning(noWarning: boolean) {
|
||||||
httpNoWarning.value = noWarning;
|
httpNoWarning.value = noWarning;
|
||||||
}
|
}
|
||||||
// 设置环境详情
|
|
||||||
function setEnvDetailInfo(item: EnvDetailItem) {
|
|
||||||
currentEnvDetailInfo.value = item;
|
|
||||||
}
|
|
||||||
// 设置全局参数
|
// 设置全局参数
|
||||||
function setAllParamDetailInfo(item: GlobalParams) {
|
function setAllParamDetailInfo(item: GlobalParams) {
|
||||||
allParamDetailInfo.value = item;
|
allParamDetailInfo.value = item;
|
||||||
|
@ -39,11 +36,14 @@ const useProjectEnvStore = defineStore(
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
try {
|
try {
|
||||||
if (id === NEW_ENV_PARAM) {
|
if (id === NEW_ENV_PARAM) {
|
||||||
currentEnvDetailInfo.value = undefined;
|
currentEnvDetailInfo.value = { projectId: appStore.currentProjectId, name: '', config: {} };
|
||||||
|
backupEnvDetailInfo.value = { projectId: appStore.currentProjectId, name: '', config: {} };
|
||||||
} else if (id === ALL_PARAM) {
|
} else if (id === ALL_PARAM) {
|
||||||
allParamDetailInfo.value = await getGlobalParamDetail(appStore.currentProjectId);
|
allParamDetailInfo.value = await getGlobalParamDetail(appStore.currentProjectId);
|
||||||
} else if (id !== ALL_PARAM && id) {
|
} else if (id !== ALL_PARAM && id) {
|
||||||
currentEnvDetailInfo.value = await getDetailEnv(id);
|
const tmpObj = await getDetailEnv(id);
|
||||||
|
currentEnvDetailInfo.value = tmpObj;
|
||||||
|
backupEnvDetailInfo.value = JSON.parse(JSON.stringify(tmpObj));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
@ -56,10 +56,11 @@ const useProjectEnvStore = defineStore(
|
||||||
getHttpNoWarning,
|
getHttpNoWarning,
|
||||||
httpNoWarning,
|
httpNoWarning,
|
||||||
allParamDetailInfo,
|
allParamDetailInfo,
|
||||||
|
currentEnvDetailInfo,
|
||||||
|
backupEnvDetailInfo,
|
||||||
groupLength,
|
groupLength,
|
||||||
setCurrentId,
|
setCurrentId,
|
||||||
setHttpNoWarning,
|
setHttpNoWarning,
|
||||||
setEnvDetailInfo,
|
|
||||||
setAllParamDetailInfo,
|
setAllParamDetailInfo,
|
||||||
initEnvDetail,
|
initEnvDetail,
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,13 +4,14 @@ import { isLogin as userIsLogin, login as userLogin, logout as userLogout } from
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { getHashParameters } from '@/utils';
|
import { getHashParameters } from '@/utils';
|
||||||
import { clearToken, setToken } from '@/utils/auth';
|
import { clearToken, setToken } from '@/utils/auth';
|
||||||
|
import { composePermissions } from '@/utils/permission';
|
||||||
import { removeRouteListener } from '@/utils/route-listener';
|
import { removeRouteListener } from '@/utils/route-listener';
|
||||||
|
|
||||||
import type { LoginData } from '@/models/user';
|
import type { LoginData } from '@/models/user';
|
||||||
|
|
||||||
import useAppStore from '../app';
|
import useAppStore from '../app';
|
||||||
import useLicenseStore from '../setting/license';
|
import useLicenseStore from '../setting/license';
|
||||||
import type { UserState } from './types';
|
import { UserState } from './types';
|
||||||
|
|
||||||
const useUserStore = defineStore('user', {
|
const useUserStore = defineStore('user', {
|
||||||
// 开启数据持久化
|
// 开启数据持久化
|
||||||
|
@ -32,12 +33,29 @@ const useUserStore = defineStore('user', {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
certification: undefined,
|
certification: undefined,
|
||||||
role: '',
|
role: '',
|
||||||
|
userRolePermissions: [],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
userInfo(state: UserState): UserState {
|
userInfo(state: UserState): UserState {
|
||||||
return { ...state };
|
return { ...state };
|
||||||
},
|
},
|
||||||
|
isAdmin(state: UserState): boolean {
|
||||||
|
if (!state.userRolePermissions) return false;
|
||||||
|
return state.userRolePermissions.findIndex((ur) => ur.userRole.id === 'admin') > -1;
|
||||||
|
},
|
||||||
|
currentRole(state: UserState): {
|
||||||
|
projectPermissions: string[];
|
||||||
|
orgPermissions: string[];
|
||||||
|
systemPermissions: string[];
|
||||||
|
} {
|
||||||
|
const appStore = useAppStore();
|
||||||
|
return {
|
||||||
|
projectPermissions: composePermissions(state.userRolePermissions || [], 'PROJECT', appStore.currentProjectId),
|
||||||
|
orgPermissions: composePermissions(state.userRolePermissions || [], 'ORGANIZATION', appStore.currentOrgId),
|
||||||
|
systemPermissions: composePermissions(state.userRolePermissions || [], 'SYSTEM', 'global'),
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -51,7 +69,6 @@ const useUserStore = defineStore('user', {
|
||||||
setInfo(partial: Partial<UserState>) {
|
setInfo(partial: Partial<UserState>) {
|
||||||
this.$patch(partial);
|
this.$patch(partial);
|
||||||
},
|
},
|
||||||
|
|
||||||
// 重置用户信息
|
// 重置用户信息
|
||||||
resetInfo() {
|
resetInfo() {
|
||||||
this.$reset();
|
this.$reset();
|
||||||
|
|
|
@ -1,4 +1,25 @@
|
||||||
export type RoleType = '' | '*' | 'admin' | 'user';
|
export type RoleType = '' | '*' | 'admin' | 'user';
|
||||||
|
export type SystemScopeType = 'PROJECT' | 'ORGANIZATION' | 'SYSTEM';
|
||||||
|
|
||||||
|
export interface UserRole {
|
||||||
|
createTime: number;
|
||||||
|
updateTime: number;
|
||||||
|
createUser: string;
|
||||||
|
description?: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
scopeId: string; // 项目/组织/系统 id
|
||||||
|
type: SystemScopeType;
|
||||||
|
}
|
||||||
|
export interface permissionsItem {
|
||||||
|
id: string;
|
||||||
|
permissionId: string;
|
||||||
|
roleId: string;
|
||||||
|
}
|
||||||
|
export interface UserRolePermissions {
|
||||||
|
userRole: UserRole;
|
||||||
|
userRolePermissions: permissionsItem[];
|
||||||
|
}
|
||||||
export interface UserState {
|
export interface UserState {
|
||||||
name?: string;
|
name?: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
|
@ -18,4 +39,5 @@ export interface UserState {
|
||||||
role: RoleType;
|
role: RoleType;
|
||||||
lastOrganizationId?: string;
|
lastOrganizationId?: string;
|
||||||
lastProjectId?: string;
|
lastProjectId?: string;
|
||||||
|
userRolePermissions?: UserRolePermissions[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { useAppStore, useUserStore } from '@/store';
|
||||||
|
import { SystemScopeType, UserRole, UserRolePermissions } from '@/store/modules/user/types';
|
||||||
|
|
||||||
|
export function hasPermission(permission: string, typeList: string[]) {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
if (userStore.isAdmin) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const { projectPermissions, orgPermissions, systemPermissions } = userStore.currentRole;
|
||||||
|
|
||||||
|
if (typeList.includes('PROJECT') && projectPermissions.includes(permission)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (typeList.includes('ORGANIZATION') && orgPermissions.includes(permission)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (typeList.includes('SYSTEM') && systemPermissions.includes(permission)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否有权限
|
||||||
|
export function hasAnyPermission(permissions: string[], typeList = ['PROJECT', 'ORGANIZATION', 'SYSTEM']) {
|
||||||
|
return permissions.some((permission) => hasPermission(permission, typeList));
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterProject(role: UserRole, id: string) {
|
||||||
|
return role && role.type === 'PROJECT' && role.scopeId === id;
|
||||||
|
}
|
||||||
|
function filterOrganization(role: UserRole, id: string) {
|
||||||
|
return role && role.type === 'ORGANIZATION' && role.scopeId === id;
|
||||||
|
}
|
||||||
|
function filterSystem(role: UserRole, id: string) {
|
||||||
|
return role && role.type === 'SYSTEM' && role.scopeId === id;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function composePermissions(userRolePermissions: UserRolePermissions[], type: SystemScopeType, id: string) {
|
||||||
|
let func: (role: UserRole, val: string) => boolean;
|
||||||
|
switch (type) {
|
||||||
|
case 'PROJECT':
|
||||||
|
func = filterProject;
|
||||||
|
break;
|
||||||
|
case 'ORGANIZATION':
|
||||||
|
func = filterOrganization;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
func = filterSystem;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return userRolePermissions
|
||||||
|
.filter((ur) => func(ur.userRole, id))
|
||||||
|
.flatMap((role) => role.userRolePermissions)
|
||||||
|
.map((g) => g.permissionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断当前一级菜单是否有权限
|
||||||
|
export function hasFirstMenuPermission(menuName: string) {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
if (userStore.isAdmin || menuName === 'setting' || menuName === 'projectManagement') {
|
||||||
|
// 如果是超级管理员,或者是系统设置菜单,或者是项目菜单,都有权限
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const { currentMenuConfig } = appStore;
|
||||||
|
return currentMenuConfig.includes(menuName);
|
||||||
|
}
|
|
@ -15,6 +15,7 @@
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
class="w-[732px]"
|
class="w-[732px]"
|
||||||
:placeholder="t('project.environmental.envNamePlaceholder')"
|
:placeholder="t('project.environmental.envNamePlaceholder')"
|
||||||
|
@blur="store.currentEnvDetailInfo.name = form.name"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
@ -32,20 +33,21 @@
|
||||||
<PreTab v-else-if="activeKey === 'pre'" />
|
<PreTab v-else-if="activeKey === 'pre'" />
|
||||||
<PostTab v-else-if="activeKey === 'post'" />
|
<PostTab v-else-if="activeKey === 'post'" />
|
||||||
<AssertTab v-else-if="activeKey === 'assert'" />
|
<AssertTab v-else-if="activeKey === 'assert'" />
|
||||||
<DisplayTab v-else-if="activeKey === 'display'" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer" :style="{ width: '100%' }">
|
<div class="footer" :style="{ width: '100%' }">
|
||||||
<a-button :disabled="!canSave" @click="handleReset">{{ t('common.cancel') }}</a-button>
|
<a-button @click="handleReset">{{ t('common.cancel') }}</a-button>
|
||||||
<a-button :disabled="!canSave" type="primary" @click="handleSave">{{ t('common.save') }}</a-button>
|
<a-button :disabled="!canSave" type="primary" @click="handleSave">{{ t('common.save') }}</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
import AssertTab from './envParams/AssertTab.vue';
|
import AssertTab from './envParams/AssertTab.vue';
|
||||||
import DataBaseTab from './envParams/DatabaseTab.vue';
|
import DataBaseTab from './envParams/DatabaseTab.vue';
|
||||||
import DisplayTab from './envParams/DisplayTab.vue';
|
|
||||||
import EnvParamsTab from './envParams/EnvParamsTab.vue';
|
import EnvParamsTab from './envParams/EnvParamsTab.vue';
|
||||||
import HostTab from './envParams/HostTab.vue';
|
import HostTab from './envParams/HostTab.vue';
|
||||||
import HttpTab from './envParams/HttpTab.vue';
|
import HttpTab from './envParams/HttpTab.vue';
|
||||||
|
@ -53,17 +55,17 @@
|
||||||
import PreTab from './envParams/PreTab.vue';
|
import PreTab from './envParams/PreTab.vue';
|
||||||
import TcpTab from './envParams/TcpTab.vue';
|
import TcpTab from './envParams/TcpTab.vue';
|
||||||
|
|
||||||
|
import { updateOrAddEnv } from '@/api/modules/project-management/envManagement';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { useAppStore } from '@/store';
|
import useProjectEnvStore from '@/store/modules/setting/useProjectEnvStore';
|
||||||
import useProjectEnvStore, { NEW_ENV_PARAM } from '@/store/modules/setting/useProjectEnvStore';
|
|
||||||
|
|
||||||
const activeKey = ref('assert');
|
const activeKey = ref('assert');
|
||||||
const envForm = ref();
|
const envForm = ref();
|
||||||
const canSave = ref(false);
|
const canSave = ref(false);
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
const store = useProjectEnvStore();
|
const store = useProjectEnvStore();
|
||||||
const appStore = useAppStore();
|
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
|
@ -109,26 +111,42 @@
|
||||||
];
|
];
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
envForm.value?.resetFields();
|
envForm.value?.resetFields();
|
||||||
|
store.initEnvDetail();
|
||||||
};
|
};
|
||||||
const handleSave = () => {
|
const handleSave = async () => {
|
||||||
envForm.value?.validate(async (valid) => {
|
await envForm.value?.validate(async (valid) => {
|
||||||
if (valid) {
|
if (!valid) {
|
||||||
console.log('form', form);
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
store.currentEnvDetailInfo.mock = true;
|
||||||
|
const res = await updateOrAddEnv(store.currentEnvDetailInfo);
|
||||||
|
store.currentEnvDetailInfo = res;
|
||||||
|
Message.success(t('common.saveSuccess'));
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const initData = async () => {
|
|
||||||
await store.initEnvDetail();
|
|
||||||
};
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (store.currentId === NEW_ENV_PARAM) {
|
if (store.currentId) {
|
||||||
store.setEnvDetailInfo({
|
store.initEnvDetail();
|
||||||
name: '',
|
}
|
||||||
projectId: appStore.currentProjectId,
|
});
|
||||||
config: {},
|
|
||||||
});
|
watchEffect(() => {
|
||||||
} else {
|
if (store.currentEnvDetailInfo) {
|
||||||
initData();
|
const { currentEnvDetailInfo } = store;
|
||||||
|
form.name = currentEnvDetailInfo.name;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
watchEffect(() => {
|
||||||
|
if (store.currentEnvDetailInfo) {
|
||||||
|
const { currentEnvDetailInfo, backupEnvDetailInfo } = store;
|
||||||
|
canSave.value = !isEqual(currentEnvDetailInfo, backupEnvDetailInfo);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<MsAssertion v-model:params="params" />
|
||||||
<ms-assertion />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import MsAssertion from '@/components/business/ms-assertion/index.vue';
|
import MsAssertion from '@/components/business/ms-assertion/index.vue';
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import useProjectEnvStore from '@/store/modules/setting/useProjectEnvStore';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const store = useProjectEnvStore();
|
||||||
|
|
||||||
|
const params = computed({
|
||||||
|
set: (value: any) => {
|
||||||
|
store.currentEnvDetailInfo.config.assertions = value;
|
||||||
|
},
|
||||||
|
get: () => store.currentEnvDetailInfo.config.assertions || [],
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped></style>
|
||||||
|
|
|
@ -49,9 +49,14 @@
|
||||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const store = useProjectEnvStore();
|
const store = useProjectEnvStore();
|
||||||
|
|
||||||
|
const innerParam = computed({
|
||||||
|
get: () => (store.currentEnvDetailInfo.config.dataSource || []) as DataSourceItem[],
|
||||||
|
set: (value: DataSourceItem[] | undefined) => {
|
||||||
|
store.currentEnvDetailInfo.config.dataSource = value;
|
||||||
|
},
|
||||||
|
});
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
const tableStore = useTableStore();
|
const tableStore = useTableStore();
|
||||||
const addVisible = ref(false);
|
const addVisible = ref(false);
|
||||||
|
@ -156,21 +161,8 @@
|
||||||
addVisible.value = true;
|
addVisible.value = true;
|
||||||
};
|
};
|
||||||
const fetchData = () => {};
|
const fetchData = () => {};
|
||||||
const handleNoWarning = () => {
|
|
||||||
store.setHttpNoWarning(false);
|
|
||||||
};
|
|
||||||
const initData = () => {
|
const initData = () => {
|
||||||
propsRes.value.data = [
|
propsRes.value.data = innerParam.value;
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
name: 'test',
|
|
||||||
desc: 'test',
|
|
||||||
url: 'test',
|
|
||||||
username: 'test',
|
|
||||||
poolMax: 'test',
|
|
||||||
timeout: 'test',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initData();
|
initData();
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="p-[24px]">
|
|
||||||
<a-divider :margin="0" class="!mb-[16px]" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
|
|
@ -1,13 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<AllPrams v-model:params="AllParams" :table-key="TableKeyEnum.PROJECT_MANAGEMENT_ENV_ENV_PARAM" />
|
<AllPrams v-model:params="allParams" :table-key="TableKeyEnum.PROJECT_MANAGEMENT_ENV_ENV_PARAM" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import AllPrams from '../allParams/index.vue';
|
import AllPrams from '../allParams/index.vue';
|
||||||
|
|
||||||
|
import useProjectEnvStore from '@/store/modules/setting/useProjectEnvStore';
|
||||||
|
|
||||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||||
|
|
||||||
const AllParams = ref<[]>([]);
|
const store = useProjectEnvStore();
|
||||||
|
|
||||||
|
const allParams = computed({
|
||||||
|
set: (value: any) => {
|
||||||
|
store.currentEnvDetailInfo.config.commmonVariables = value;
|
||||||
|
},
|
||||||
|
get: () => store.currentEnvDetailInfo.config.commmonVariables || [],
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped></style>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="p-[24px]">
|
<div>
|
||||||
<div class="flex flex-row items-center gap-[8px]">
|
<div class="flex flex-row items-center gap-[8px]">
|
||||||
<a-switch v-model:model-value="configSwitch" size="small" />
|
<a-switch type="line" size="small" />
|
||||||
<div class="text-[var(--color-text-1)]">{{ t('project.environmental.host.config') }}</div>
|
<div class="text-[var(--color-text-1)]">{{ t('project.environmental.host.config') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-[8px]">
|
<div class="mt-[8px]">
|
||||||
|
@ -19,18 +19,31 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { onClickOutside } from '@vueuse/core';
|
||||||
|
|
||||||
import MsBatchForm from '@/components/business/ms-batch-form/index.vue';
|
import MsBatchForm from '@/components/business/ms-batch-form/index.vue';
|
||||||
import { FormItemModel } from '@/components/business/ms-batch-form/types';
|
import { FormItemModel } from '@/components/business/ms-batch-form/types';
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import useProjectEnvStore from '@/store/modules/setting/useProjectEnvStore';
|
||||||
|
|
||||||
|
import { EnvConfigItem } from '@/models/projectManagement/environmental';
|
||||||
import { FakeTableListItem } from '@/models/projectManagement/menuManagement';
|
import { FakeTableListItem } from '@/models/projectManagement/menuManagement';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const currentList = ref<FakeTableListItem[]>([]);
|
const store = useProjectEnvStore();
|
||||||
|
|
||||||
const configSwitch = ref<boolean>(false);
|
const currentList = computed({
|
||||||
|
get: () => (store.currentEnvDetailInfo.config.hostConfig || []) as FakeTableListItem[],
|
||||||
|
set: (value: EnvConfigItem[] | undefined) => {
|
||||||
|
store.currentEnvDetailInfo.config.hostConfig = value;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const batchFormRef = ref();
|
||||||
|
onClickOutside(batchFormRef, () => {
|
||||||
|
currentList.value = batchFormRef.value?.getFormResult();
|
||||||
|
});
|
||||||
type UserModalMode = 'create' | 'edit';
|
type UserModalMode = 'create' | 'edit';
|
||||||
const batchFormModels: Ref<FormItemModel[]> = ref([
|
const batchFormModels: Ref<FormItemModel[]> = ref([
|
||||||
{
|
{
|
||||||
|
|
|
@ -68,6 +68,10 @@
|
||||||
const tableStore = useTableStore();
|
const tableStore = useTableStore();
|
||||||
const addVisible = ref(false);
|
const addVisible = ref(false);
|
||||||
const currentId = ref('');
|
const currentId = ref('');
|
||||||
|
const innerParam = defineModel<TableData[]>('modelValue', {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
});
|
||||||
|
|
||||||
const columns: MsTableColumn = [
|
const columns: MsTableColumn = [
|
||||||
{
|
{
|
||||||
|
@ -160,15 +164,7 @@
|
||||||
store.setHttpNoWarning(false);
|
store.setHttpNoWarning(false);
|
||||||
};
|
};
|
||||||
const initData = () => {
|
const initData = () => {
|
||||||
propsRes.value.data = [
|
propsRes.value.data = innerParam.value;
|
||||||
{
|
|
||||||
host: 'http://www.baidu.com',
|
|
||||||
desc: '百度',
|
|
||||||
applyScope: '全部',
|
|
||||||
enableScope: '全部',
|
|
||||||
value: 'http://www.baidu.com',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initData();
|
initData();
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="p-[24px]">
|
<PostTab v-model:params="params" layout="horizontal" />
|
||||||
<a-divider :margin="0" class="!mb-[16px]" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import PostTab from '@/views/api-test/debug/components/debug/postcondition.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
import useProjectEnvStore from '@/store/modules/setting/useProjectEnvStore';
|
||||||
|
|
||||||
|
const store = useProjectEnvStore();
|
||||||
|
|
||||||
|
const params = computed({
|
||||||
|
set: (value: any) => {
|
||||||
|
store.currentEnvDetailInfo.config.postScript = value;
|
||||||
|
},
|
||||||
|
get: () => store.currentEnvDetailInfo.config.postScript || [],
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped></style>
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="p-[24px]">
|
<PreTab v-model:params="params" layout="horizontal" />
|
||||||
<a-divider :margin="0" class="!mb-[16px]" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import PreTab from '@/views/api-test/debug/components/debug/precondition.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
import useProjectEnvStore from '@/store/modules/setting/useProjectEnvStore';
|
||||||
|
|
||||||
|
const store = useProjectEnvStore();
|
||||||
|
|
||||||
|
const params = computed({
|
||||||
|
set: (value: any) => {
|
||||||
|
store.currentEnvDetailInfo.config.preScript = value;
|
||||||
|
},
|
||||||
|
get: () => store.currentEnvDetailInfo.config.preScript || [],
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped></style>
|
||||||
|
|
|
@ -20,17 +20,33 @@
|
||||||
</template>
|
</template>
|
||||||
<template #operation="{ record }">
|
<template #operation="{ record }">
|
||||||
<template v-if="record.deleted">
|
<template v-if="record.deleted">
|
||||||
<MsButton @click="handleRevokeDelete(record)">{{ t('common.revokeDelete') }}</MsButton>
|
<MsButton v-permission="['SYSTEM_ORGANIZATION_PROJECT:READ+RECOVER']" @click="handleRevokeDelete(record)">{{
|
||||||
|
t('common.revokeDelete')
|
||||||
|
}}</MsButton>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="!record.enable">
|
<template v-else-if="!record.enable">
|
||||||
<MsButton @click="handleEnableOrDisableOrg(record)">{{ t('common.enable') }}</MsButton>
|
<MsButton v-permission="['SYSTEM_ORGANIZATIN_PROJECT:READ+UPDATE']" @click="handleEnableOrDisableOrg(record)">{{
|
||||||
<MsButton @click="handleDelete(record)">{{ t('common.delete') }}</MsButton>
|
t('common.enable')
|
||||||
|
}}</MsButton>
|
||||||
|
<MsButton v-permission="['SYSTEM_ORGANIZATIN_PROJECT:READ+DELETE']" @click="handleDelete(record)">{{
|
||||||
|
t('common.delete')
|
||||||
|
}}</MsButton>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<MsButton @click="showOrganizationModal(record)">{{ t('common.edit') }}</MsButton>
|
<MsButton v-permission="['SYSTEM_ORGANIZATIN_PROJECT:READ+UPDATE']" @click="showOrganizationModal(record)">{{
|
||||||
<MsButton @click="showAddUserModal(record)">{{ t('system.organization.addMember') }}</MsButton>
|
t('common.edit')
|
||||||
<MsButton @click="handleEnableOrDisableOrg(record, false)">{{ t('common.end') }}</MsButton>
|
}}</MsButton>
|
||||||
<MsTableMoreAction :list="tableActions" @select="handleMoreAction($event, record)"></MsTableMoreAction>
|
<MsButton v-permission="['SYSTEM_ORGANIZATIN_PROJECT:READ']" @click="showAddUserModal(record)">{{
|
||||||
|
t('system.organization.addMember')
|
||||||
|
}}</MsButton>
|
||||||
|
<MsButton v-permission="['SYSTEM_ORGANIZATIN_PROJECT:READ']" @click="handleEnableOrDisableOrg(record, false)">{{
|
||||||
|
t('common.end')
|
||||||
|
}}</MsButton>
|
||||||
|
<MsTableMoreAction
|
||||||
|
v-permission="['SYSTEM_ORGANIZATIN_PROJECT:READ+DELETE']"
|
||||||
|
:list="tableActions"
|
||||||
|
@select="handleMoreAction($event, record)"
|
||||||
|
></MsTableMoreAction>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</MsBaseTable>
|
</MsBaseTable>
|
||||||
|
|
|
@ -17,17 +17,37 @@
|
||||||
</template>
|
</template>
|
||||||
<template #operation="{ record }">
|
<template #operation="{ record }">
|
||||||
<template v-if="record.deleted">
|
<template v-if="record.deleted">
|
||||||
<MsButton @click="handleRevokeDelete(record)">{{ t('common.revokeDelete') }}</MsButton>
|
<MsButton v-permission="['SYSTEM_ORGANIZATION_PROJECT:READ+RECOVER']" @click="handleRevokeDelete(record)">{{
|
||||||
|
t('common.revokeDelete')
|
||||||
|
}}</MsButton>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="!record.enable">
|
<template v-else-if="!record.enable">
|
||||||
<MsButton @click="handleEnableOrDisableProject(record)">{{ t('common.enable') }}</MsButton>
|
<MsButton
|
||||||
<MsButton @click="handleDelete(record)">{{ t('common.delete') }}</MsButton>
|
v-permission="['SYSTEM_ORGANIZATIN_PROJECT:READ+UPDATE']"
|
||||||
|
@click="handleEnableOrDisableProject(record)"
|
||||||
|
>{{ t('common.enable') }}</MsButton
|
||||||
|
>
|
||||||
|
<MsButton v-permission="['SYSTEM_ORGANIZATIN_PROJECT:READ+DELETE']" @click="handleDelete(record)">{{
|
||||||
|
t('common.delete')
|
||||||
|
}}</MsButton>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<MsButton @click="showAddProjectModal(record)">{{ t('common.edit') }}</MsButton>
|
<MsButton v-permission="['SYSTEM_ORGANIZATIN_PROJECT:READ+UPDATE']" @click="showAddProjectModal(record)">{{
|
||||||
<MsButton @click="showAddUserModal(record)">{{ t('system.organization.addMember') }}</MsButton>
|
t('common.edit')
|
||||||
<MsButton @click="handleEnableOrDisableProject(record, false)">{{ t('common.end') }}</MsButton>
|
}}</MsButton>
|
||||||
<MsTableMoreAction :list="tableActions" @select="handleMoreAction($event, record)"></MsTableMoreAction>
|
<MsButton v-permission="['SYSTEM_ORGANIZATIN_PROJECT:READ']" @click="showAddUserModal(record)">{{
|
||||||
|
t('system.organization.addMember')
|
||||||
|
}}</MsButton>
|
||||||
|
<MsButton
|
||||||
|
v-permission="['SYSTEM_ORGANIZATIN_PROJECT:READ+UPDATE']"
|
||||||
|
@click="handleEnableOrDisableProject(record, false)"
|
||||||
|
>{{ t('common.end') }}</MsButton
|
||||||
|
>
|
||||||
|
<MsTableMoreAction
|
||||||
|
v-permission="['SYSTEM_ORGANIZATIN_PROJECT:READ+DELETE']"
|
||||||
|
:list="tableActions"
|
||||||
|
@select="handleMoreAction($event, record)"
|
||||||
|
></MsTableMoreAction>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</MsBaseTable>
|
</MsBaseTable>
|
||||||
|
|
Loading…
Reference in New Issue