feat(系统日志): 单词拼写调整&系统日志页面&部分组件调整&MS 级联选择组件
This commit is contained in:
parent
914397be18
commit
9b4462e948
|
@ -531,7 +531,7 @@ export default i18n;
|
|||
|
||||
## -theme 主题配置
|
||||
|
||||
1. 去 Desing Lab 创建主题 https://arco.design/themes/home
|
||||
1. 去 Design Lab 创建主题 https://arco.design/themes/home
|
||||
2. 以`ms-theme-` 命名为开头
|
||||
3. 点击页面的配置主题
|
||||
**“CSS 变量” + “Tailwind 配置变量” + “基于 css 变量自行计算混合色覆盖 arco-theme 变量”**
|
||||
|
@ -777,8 +777,11 @@ export default mergeConfig(
|
|||
})
|
||||
);
|
||||
```
|
||||
|
||||
## 本地生产环境调试
|
||||
|
||||
需安装 docker: https://www.docker.com/, 选择对应系统版本安装。
|
||||
|
||||
```bash
|
||||
cd frontend/
|
||||
pnpm run build:local
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
import MSR from '@/api/http/index';
|
||||
import { GetLogListUrl, GetLogOptionstUrl } from '@/api/requrls/setting/log';
|
||||
|
||||
import type { LogOptions } from '@/models/setting/log';
|
||||
|
||||
// 获取日志列表
|
||||
export function getLogList(data: any) {
|
||||
return MSR.post({ url: GetLogListUrl, data });
|
||||
}
|
||||
|
||||
// 获取日志操作范围选项
|
||||
export function getLogOptions() {
|
||||
// return MSR.get<LogOptions>({ url: GetLogOptionstUrl });
|
||||
return {
|
||||
organizationList: [
|
||||
{
|
||||
id: 'bfa4feec-276f-11ee-bc36-0242ac1e0a05',
|
||||
name: '默认组织默认组织默认组织默认组织默认组织默认组织默认组织默认组织默认组织默认组织',
|
||||
},
|
||||
{
|
||||
id: 'e21d5270-369e-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试组织2',
|
||||
},
|
||||
{
|
||||
id: 'e2433740-369e-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试组织3',
|
||||
},
|
||||
{
|
||||
id: 'e2817131-369e-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试组织4',
|
||||
},
|
||||
{
|
||||
id: 'e28950e3-369e-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试组织5',
|
||||
},
|
||||
{
|
||||
id: 'e2c03258-369e-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试组织6',
|
||||
},
|
||||
{
|
||||
id: 'e2f08005-369e-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试组织7',
|
||||
},
|
||||
{
|
||||
id: 'e30650b8-369e-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试组织8',
|
||||
},
|
||||
{
|
||||
id: 'e3487cf7-369e-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试组织9',
|
||||
},
|
||||
{
|
||||
id: 'e363b914-369e-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试组织10',
|
||||
},
|
||||
{
|
||||
id: 'e36bd98b-369e-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试组织11',
|
||||
},
|
||||
],
|
||||
projectList: [
|
||||
{
|
||||
id: 'ab11c4a1-369f-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试2项目测试2项目测试2项目测试2项目测试2项目测试2项目测试2项目测试2项目测试2项目测试2项目测试2项目测试2项目',
|
||||
},
|
||||
{
|
||||
id: 'ab2cf284-369f-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试3项目',
|
||||
},
|
||||
{
|
||||
id: 'ab6ffa01-369f-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试4项目',
|
||||
},
|
||||
{
|
||||
id: 'ab845cd2-369f-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试5项目',
|
||||
},
|
||||
{
|
||||
id: 'ab8aef35-369f-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试6项目',
|
||||
},
|
||||
{
|
||||
id: 'ab91ff63-369f-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试7项目',
|
||||
},
|
||||
{
|
||||
id: 'abc542d5-369f-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试8项目',
|
||||
},
|
||||
{
|
||||
id: 'abcc2352-369f-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试9项目',
|
||||
},
|
||||
{
|
||||
id: 'abeeb108-369f-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试10项目',
|
||||
},
|
||||
{
|
||||
id: 'ac4e4fc0-369f-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试11项目',
|
||||
},
|
||||
{
|
||||
id: 'acb0831f-369f-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试12项目',
|
||||
},
|
||||
{
|
||||
id: 'ad5a7bc0-369f-11ee-93aa-0242ac1e0a02',
|
||||
name: '测试13项目',
|
||||
},
|
||||
{
|
||||
id: 'bfa52f87-276f-11ee-bc36-0242ac1e0a05',
|
||||
name: '默认项目',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
|
@ -14,7 +14,7 @@ import type {
|
|||
UserListItem,
|
||||
CreateUserParams,
|
||||
UpdateUserInfoParams,
|
||||
UpdateUserStausParams,
|
||||
UpdateUserStatusParams,
|
||||
DeleteUserParams,
|
||||
ImportUserParams,
|
||||
SystemRole,
|
||||
|
@ -39,7 +39,7 @@ export function updateUserInfo(data: UpdateUserInfoParams) {
|
|||
}
|
||||
|
||||
// 更新用户启用/禁用状态
|
||||
export function toggleUserStatus(data: UpdateUserStausParams) {
|
||||
export function toggleUserStatus(data: UpdateUserStatusParams) {
|
||||
return MSR.post({ url: EnableUserUrl, data });
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export const GetLogListUrl = '/operation/log/list';
|
||||
export const GetLogOptionstUrl = '/operation/log/get/options';
|
|
@ -613,3 +613,10 @@
|
|||
.arco-collapse-item-header-title {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/** 标签 **/
|
||||
.arco-tag {
|
||||
.arco-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,3 +159,27 @@
|
|||
color: rgb(var(--primary-3)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/** 滚动条 **/
|
||||
.ms-scroll-bar() {
|
||||
&::-webkit-scrollbar {
|
||||
@apply bg-transparent;
|
||||
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@apply hidden;
|
||||
|
||||
border-radius: var(--border-radius-medium);
|
||||
background-color: var(--color-text-input-border);
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
&:hover {
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@apply block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -131,7 +131,7 @@
|
|||
msg?: string
|
||||
) {
|
||||
if (value === '' || value === undefined) return;
|
||||
// 遍历其他同 feild 名的输入框的值,检查是否与当前输入框的值重复
|
||||
// 遍历其他同 field 名的输入框的值,检查是否与当前输入框的值重复
|
||||
for (let i = 0; i < form.value.list.length; i++) {
|
||||
if (i !== index && form.value.list[i][field].trim() === value) {
|
||||
callback(t(msg || ''));
|
||||
|
@ -178,9 +178,14 @@
|
|||
form.value.list.splice(i, 1);
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
formRef.value?.resetFields();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
formValidate,
|
||||
getFormResult,
|
||||
resetForm,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
const target = ref<string[]>([]);
|
||||
const treeList = ref<TreeDataItem[]>([]);
|
||||
|
||||
function handelTableBatch(action: string) {
|
||||
function handleTableBatch(action: string) {
|
||||
switch (action) {
|
||||
case 'batchAddProject':
|
||||
batchTitle.value = t('system.user.batchAddProject');
|
||||
|
@ -100,7 +100,7 @@
|
|||
() => props.visible,
|
||||
(val) => {
|
||||
if (val) {
|
||||
handelTableBatch(props.action);
|
||||
handleTableBatch(props.action);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -0,0 +1,234 @@
|
|||
<template>
|
||||
<a-cascader
|
||||
v-if="props.mode === 'MS'"
|
||||
ref="cascader"
|
||||
v-model="innerValue"
|
||||
class="ms-cascader"
|
||||
:options="props.options"
|
||||
:trigger-props="{ contentClass: 'ms-cascader-popper' }"
|
||||
multiple
|
||||
allow-clear
|
||||
check-strictly
|
||||
:max-tag-count="maxTagCount"
|
||||
:virtual-list-props="props.virtualListProps"
|
||||
:placeholder="props.placeholder"
|
||||
@change="handleMsCascaderChange"
|
||||
>
|
||||
<template #prefix>
|
||||
{{ props.prefix }}
|
||||
</template>
|
||||
<template #label="{ data }">
|
||||
<a-tooltip :content="data.label" position="top" :mouse-enter-delay="500" mini>
|
||||
<div class="one-line-text inline-block">{{ data.label }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #option="{ data }">
|
||||
<a-tooltip :content="data.label" position="top" :mouse-enter-delay="500" mini>
|
||||
<a-radio
|
||||
v-if="data.level === 0"
|
||||
v-model:model-value="level"
|
||||
:value="data.value.value"
|
||||
size="mini"
|
||||
@change="handleLevelChange"
|
||||
>
|
||||
<div class="one-line-text" :style="getOptionComputedStyle">{{ data.label }}</div>
|
||||
</a-radio>
|
||||
<div v-else class="one-line-text" :style="getOptionComputedStyle">{{ data.label }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-cascader>
|
||||
<a-cascader
|
||||
v-else
|
||||
ref="cascader"
|
||||
v-model="innerValue"
|
||||
class="ms-cascader"
|
||||
:options="props.options"
|
||||
:trigger-props="{ contentClass: 'ms-cascader-popper' }"
|
||||
:multiple="props.multiple"
|
||||
allow-clear
|
||||
:check-strictly="props.strictly"
|
||||
:max-tag-count="maxTagCount"
|
||||
:placeholder="props.placeholder"
|
||||
:virtual-list-props="props.virtualListProps"
|
||||
>
|
||||
<template #prefix>
|
||||
{{ props.prefix }}
|
||||
</template>
|
||||
<template #label="{ data }">
|
||||
<a-tooltip :content="data.label" position="top" :mouse-enter-delay="500" mini>
|
||||
<div class="one-line-text inline translate-y-[15%]">{{ data.label }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #option="{ data }">
|
||||
<a-tooltip :content="data.label" position="top" :mouse-enter-delay="500" mini>
|
||||
<div class="one-line-text" :style="getOptionComputedStyle">{{ data.label }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-cascader>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, Ref, nextTick, onMounted, computed, onBeforeUnmount } from 'vue';
|
||||
import { calculateMaxDepth } from '@/utils';
|
||||
|
||||
import type { CascaderOption } from '@arco-design/web-vue';
|
||||
import type { VirtualListProps } from '@arco-design/web-vue/es/_components/virtual-list-v2/interface';
|
||||
|
||||
export type CascaderModelValue = string | number | Record<string, any> | (string | number | Record<string, any>)[];
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: CascaderModelValue;
|
||||
options: CascaderOption[];
|
||||
mode?: 'MS' | 'native'; // MS的多选、原生;这里的多选指的是出了一级以外的多选,一级是顶级分类选项只能单选。原生模式使用 arco-design 的 cascader 组件,只加了getOptionComputedStyle
|
||||
prefix?: string; // 输入框前缀
|
||||
levelTop?: string[]; // 顶级选项,多选时则必传
|
||||
multiple?: boolean; // 是否多选
|
||||
strictly?: boolean; // 是否严格模式
|
||||
virtualListProps?: VirtualListProps; // 传入开启虚拟滚动
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
mode: 'MS',
|
||||
}
|
||||
);
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const innerValue = ref<CascaderModelValue>([]);
|
||||
const level = ref(''); // 顶级选项,该级别为单选选项
|
||||
const maxTagCount = ref(1); // 最大显示 tag 数量
|
||||
const cascader: Ref = ref(null);
|
||||
const cascaderWidth = ref(0); // cascader 宽度
|
||||
const cascaderDeep = ref(1); // 默认层级只有一层
|
||||
const cascaderViewInner = ref<HTMLElement | null>(null); // 输入框内容容器 DOM
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
innerValue.value = val;
|
||||
if (
|
||||
props.mode === 'MS' &&
|
||||
Array.isArray(val) &&
|
||||
val[0] &&
|
||||
typeof val[0] === 'string' &&
|
||||
props.levelTop?.includes(val[0])
|
||||
) {
|
||||
level.value = val[0] as string;
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => innerValue.value,
|
||||
(val) => {
|
||||
emit('update:modelValue', val);
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.options,
|
||||
(arr) => {
|
||||
cascaderDeep.value = calculateMaxDepth(arr);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
const getOptionComputedStyle = computed(() => {
|
||||
// 减去 80px 是为了防止溢出,因为会出现单选框、右侧箭头
|
||||
return {
|
||||
width: cascaderDeep.value <= 2 ? `${cascaderWidth.value / cascaderDeep.value - 80}px` : '150px',
|
||||
};
|
||||
});
|
||||
|
||||
interface CascaderValue {
|
||||
level: keyof typeof props.levelTop;
|
||||
value: string;
|
||||
}
|
||||
|
||||
function handleLevelChange(val: string | number | boolean) {
|
||||
innerValue.value = [val as string];
|
||||
}
|
||||
|
||||
function handleMsCascaderChange(
|
||||
value:
|
||||
| string
|
||||
| number
|
||||
| Record<string, any>
|
||||
| (string | number | Record<string, any> | (string | number | Record<string, any>)[])[]
|
||||
| undefined
|
||||
) {
|
||||
const lastValue = Array.isArray(value) ? value[value.length - 1] : '';
|
||||
if (lastValue && Array.isArray(innerValue.value)) {
|
||||
if (typeof lastValue === 'object') {
|
||||
// 当选中二级选项时,剔除选中的一级选项以及不同类别的选项
|
||||
innerValue.value = innerValue.value.filter(
|
||||
(e) => typeof e !== 'string' && (e as CascaderValue).level === lastValue.level
|
||||
);
|
||||
level.value = '';
|
||||
}
|
||||
}
|
||||
nextTick(() => {
|
||||
if (cascader.value && cascaderViewInner.value && Array.isArray(innerValue.value)) {
|
||||
if (maxTagCount.value !== 0 && innerValue.value.length > maxTagCount.value) return; // 已经超过最大数量的展示,不需要再计算
|
||||
let lastWidth = cascaderViewInner.value?.getBoundingClientRect().width || 0;
|
||||
const tags = cascaderViewInner.value.querySelectorAll('.arco-tag');
|
||||
let tagCount = 0;
|
||||
for (let i = 0; i < tags.length; i++) {
|
||||
const tagWidth = Number(getComputedStyle(tags[i]).width.replace('px', ''));
|
||||
if (lastWidth < tagWidth + 65) {
|
||||
// 65px 是“+N”的标签宽度+聚焦输入框的宽度
|
||||
lastWidth = 0; // 当剩余宽度已经放不下刚添加的标签,则剩余宽度置为 0,避免后面再进行计算
|
||||
break;
|
||||
} else {
|
||||
tagCount += 1;
|
||||
lastWidth = lastWidth - tagWidth - 5;
|
||||
}
|
||||
}
|
||||
maxTagCount.value = tagCount === 0 ? 1 : tagCount;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (cascader.value) {
|
||||
cascaderWidth.value = cascader.value.$el.nextElementSibling.getBoundingClientRect().width;
|
||||
cascaderViewInner.value = cascader.value.$el.nextElementSibling.querySelector('.arco-select-view-inner');
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
cascaderViewInner.value = null; // 释放 DOM 引用
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ms-cascader {
|
||||
@apply overflow-hidden;
|
||||
.arco-select-view-inner {
|
||||
height: 30px;
|
||||
.arco-select-view-tag {
|
||||
max-width: 75%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ms-cascader-popper {
|
||||
.arco-cascader-panel {
|
||||
.arco-cascader-panel-column {
|
||||
.arco-virtual-list {
|
||||
.ms-scroll-bar();
|
||||
}
|
||||
}
|
||||
.arco-cascader-panel-column:first-child {
|
||||
.arco-checkbox {
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -110,7 +110,7 @@
|
|||
if (appStore.device === 'desktop') appStore.updateSettings({ menuCollapse: val });
|
||||
};
|
||||
|
||||
const personalMenusVisble = ref(false);
|
||||
const personalMenusVisible = ref(false);
|
||||
|
||||
const personalMenus = [
|
||||
{
|
||||
|
@ -136,12 +136,12 @@
|
|||
const personalInfoMenu = () => {
|
||||
return (
|
||||
<a-trigger
|
||||
v-model:popup-visible={personalMenusVisble.value}
|
||||
v-model:popup-visible={personalMenusVisible.value}
|
||||
trigger="click"
|
||||
unmount-on-close={false}
|
||||
popup-offset={4}
|
||||
position="right"
|
||||
class={['arco-trigger-menu absolute', personalMenusVisble.value ? 'block' : 'hidden']}
|
||||
class={['arco-trigger-menu absolute', personalMenusVisible.value ? 'block' : 'hidden']}
|
||||
v-slots={{
|
||||
content: () => (
|
||||
<div class="arco-trigger-menu-inner">
|
||||
|
@ -163,7 +163,7 @@
|
|||
} else if (e.route) {
|
||||
goto(e.route);
|
||||
}
|
||||
personalMenusVisble.value = false;
|
||||
personalMenusVisible.value = false;
|
||||
}}
|
||||
>
|
||||
{e.icon}
|
||||
|
|
|
@ -99,18 +99,18 @@
|
|||
return appStore.menuCollapse ? collapsedWidth : appStore.menuWidth;
|
||||
});
|
||||
|
||||
const _spcialHeight = props.hasBreadcrumb ? 31 + props.specialHeight : props.specialHeight; // 有面包屑的话,默认面包屑高度31
|
||||
const _specialHeight = props.hasBreadcrumb ? 31 + props.specialHeight : props.specialHeight; // 有面包屑的话,默认面包屑高度31
|
||||
|
||||
const cardOverHeight = computed(() => {
|
||||
if (props.simple) {
|
||||
// 简单模式没有标题、没有底部
|
||||
return 136 + _spcialHeight;
|
||||
return 136 + _specialHeight;
|
||||
}
|
||||
if (props.hideFooter) {
|
||||
// 隐藏底部
|
||||
return 192;
|
||||
}
|
||||
return 246 + _spcialHeight;
|
||||
return 246 + _specialHeight;
|
||||
});
|
||||
|
||||
function back() {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
import { editorProps, CustomeTheme } from './types';
|
||||
import { editorProps, CustomTheme } from './types';
|
||||
import './userWorker';
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
|
@ -35,8 +35,8 @@
|
|||
|
||||
const init = () => {
|
||||
// 注册自定义主题
|
||||
if (MsCodeEditorTheme[props.theme as CustomeTheme]) {
|
||||
monaco.editor.defineTheme(props.theme, MsCodeEditorTheme[props.theme as CustomeTheme]);
|
||||
if (MsCodeEditorTheme[props.theme as CustomTheme]) {
|
||||
monaco.editor.defineTheme(props.theme, MsCodeEditorTheme[props.theme as CustomTheme]);
|
||||
}
|
||||
editor = monaco.editor.create(codeEditBox.value, {
|
||||
value: props.modelValue,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import MSText from './MS-text';
|
||||
|
||||
import type { CustomeTheme } from '../types';
|
||||
import type { CustomTheme } from '../types';
|
||||
|
||||
const MsCodeEditorThemes: Record<CustomeTheme, any> = {
|
||||
const MsCodeEditorThemes: Record<CustomTheme, any> = {
|
||||
'MS-text': MSText,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { PropType } from 'vue';
|
||||
|
||||
export type CustomeTheme = 'MS-text';
|
||||
export type Theme = 'vs' | 'hc-black' | 'vs-dark' | CustomeTheme;
|
||||
export type CustomTheme = 'MS-text';
|
||||
export type Theme = 'vs' | 'hc-black' | 'vs-dark' | CustomTheme;
|
||||
export type FoldingStrategy = 'auto' | 'indentation';
|
||||
export type RenderLineHighlight = 'all' | 'line' | 'none' | 'gutter';
|
||||
export type Language =
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
word: 'icon-icon_file-word_colorful1',
|
||||
pdf: 'icon-icon_file-pdf_colorful1',
|
||||
txt: 'icon-icon_file-text_colorful1',
|
||||
vedio: 'icon-icon_file-vedio_colorful1',
|
||||
video: 'icon-icon_file-vedio_colorful1',
|
||||
sql: 'icon-icon_file-sql_colorful1',
|
||||
csv: 'icon-icon_file-CSV_colorful1',
|
||||
zip: 'icon-a-icon_file-compressed_colorful1',
|
||||
|
|
|
@ -3,7 +3,7 @@ export enum UploadAcceptEnum {
|
|||
word = '.docx,.doc',
|
||||
pdf = '.pdf',
|
||||
txt = '.txt',
|
||||
vedio = '.mp4',
|
||||
video = '.mp4',
|
||||
sql = '.sql',
|
||||
csv = '.csv',
|
||||
zip = '.zip',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// 请求返回结构
|
||||
export default interface CommonReponse<T> {
|
||||
export default interface CommonResponse<T> {
|
||||
code: number;
|
||||
message: string;
|
||||
messageDetail: string;
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
export interface OptionsItem {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface LogOptions {
|
||||
organizationList: OptionsItem[];
|
||||
projectList: OptionsItem[];
|
||||
}
|
|
@ -71,7 +71,7 @@ export interface CreateUserParams {
|
|||
userInfoList: SimpleUserInfo[];
|
||||
userRoleIdList: string[];
|
||||
}
|
||||
export interface UpdateUserStausParams {
|
||||
export interface UpdateUserStatusParams {
|
||||
userIdList: string[];
|
||||
enable: boolean;
|
||||
}
|
||||
|
|
|
@ -52,6 +52,16 @@ const Setting: AppRouteRecordRaw = {
|
|||
isTopMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'parameter',
|
||||
name: 'settingSystemParameter',
|
||||
component: () => import('@/views/setting/system/config/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.settings.system.parameter',
|
||||
roles: ['*'],
|
||||
isTopMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'resourcePool',
|
||||
name: 'settingSystemResourcePool',
|
||||
|
@ -100,21 +110,21 @@ const Setting: AppRouteRecordRaw = {
|
|||
},
|
||||
},
|
||||
{
|
||||
path: 'pluginmanger',
|
||||
name: 'settingSystemPluginManger',
|
||||
component: () => import('@/views/setting/system/pluginManager/index.vue'),
|
||||
path: 'log',
|
||||
name: 'settingSystemLog',
|
||||
component: () => import('@/views/setting/system/log/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.settings.system.pluginmanger',
|
||||
locale: 'menu.settings.system.log',
|
||||
roles: ['*'],
|
||||
isTopMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'parameter',
|
||||
name: 'settingSystemParameter',
|
||||
component: () => import('@/views/setting/system/config/index.vue'),
|
||||
path: 'pluginmanger',
|
||||
name: 'settingSystemPluginManger',
|
||||
component: () => import('@/views/setting/system/pluginManager/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.settings.system.parameter',
|
||||
locale: 'menu.settings.system.pluginmanger',
|
||||
roles: ['*'],
|
||||
isTopMenu: true,
|
||||
},
|
||||
|
|
|
@ -82,7 +82,7 @@ const useAppStore = defineStore('app', {
|
|||
getCurrentProjectId(state: AppState): string {
|
||||
return state.currentProjectId;
|
||||
},
|
||||
getDefaulPageConfig(state: AppState): PageConfig {
|
||||
getDefaultPageConfig(state: AppState): PageConfig {
|
||||
return {
|
||||
...state.defaultThemeConfig,
|
||||
...state.defaultLoginConfig,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Recordable } from '#/global';
|
||||
import { isObject } from './is';
|
||||
|
||||
type TargetContext = '_self' | '_parent' | '_blank' | '_top';
|
||||
|
@ -146,3 +147,28 @@ export function characterLimit(str?: string): string {
|
|||
}
|
||||
return `${str.slice(0, 20 - 3)}...`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归计算树形数组的最大深度
|
||||
* @param node 树形数组
|
||||
* @param depth 深度
|
||||
* @returns 最大深度
|
||||
*/
|
||||
export interface Node {
|
||||
children?: Node[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function calculateMaxDepth(arr?: Node[], depth = 0) {
|
||||
if (!arr || arr.length === 0) {
|
||||
return depth;
|
||||
}
|
||||
|
||||
let maxDepth = depth;
|
||||
Object.values(arr).forEach((item) => {
|
||||
const childDepth = calculateMaxDepth(item.children, depth + 1);
|
||||
maxDepth = Math.max(maxDepth, childDepth);
|
||||
});
|
||||
|
||||
return maxDepth;
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
:placeholder="t('system.config.baseInfo.pageUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
<MsFormItemSub :text="t('system.config.baseInfo.pageUrlSub', { url: defaulUrl })" @fill="fillDefaultUrl" />
|
||||
<MsFormItemSub :text="t('system.config.baseInfo.pageUrlSub', { url: defaultUrl })" @fill="fillDefaultUrl" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('system.config.prometheus')" field="prometheusHost" asterisk-position="end">
|
||||
<a-input
|
||||
|
@ -85,7 +85,7 @@
|
|||
allow-clear
|
||||
></a-input>
|
||||
<MsFormItemSub
|
||||
:text="t('system.config.baseInfo.prometheusSub', { prometheus: defaulPrometheus })"
|
||||
:text="t('system.config.baseInfo.prometheusSub', { prometheus: defaultPrometheus })"
|
||||
@fill="fillDefaultPrometheus"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
@ -219,15 +219,15 @@
|
|||
const baseInfoForm = ref({ ...baseInfo.value });
|
||||
const baseInfoDescs = ref<Description[]>([]);
|
||||
// 默认示例
|
||||
const defaulUrl = 'https://metersphere.com';
|
||||
const defaulPrometheus = 'http://ms-prometheus:9090';
|
||||
const defaultUrl = 'https://metersphere.com';
|
||||
const defaultPrometheus = 'http://ms-prometheus:9090';
|
||||
|
||||
function fillDefaultUrl() {
|
||||
baseInfoForm.value.url = defaulUrl;
|
||||
baseInfoForm.value.url = defaultUrl;
|
||||
}
|
||||
|
||||
function fillDefaultPrometheus() {
|
||||
baseInfoForm.value.prometheusHost = defaulPrometheus;
|
||||
baseInfoForm.value.prometheusHost = defaultPrometheus;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -157,7 +157,7 @@
|
|||
ref="loginConfigFormRef"
|
||||
:model="pageConfig"
|
||||
layout="vertical"
|
||||
:rules="{ slogan: [{ required: true, message: t('system.config.page.sloganRquired') }] }"
|
||||
:rules="{ slogan: [{ required: true, message: t('system.config.page.sloganRequired') }] }"
|
||||
>
|
||||
<a-form-item
|
||||
:label="t('system.config.page.slogan')"
|
||||
|
@ -259,7 +259,7 @@
|
|||
ref="platformConfigFormRef"
|
||||
:model="pageConfig"
|
||||
layout="vertical"
|
||||
:rules="{ platformName: [{ required: true, message: t('system.config.page.platformNameRquired') }] }"
|
||||
:rules="{ platformName: [{ required: true, message: t('system.config.page.platformNameRequired') }] }"
|
||||
>
|
||||
<a-form-item
|
||||
:label="t('system.config.page.platformName')"
|
||||
|
@ -423,7 +423,7 @@
|
|||
* 全部重置
|
||||
*/
|
||||
function resetAll() {
|
||||
pageConfig.value = { ...appStore.getDefaulPageConfig };
|
||||
pageConfig.value = { ...appStore.getDefaultPageConfig };
|
||||
}
|
||||
|
||||
function makeParams() {
|
||||
|
|
|
@ -68,7 +68,7 @@ export default {
|
|||
'It is recommended to use the SVG format for the background image; the recommended size for vector graphics is 800*900, and the recommended size for bitmaps is 800*900; the size of the image only supports within 1 MB',
|
||||
'system.config.page.slogan': 'Slogan',
|
||||
'system.config.page.sloganPlaceholder': 'Please enter Slogan',
|
||||
'system.config.page.sloganRquired': 'Slogan cannot be empty',
|
||||
'system.config.page.sloganRequired': 'Slogan cannot be empty',
|
||||
'system.config.page.sloganTip': 'The slogan under the product logo',
|
||||
'system.config.page.title': 'Website name',
|
||||
'system.config.page.titlePlaceholder': 'Please enter a website name',
|
||||
|
@ -81,7 +81,7 @@ export default {
|
|||
'The logo displayed at the top of the platform page; it is recommended to use a transparent background image in SVG or PNG format, with a height of not less than 32px; the image size is only supported within 200 KB',
|
||||
'system.config.page.platformName': 'Platform name',
|
||||
'system.config.page.platformNamePlaceholder': 'Please enter a platform name',
|
||||
'system.config.page.platformNameRquired': 'Platform name cannot be empty',
|
||||
'system.config.page.platformNameRequired': 'Platform name cannot be empty',
|
||||
'system.config.page.platformNameTip':
|
||||
'The general product name of the whole site, the recommended number of words is 8',
|
||||
'system.config.page.helpDoc': 'Help document',
|
||||
|
|
|
@ -67,7 +67,7 @@ export default {
|
|||
'背景图建议使用 SVG 格式;矢量图建议尺寸 800*900,位图建议尺寸 800*900;图片大小仅支持 1 MB 以内',
|
||||
'system.config.page.slogan': 'Slogan',
|
||||
'system.config.page.sloganPlaceholder': '请输入 Slogan',
|
||||
'system.config.page.sloganRquired': 'Slogan不能为空',
|
||||
'system.config.page.sloganRequired': 'Slogan不能为空',
|
||||
'system.config.page.sloganTip': '产品logo下的 slogan',
|
||||
'system.config.page.title': '网站名称',
|
||||
'system.config.page.titlePlaceholder': '请输入网站名称',
|
||||
|
@ -79,7 +79,7 @@ export default {
|
|||
'平台页面顶部显示的 logo;建议使用 SVG 或 PNG 格式透明背景图片,高度不小于 32px;图片大小仅支持 200 KB 以内',
|
||||
'system.config.page.platformName': '平台名称',
|
||||
'system.config.page.platformNamePlaceholder': '请输入平台名称',
|
||||
'system.config.page.platformNameRquired': '平台名称不能为空',
|
||||
'system.config.page.platformNameRequired': '平台名称不能为空',
|
||||
'system.config.page.platformNameTip': '全站通用产品名称,建议字数 8',
|
||||
'system.config.page.helpDoc': '帮助文档',
|
||||
'system.config.page.helpDocPlaceholder': '请输入帮助文档地址',
|
||||
|
|
|
@ -0,0 +1,240 @@
|
|||
<template>
|
||||
<MsCard simple auto-height>
|
||||
<div class="filter-box">
|
||||
<a-input
|
||||
v-model:model-value="operUser"
|
||||
class="filter-item"
|
||||
:placeholder="t('system.log.operaterPlaceholder')"
|
||||
allow-clear
|
||||
>
|
||||
<template #prefix>
|
||||
{{ t('system.log.operater') }}
|
||||
</template>
|
||||
</a-input>
|
||||
<a-range-picker
|
||||
v-model:model-value="time"
|
||||
show-time
|
||||
:time-picker-props="{
|
||||
defaultValue: ['00:00:00', '00:00:00'],
|
||||
}"
|
||||
:disabled-date="disabledDate"
|
||||
class="filter-item"
|
||||
>
|
||||
<template #prefix>
|
||||
{{ t('system.log.operateTime') }}
|
||||
</template>
|
||||
</a-range-picker>
|
||||
<MsCascader
|
||||
v-model="operateRange"
|
||||
:options="rangeOptions"
|
||||
:prefix="t('system.log.operateRange')"
|
||||
:level-top="levelTop"
|
||||
:virtual-list-props="{ height: 200 }"
|
||||
/>
|
||||
<a-select v-model:model-value="type" class="filter-item">
|
||||
<template #prefix>
|
||||
{{ t('system.log.operateType') }}
|
||||
</template>
|
||||
<a-option v-for="opt of typeOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</a-option>
|
||||
</a-select>
|
||||
<MsCascader
|
||||
v-model="_module"
|
||||
:options="moduleOptions"
|
||||
mode="native"
|
||||
:prefix="t('system.log.operateTarget')"
|
||||
:virtual-list-props="{ height: 200 }"
|
||||
:placeholder="t('system.log.operateTargetPlaceholder')"
|
||||
/>
|
||||
<a-input
|
||||
v-model:model-value="content"
|
||||
class="filter-item"
|
||||
:placeholder="t('system.log.operateNamePlaceholder')"
|
||||
allow-clear
|
||||
>
|
||||
<template #prefix>
|
||||
{{ t('system.log.operateName') }}
|
||||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
<a-button type="outline">{{ t('system.log.search') }}</a-button>
|
||||
<a-button type="outline" class="arco-btn-outline--secondary ml-[8px]" @click="resetFilter">
|
||||
{{ t('system.log.reset') }}
|
||||
</a-button>
|
||||
</MsCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeMount, ref } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { getLogOptions } from '@/api/modules/setting/log';
|
||||
import MsCascader from '@/components/bussiness/ms-cascader/index.vue';
|
||||
|
||||
import type { CascaderOption } from '@arco-design/web-vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const operUser = ref(''); // 操作人
|
||||
const levelTop = ['SYSTEM', 'ORGANIZATION', 'PROJECT']; // 操作范围顶级
|
||||
const operateRange = ref<(string | number | Record<string, any>)[]>([levelTop[0]]); // 操作范围
|
||||
const type = ref(''); // 操作类型
|
||||
const _module = ref(''); // 操作对象
|
||||
const content = ref(''); // 名称
|
||||
const time = ref<(Date | string | number)[]>([]); // 操作时间
|
||||
const rangeOptions = ref<CascaderOption[]>([
|
||||
{
|
||||
value: {
|
||||
level: 0,
|
||||
value: levelTop[0],
|
||||
},
|
||||
label: t('system.log.system'),
|
||||
isLeaf: true,
|
||||
},
|
||||
]);
|
||||
const moduleOptions = ref<CascaderOption[]>([
|
||||
{
|
||||
value: 'system',
|
||||
label: t('system.log.system'),
|
||||
isLeaf: true,
|
||||
},
|
||||
]);
|
||||
const typeOptions = [
|
||||
{
|
||||
label: t('system.log.operateType.all'),
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
label: t('system.log.operateType.add'),
|
||||
value: 'ADD',
|
||||
},
|
||||
{
|
||||
label: t('system.log.operateType.delete'),
|
||||
value: 'DELETE',
|
||||
},
|
||||
{
|
||||
label: t('system.log.operateType.update'),
|
||||
value: 'UPDATE',
|
||||
},
|
||||
{
|
||||
label: t('system.log.operateType.debug'),
|
||||
value: 'DEBUG',
|
||||
},
|
||||
{
|
||||
label: t('system.log.operateType.review'),
|
||||
value: 'REVIEW',
|
||||
},
|
||||
{
|
||||
label: t('system.log.operateType.copy'),
|
||||
value: 'COPY',
|
||||
},
|
||||
{
|
||||
label: t('system.log.operateType.execute'),
|
||||
value: 'EXECUTE',
|
||||
},
|
||||
{
|
||||
label: t('system.log.operateType.share'),
|
||||
value: 'SHARE',
|
||||
},
|
||||
{
|
||||
label: t('system.log.operateType.restore'),
|
||||
value: 'RESTORE',
|
||||
},
|
||||
{
|
||||
label: t('system.log.operateType.import'),
|
||||
value: 'IMPORT',
|
||||
},
|
||||
{
|
||||
label: t('system.log.operateType.export'),
|
||||
value: 'EXPORT',
|
||||
},
|
||||
{
|
||||
label: t('system.log.operateType.login'),
|
||||
value: 'LOGIN',
|
||||
},
|
||||
{
|
||||
label: t('system.log.operateType.select'),
|
||||
value: 'SELECT',
|
||||
},
|
||||
{
|
||||
label: t('system.log.operateType.recover'),
|
||||
value: 'RECOVER',
|
||||
},
|
||||
{
|
||||
label: t('system.log.operateType.logout'),
|
||||
value: 'LOGOUT',
|
||||
},
|
||||
];
|
||||
|
||||
onBeforeMount(async () => {
|
||||
try {
|
||||
const res = await getLogOptions();
|
||||
rangeOptions.value.push({
|
||||
value: {
|
||||
level: 0,
|
||||
value: levelTop[1],
|
||||
},
|
||||
label: t('system.log.orgnization'),
|
||||
children: res.organizationList.map((e) => ({
|
||||
value: {
|
||||
level: levelTop[1],
|
||||
value: e.id,
|
||||
},
|
||||
label: e.name,
|
||||
isLeaf: true,
|
||||
})),
|
||||
});
|
||||
rangeOptions.value.push({
|
||||
value: {
|
||||
level: 0,
|
||||
value: levelTop[2],
|
||||
},
|
||||
label: t('system.log.project'),
|
||||
children: res.projectList.map((e) => ({
|
||||
value: {
|
||||
level: levelTop[2],
|
||||
value: e.id,
|
||||
},
|
||||
label: e.name,
|
||||
isLeaf: true,
|
||||
})),
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
|
||||
function disabledDate(current?: Date) {
|
||||
const now = dayjs();
|
||||
const endDate = dayjs(current);
|
||||
return !!current && endDate.isAfter(now);
|
||||
}
|
||||
|
||||
function resetFilter() {
|
||||
operUser.value = '';
|
||||
operateRange.value = [levelTop[0]];
|
||||
type.value = '';
|
||||
_module.value = '';
|
||||
content.value = '';
|
||||
time.value = [];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.filter-box {
|
||||
@apply grid;
|
||||
|
||||
margin-bottom: 16px;
|
||||
gap: 16px;
|
||||
}
|
||||
@media screen and (max-width: 1400px) {
|
||||
.filter-box {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 1400px) {
|
||||
.filter-box {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1 @@
|
|||
export default {};
|
|
@ -0,0 +1,32 @@
|
|||
export default {
|
||||
'system.log.operater': '操作人',
|
||||
'system.log.operaterPlaceholder': '请输入用户名/邮箱',
|
||||
'system.log.operateTime': '操作时间',
|
||||
'system.log.operateRange': '操作范围',
|
||||
'system.log.operateType': '操作类型',
|
||||
'system.log.operateTarget': '操作对象',
|
||||
'system.log.operateTargetPlaceholder': '请选择',
|
||||
'system.log.operateName': '名称',
|
||||
'system.log.operateNamePlaceholder': '请输入用例/环境/菜单名',
|
||||
'system.log.system': '系统',
|
||||
'system.log.orgnization': '组织',
|
||||
'system.log.project': '项目',
|
||||
'system.log.search': '查询',
|
||||
'system.log.reset': '重置',
|
||||
'system.log.operateType.all': '全部',
|
||||
'system.log.operateType.add': '新增',
|
||||
'system.log.operateType.delete': '删除',
|
||||
'system.log.operateType.update': '更新',
|
||||
'system.log.operateType.debug': '调试',
|
||||
'system.log.operateType.review': '评审',
|
||||
'system.log.operateType.copy': '复制',
|
||||
'system.log.operateType.execute': '执行',
|
||||
'system.log.operateType.share': '分享',
|
||||
'system.log.operateType.restore': '重置',
|
||||
'system.log.operateType.import': '导入',
|
||||
'system.log.operateType.export': '导出',
|
||||
'system.log.operateType.login': '登录',
|
||||
'system.log.operateType.select': '选择',
|
||||
'system.log.operateType.recover': '恢复',
|
||||
'system.log.operateType.logout': '登出',
|
||||
};
|
|
@ -60,7 +60,7 @@
|
|||
multiple
|
||||
allow-clear
|
||||
>
|
||||
<a-option v-for="org of orgOptons" :key="org.id" :value="org.id">{{ org.name }}</a-option>
|
||||
<a-option v-for="org of orgOptions" :key="org.id" :value="org.id">{{ org.name }}</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
|
@ -385,7 +385,7 @@
|
|||
};
|
||||
const form = ref({ ...defaultForm });
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const orgOptons = ref<SelectOptionData>([]);
|
||||
const orgOptions = ref<SelectOptionData>([]);
|
||||
const useList = [
|
||||
{
|
||||
label: 'system.resourcePool.usePerformance',
|
||||
|
@ -403,7 +403,7 @@
|
|||
const defaultGrid = 'http://selenium-hub:4444';
|
||||
|
||||
onBeforeMount(async () => {
|
||||
orgOptons.value = await getAllOrgList();
|
||||
orgOptions.value = await getAllOrgList();
|
||||
});
|
||||
|
||||
async function initPoolInfo() {
|
||||
|
@ -553,7 +553,7 @@
|
|||
watchEffect(() => {
|
||||
const { nodesList } = form.value.testResourceDTO;
|
||||
let res = '';
|
||||
for (let i = 0; i < nodesList.length; i++) {
|
||||
for (let i = 0; i < nodesList?.length; i++) {
|
||||
const node = nodesList[i];
|
||||
// 按顺序拼接:ip、port、monitor、concurrentNumber
|
||||
if (Object.values(node).every((e) => e !== '')) {
|
||||
|
|
|
@ -80,40 +80,40 @@
|
|||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'system.resourcePool.tableColunmName',
|
||||
title: 'system.resourcePool.tableColumnName',
|
||||
slotName: 'name',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.resourcePool.tableColunmStatus',
|
||||
title: 'system.resourcePool.tableColumnStatus',
|
||||
slotName: 'enable',
|
||||
dataIndex: 'enable',
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.resourcePool.tableColunmDescription',
|
||||
title: 'system.resourcePool.tableColumnDescription',
|
||||
dataIndex: 'description',
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.resourcePool.tableColunmType',
|
||||
title: 'system.resourcePool.tableColumnType',
|
||||
dataIndex: 'type',
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.resourcePool.tableColunmCreateTime',
|
||||
title: 'system.resourcePool.tableColumnCreateTime',
|
||||
dataIndex: 'createTime',
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.resourcePool.tableColunmUpdateTime',
|
||||
title: 'system.resourcePool.tableColumnUpdateTime',
|
||||
dataIndex: 'updateTime',
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.resourcePool.tableColunmActions',
|
||||
title: 'system.resourcePool.tableColumnActions',
|
||||
slotName: 'action',
|
||||
fixed: 'right',
|
||||
width: 140,
|
||||
|
|
|
@ -5,13 +5,13 @@ export default {
|
|||
'system.resourcePool.tableEnable': 'Enable',
|
||||
'system.resourcePool.tableDisable': 'Disable',
|
||||
'system.resourcePool.editPool': 'Edit',
|
||||
'system.resourcePool.tableColunmName': 'Name',
|
||||
'system.resourcePool.tableColunmStatus': 'Status',
|
||||
'system.resourcePool.tableColunmDescription': 'Description',
|
||||
'system.resourcePool.tableColunmType': 'Type',
|
||||
'system.resourcePool.tableColunmCreateTime': 'CreateTime',
|
||||
'system.resourcePool.tableColunmUpdateTime': 'UpdateTime',
|
||||
'system.resourcePool.tableColunmActions': 'Actions',
|
||||
'system.resourcePool.tableColumnName': 'Name',
|
||||
'system.resourcePool.tableColumnStatus': 'Status',
|
||||
'system.resourcePool.tableColumnDescription': 'Description',
|
||||
'system.resourcePool.tableColumnType': 'Type',
|
||||
'system.resourcePool.tableColumnCreateTime': 'CreateTime',
|
||||
'system.resourcePool.tableColumnUpdateTime': 'UpdateTime',
|
||||
'system.resourcePool.tableColumnActions': 'Actions',
|
||||
'system.resourcePool.enablePoolSuccess': 'Enabled successfully',
|
||||
'system.resourcePool.disablePoolTip': 'About to disable resource pool `{name}`',
|
||||
'system.resourcePool.disablePoolContent': 'When disabled, tests using this resource pool will stop executing',
|
||||
|
|
|
@ -5,13 +5,13 @@ export default {
|
|||
'system.resourcePool.tableEnable': '启用',
|
||||
'system.resourcePool.tableDisable': '禁用',
|
||||
'system.resourcePool.editPool': '编辑',
|
||||
'system.resourcePool.tableColunmName': '名称',
|
||||
'system.resourcePool.tableColunmStatus': '状态',
|
||||
'system.resourcePool.tableColunmDescription': '描述',
|
||||
'system.resourcePool.tableColunmType': '类型',
|
||||
'system.resourcePool.tableColunmCreateTime': '创建时间',
|
||||
'system.resourcePool.tableColunmUpdateTime': '更新时间',
|
||||
'system.resourcePool.tableColunmActions': '操作',
|
||||
'system.resourcePool.tableColumnName': '名称',
|
||||
'system.resourcePool.tableColumnStatus': '状态',
|
||||
'system.resourcePool.tableColumnDescription': '描述',
|
||||
'system.resourcePool.tableColumnType': '类型',
|
||||
'system.resourcePool.tableColumnCreateTime': '创建时间',
|
||||
'system.resourcePool.tableColumnUpdateTime': '更新时间',
|
||||
'system.resourcePool.tableColumnActions': '操作',
|
||||
'system.resourcePool.enablePoolSuccess': '启用成功',
|
||||
'system.resourcePool.disablePoolTip': '即将禁用资源池 `{name}`',
|
||||
'system.resourcePool.disablePoolContent': '禁用后,正在使用该资源池的测试会停止执行',
|
||||
|
|
|
@ -2,17 +2,17 @@ export const daemonSet = `apiVersion: apps/v1
|
|||
kind: DaemonSet
|
||||
metadata:
|
||||
labels:
|
||||
app: ms-node-controller
|
||||
app: task-runner
|
||||
name: {name}
|
||||
namespace: {namespace}
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ms-node-controller
|
||||
app: task-runner
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ms-node-controller
|
||||
app: task-runner
|
||||
spec:
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
|
@ -23,16 +23,16 @@ spec:
|
|||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- ms-node-controller
|
||||
- task-runner
|
||||
topologyKey: kubernetes.io/hostname
|
||||
weight: 100
|
||||
containers:
|
||||
- env:
|
||||
image: {image}
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: ms-node-controller
|
||||
name: task-runner
|
||||
ports:
|
||||
- containerPort: 8082
|
||||
- containerPort: 6000
|
||||
protocol: TCP
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
|
@ -48,18 +48,18 @@ export const deployment = `apiVersion: apps/v1
|
|||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: ms-node-controller
|
||||
app: task-runner
|
||||
name: {name}
|
||||
namespace: {namespace}
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ms-node-controller
|
||||
app: task-runner
|
||||
replicas: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ms-node-controller
|
||||
app: task-runner
|
||||
spec:
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
|
@ -70,16 +70,16 @@ spec:
|
|||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- ms-node-controller
|
||||
- task-runner
|
||||
topologyKey: kubernetes.io/hostname
|
||||
weight: 100
|
||||
containers:
|
||||
- env:
|
||||
image: {image}
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: ms-node-controller
|
||||
name: task-runner
|
||||
ports:
|
||||
- containerPort: 8082
|
||||
- containerPort: 6000
|
||||
protocol: TCP
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
|
@ -211,8 +211,6 @@ spec:
|
|||
value: \${BOOTSTRAP_SERVERS}
|
||||
- name: RATIO
|
||||
value: "\${RATIO}"
|
||||
- name: REPORT_FINAL
|
||||
value: "\${REPORT_FINAL}"
|
||||
- name: TEST_ID
|
||||
value: \${TEST_ID}
|
||||
- name: THREAD_NUM
|
||||
|
@ -221,8 +219,6 @@ spec:
|
|||
value: \${HEAP}
|
||||
- name: REPORT_ID
|
||||
value: \${REPORT_ID}
|
||||
- name: REPORT_REALTIME
|
||||
value: "\${REPORT_REALTIME}"
|
||||
- name: RESOURCE_INDEX
|
||||
value: "\${RESOURCE_INDEX}"
|
||||
- name: LOG_TOPIC
|
||||
|
@ -240,53 +236,6 @@ spec:
|
|||
name: test-files
|
||||
- mountPath: /jmeter-log
|
||||
name: log-files
|
||||
- command:
|
||||
- sh
|
||||
- -c
|
||||
- /generate-report.sh
|
||||
env:
|
||||
- name: START_TIME
|
||||
value: "\${START_TIME}"
|
||||
- name: GRANULARITY
|
||||
value: "\${GRANULARITY}"
|
||||
- name: JMETER_REPORTS_TOPIC
|
||||
value: \${JMETER_REPORTS_TOPIC}
|
||||
- name: METERSPHERE_URL
|
||||
value: \${METERSPHERE_URL}
|
||||
- name: RESOURCE_ID
|
||||
value: \${RESOURCE_ID}
|
||||
- name: BACKEND_LISTENER
|
||||
value: "\${BACKEND_LISTENER}"
|
||||
- name: BOOTSTRAP_SERVERS
|
||||
value: \${BOOTSTRAP_SERVERS}
|
||||
- name: RATIO
|
||||
value: "\${RATIO}"
|
||||
- name: REPORT_FINAL
|
||||
value: "\${REPORT_FINAL}"
|
||||
- name: TEST_ID
|
||||
value: \${TEST_ID}
|
||||
- name: THREAD_NUM
|
||||
value: "\${THREAD_NUM}"
|
||||
- name: HEAP
|
||||
value: \${HEAP}
|
||||
- name: REPORT_ID
|
||||
value: \${REPORT_ID}
|
||||
- name: REPORT_REALTIME
|
||||
value: "\${REPORT_REALTIME}"
|
||||
- name: RESOURCE_INDEX
|
||||
value: "\${RESOURCE_INDEX}"
|
||||
- name: LOG_TOPIC
|
||||
value: \${LOG_TOPIC}
|
||||
- name: GC_ALGO
|
||||
value: \${GC_ALGO}
|
||||
image: \${JMETER_IMAGE}
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: report
|
||||
volumeMounts:
|
||||
- mountPath: /test
|
||||
name: test-files
|
||||
- mountPath: /jmeter-log
|
||||
name: log-files
|
||||
restartPolicy: Never
|
||||
volumes:
|
||||
- emptyDir: {}
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
const target = ref<string[]>([]);
|
||||
const batchModalMode = ref<'project' | 'usergroup' | 'organization'>('project');
|
||||
|
||||
function handelTableBatch(action: string) {
|
||||
function handleTableBatch(action: string) {
|
||||
switch (action) {
|
||||
case 'batchAddProject':
|
||||
batchModalMode.value = 'project';
|
||||
|
@ -103,7 +103,7 @@
|
|||
() => props.visible,
|
||||
(val) => {
|
||||
if (val) {
|
||||
handelTableBatch(props.action);
|
||||
handleTableBatch(props.action);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -51,11 +51,11 @@
|
|||
const inviteVisible = ref(false);
|
||||
const inviteLoading = ref(false);
|
||||
const inviteFormRef = ref<FormInstance | null>(null);
|
||||
const defaulInviteForm = {
|
||||
const defaultInviteForm = {
|
||||
emails: [],
|
||||
userGroup: [],
|
||||
};
|
||||
const emailForm = ref(cloneDeep(defaulInviteForm));
|
||||
const emailForm = ref(cloneDeep(defaultInviteForm));
|
||||
const userGroupOptions = ref([
|
||||
{
|
||||
label: 'Beijing',
|
||||
|
@ -88,7 +88,7 @@
|
|||
function cancelInvite() {
|
||||
inviteVisible.value = false;
|
||||
inviteFormRef.value?.resetFields();
|
||||
emailForm.value = cloneDeep(defaulInviteForm);
|
||||
emailForm.value = cloneDeep(defaultInviteForm);
|
||||
}
|
||||
|
||||
function emailInvite() {
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
:action-config="tableBatchActions"
|
||||
v-on="propsEvent"
|
||||
@selected-change="handleTableSelect"
|
||||
@batch-action="handelTableBatch"
|
||||
@batch-action="handleTableBatch"
|
||||
>
|
||||
<template #organization="{ record }">
|
||||
<a-tooltip :content="record.organizationList.filter((e: any) => e).map((e: any) => e.name).join(',')">
|
||||
|
@ -246,41 +246,41 @@
|
|||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'system.user.tableColunmEmail',
|
||||
title: 'system.user.tableColumnEmail',
|
||||
dataIndex: 'email',
|
||||
width: 200,
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.user.tableColunmName',
|
||||
title: 'system.user.tableColumnName',
|
||||
dataIndex: 'name',
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.user.tableColunmPhone',
|
||||
title: 'system.user.tableColumnPhone',
|
||||
dataIndex: 'phone',
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.user.tableColunmOrg',
|
||||
title: 'system.user.tableColumnOrg',
|
||||
slotName: 'organization',
|
||||
dataIndex: 'organizationList',
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.user.tableColunmUsergroup',
|
||||
title: 'system.user.tableColumnUsergroup',
|
||||
slotName: 'userRole',
|
||||
dataIndex: 'userRoleList',
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.user.tableColunmStatus',
|
||||
title: 'system.user.tableColumnStatus',
|
||||
slotName: 'enable',
|
||||
dataIndex: 'enable',
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.user.tableColunmActions',
|
||||
title: 'system.user.tableColumnActions',
|
||||
slotName: 'action',
|
||||
fixed: 'right',
|
||||
width: 120,
|
||||
|
@ -570,7 +570,7 @@
|
|||
* 处理表格选中后批量操作
|
||||
* @param event 批量操作事件对象
|
||||
*/
|
||||
function handelTableBatch(event: BatchActionParams) {
|
||||
function handleTableBatch(event: BatchActionParams) {
|
||||
switch (event.eventTag) {
|
||||
case 'batchAddProject':
|
||||
case 'batchAddUsergroup':
|
||||
|
@ -623,7 +623,7 @@
|
|||
const loading = ref(false);
|
||||
const userFormMode = ref<UserModalMode>('create');
|
||||
const userFormRef = ref<FormInstance | null>(null);
|
||||
const defaulUserForm = {
|
||||
const defaultUserForm = {
|
||||
list: [
|
||||
{
|
||||
name: '',
|
||||
|
@ -633,7 +633,7 @@
|
|||
],
|
||||
userGroup: [],
|
||||
};
|
||||
const userForm = ref<UserForm>(cloneDeep(defaulUserForm));
|
||||
const userForm = ref<UserForm>(cloneDeep(defaultUserForm));
|
||||
const userGroupOptions = ref();
|
||||
|
||||
async function init() {
|
||||
|
@ -680,15 +680,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理用户表单弹窗关闭
|
||||
*/
|
||||
function handleUserModalClose() {
|
||||
if (userFormMode.value === 'edit') {
|
||||
resetUserForm();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验用户姓名
|
||||
* @param value 输入的值
|
||||
|
@ -840,6 +831,14 @@
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理用户表单弹窗关闭
|
||||
*/
|
||||
function handleUserModalClose() {
|
||||
resetUserForm();
|
||||
batchFormRef.value?.resetForm();
|
||||
}
|
||||
|
||||
const inviteVisible = ref(false);
|
||||
function showEmailInviteModal() {
|
||||
inviteVisible.value = true;
|
||||
|
@ -866,9 +865,6 @@
|
|||
importVisible.value = false;
|
||||
importResultVisible.value = false;
|
||||
userImportFile.value = [];
|
||||
if (importResult.value === 'success' || (importResult.value === 'fail' && importSuccessCount.value > 0)) {
|
||||
loadList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -879,12 +875,14 @@
|
|||
switch (importResult.value) {
|
||||
case 'success':
|
||||
importResultTitle.value = t('system.user.importSuccessTitle');
|
||||
loadList();
|
||||
break;
|
||||
case 'allFail':
|
||||
importResultTitle.value = t('system.user.importAllfailTitle');
|
||||
break;
|
||||
case 'fail':
|
||||
importResultTitle.value = t('system.user.importFailTitle');
|
||||
loadList();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
|
@ -99,11 +99,11 @@ export default {
|
|||
'system.user.batchAddOrgnization': 'Batch add to organization',
|
||||
'system.user.batchOptional': 'Optional',
|
||||
'system.user.batchChosen': 'Chosen',
|
||||
'system.user.tableColunmEmail': 'Email',
|
||||
'system.user.tableColunmName': 'Name',
|
||||
'system.user.tableColunmPhone': 'Phone',
|
||||
'system.user.tableColunmOrg': 'Orgnization',
|
||||
'system.user.tableColunmUsergroup': 'UserGroup',
|
||||
'system.user.tableColunmStatus': 'Status',
|
||||
'system.user.tableColunmActions': 'Actions',
|
||||
'system.user.tableColumnEmail': 'Email',
|
||||
'system.user.tableColumnName': 'Name',
|
||||
'system.user.tableColumnPhone': 'Phone',
|
||||
'system.user.tableColumnOrg': 'Orgnization',
|
||||
'system.user.tableColumnUsergroup': 'UserGroup',
|
||||
'system.user.tableColumnStatus': 'Status',
|
||||
'system.user.tableColumnActions': 'Actions',
|
||||
};
|
||||
|
|
|
@ -98,11 +98,11 @@ export default {
|
|||
'system.user.batchAddOrgnization': '批量添加至组织',
|
||||
'system.user.batchOptional': '可选',
|
||||
'system.user.batchChosen': '已选',
|
||||
'system.user.tableColunmEmail': '邮箱',
|
||||
'system.user.tableColunmName': '姓名',
|
||||
'system.user.tableColunmPhone': '手机',
|
||||
'system.user.tableColunmOrg': '组织',
|
||||
'system.user.tableColunmUsergroup': '用户组',
|
||||
'system.user.tableColunmStatus': '状态',
|
||||
'system.user.tableColunmActions': '操作',
|
||||
'system.user.tableColumnEmail': '邮箱',
|
||||
'system.user.tableColumnName': '姓名',
|
||||
'system.user.tableColumnPhone': '手机',
|
||||
'system.user.tableColumnOrg': '组织',
|
||||
'system.user.tableColumnUsergroup': '用户组',
|
||||
'system.user.tableColumnStatus': '状态',
|
||||
'system.user.tableColumnActions': '操作',
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue