feat(系统日志): 单词拼写调整&系统日志页面&部分组件调整&MS 级联选择组件

This commit is contained in:
baiqi 2023-08-11 18:01:56 +08:00 committed by fit2-zhao
parent 914397be18
commit 9b4462e948
39 changed files with 833 additions and 177 deletions

View File

@ -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

View File

@ -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: '默认项目',
},
],
};
}

View File

@ -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 });
}

View File

@ -0,0 +1,2 @@
export const GetLogListUrl = '/operation/log/list';
export const GetLogOptionstUrl = '/operation/log/get/options';

View File

@ -613,3 +613,10 @@
.arco-collapse-item-header-title {
font-weight: 500;
}
/** 标签 **/
.arco-tag {
.arco-icon {
font-size: 14px;
}
}

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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);
}
},
{

View File

@ -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>

View File

@ -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}

View File

@ -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() {

View File

@ -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,

View File

@ -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,
};

View File

@ -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 =

View File

@ -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',

View File

@ -3,7 +3,7 @@ export enum UploadAcceptEnum {
word = '.docx,.doc',
pdf = '.pdf',
txt = '.txt',
vedio = '.mp4',
video = '.mp4',
sql = '.sql',
csv = '.csv',
zip = '.zip',

View File

@ -1,5 +1,5 @@
// 请求返回结构
export default interface CommonReponse<T> {
export default interface CommonResponse<T> {
code: number;
message: string;
messageDetail: string;

View File

@ -0,0 +1,9 @@
export interface OptionsItem {
id: string;
name: string;
}
export interface LogOptions {
organizationList: OptionsItem[];
projectList: OptionsItem[];
}

View File

@ -71,7 +71,7 @@ export interface CreateUserParams {
userInfoList: SimpleUserInfo[];
userRoleIdList: string[];
}
export interface UpdateUserStausParams {
export interface UpdateUserStatusParams {
userIdList: string[];
enable: boolean;
}

View File

@ -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,
},

View File

@ -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,

View File

@ -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;
}

View File

@ -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;
}
/**

View File

@ -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() {

View File

@ -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',

View File

@ -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': '请输入帮助文档地址',

View File

@ -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>

View File

@ -0,0 +1 @@
export default {};

View File

@ -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': '登出',
};

View File

@ -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];
// ipportmonitorconcurrentNumber
if (Object.values(node).every((e) => e !== '')) {

View File

@ -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,

View File

@ -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',

View File

@ -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': '禁用后,正在使用该资源池的测试会停止执行',

View File

@ -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: {}

View File

@ -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);
}
},
{

View File

@ -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() {

View File

@ -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;

View File

@ -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',
};

View File

@ -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': '操作',
};