feat(全局): 命名错误调整&搜索选择器组件&系统/组织/项目日志&部分组件调整

This commit is contained in:
baiqi 2023-08-22 10:44:35 +08:00 committed by f2c-ci-robot[bot]
parent 2e689901e2
commit e4d6864afd
63 changed files with 1084 additions and 403 deletions

View File

@ -486,7 +486,7 @@ export enum ContentTypeEnum {
## -locale
国际化模块,存放项目声明的国际化配置,按语种划分模块。模块入口文件为`index.ts`,负责定义菜单、导航栏等公共的国际化配置,其他的按系统功能声明并导入`index.ts`,还有项目内页面组件的国际化配置也需要导入至`index.ts`,页面组件的国际化配置在页面的目录下声明,如:`views/dashboard/workplace/locale`。
国际化模块,存放项目声明的国际化配置,按语种划分模块。模块入口文件为`index.ts`,负责定义菜单、导航栏等公共的国际化配置,其他的按系统功能声明并导入`index.ts`,还有项目内页面组件的国际化配置也需要导入至`index.ts`,页面组件的国际化配置在页面的目录下声明,如:`views/dashboard/workbench/locale`。
```typescript
import { createI18n } from 'vue-i18n';

View File

@ -1,8 +1,8 @@
import MSR from '@/api/http/index';
import { GetLogListUrl, GetLogOptionstUrl } from '@/api/requrls/setting/log';
import { GetLogListUrl, GetLogOptionsUrl, GetLogUserUrl } from '@/api/requrls/setting/log';
import type { CommonList } from '@/models/common';
import type { LogOptions, LogItem } from '@/models/setting/log';
import type { LogOptions, LogItem, UserItem } from '@/models/setting/log';
// 获取日志列表
export function getLogList(data: any) {
@ -11,5 +11,10 @@ export function getLogList(data: any) {
// 获取日志操作范围选项
export function getLogOptions() {
return MSR.get<LogOptions>({ url: GetLogOptionstUrl });
return MSR.get<LogOptions>({ url: GetLogOptionsUrl });
}
// 获取日志操作用户列表
export function getLogUsers() {
return MSR.get<UserItem[]>({ url: GetLogUserUrl });
}

View File

@ -1,2 +1,3 @@
export const GetLogListUrl = '/operation/log/list';
export const GetLogOptionstUrl = '/operation/log/get/options';
export const GetLogOptionsUrl = '/operation/log/get/options'; // 获取组织/项目级联下拉框选项
export const GetLogUserUrl = '/operation/log/user/list'; // 搜索操作用户

View File

@ -89,6 +89,7 @@
multiple?: boolean; //
strictly?: boolean; //
virtualListProps?: VirtualListProps; //
panelWidth?: number; // 150px
placeholder?: string;
loading?: boolean;
}>(),
@ -167,7 +168,10 @@
const getOptionComputedStyle = computed(() => {
// 80px
return {
width: cascaderDeep.value <= 2 ? `${cascaderWidth.value / cascaderDeep.value - 80}px` : '150px',
width:
cascaderDeep.value <= 2
? `${cascaderWidth.value / cascaderDeep.value - 80 - cascaderDeep.value * 4}px`
: `${props.panelWidth}px` || '150px',
};
});
@ -248,6 +252,10 @@
.arco-virtual-list {
.ms-scroll-bar();
}
.arco-cascader-option {
margin: 2px 6px;
border-radius: 4px;
}
}
.arco-cascader-panel-column:first-child {
.arco-checkbox {

View File

@ -0,0 +1,96 @@
import { watch, ref, h, defineComponent } from 'vue';
import { debounce } from 'lodash-es';
import { useI18n } from '@/hooks/useI18n';
import type { SelectOptionData } from '@arco-design/web-vue';
export type ModelType = string | number | Record<string, any> | (string | number | Record<string, any>)[];
export interface MsSearchSelectProps {
modelValue: ModelType;
allowClear?: boolean;
placeholder?: string;
prefix?: string;
searchKeys: string[]; // 需要搜索的 key 名,关键字会遍历这个 key 数组,然后取 item[key] 进行模糊匹配
options: SelectOptionData[];
optionLabelRender?: (item: SelectOptionData) => string; // 自定义 option 的 label 渲染,返回一个 html 字符串,默认使用 item.label
}
export default defineComponent(
(props: MsSearchSelectProps, { emit }) => {
const { t } = useI18n();
const innerValue = ref(props.modelValue);
const filterOptions = ref<SelectOptionData[]>([...props.options]);
watch(
() => props.modelValue,
(val) => {
innerValue.value = val;
}
);
watch(
() => props.options,
(arr) => {
filterOptions.value = [...arr];
}
);
function handleUserSearch(val: string) {
if (val.trim() === '') {
filterOptions.value = [...props.options];
return;
}
const highlightedKeyword = `<span class="text-[rgb(var(--primary-4))]">${val}</span>`;
filterOptions.value = props.options
.map((e) => {
const item = { ...e };
let hasMatch = false;
for (let i = 0; i < props.searchKeys.length; i++) {
// 遍历传入的搜索字段
const key = props.searchKeys[i];
if (e[key].includes(val)) {
// 是否匹配
hasMatch = true;
item[key] = e[key].replace(new RegExp(val, 'gi'), highlightedKeyword); // 高亮关键字替换
}
}
if (hasMatch) {
return item;
}
return null;
})
.filter((e) => e) as SelectOptionData[];
}
return () => (
<a-select
default-value={innerValue}
placeholder={t(props.placeholder || '')}
allow-clear={props.allowClear}
allow-search
filter-option={false}
onUpdate:model-value={(value: ModelType) => emit('update:modelValue', value)}
onInputValueChange={debounce(handleUserSearch, 300)}
>
{{
prefix: () => t(props.prefix || ''),
default: () =>
filterOptions.value.map((item) => (
<a-option key={item.id} value={item.value}>
{typeof props.optionLabelRender === 'function'
? h('div', { innerHTML: props.optionLabelRender(item) })
: item.label}
</a-option>
)),
}}
</a-select>
);
},
{
// eslint-disable-next-line vue/require-prop-types
props: ['modelValue', 'allowClear', 'placeholder', 'prefix', 'searchKeys', 'options', 'optionLabelRender'],
emits: ['update:modelValue'],
}
);

View File

@ -14,7 +14,7 @@
<script setup lang="ts">
import { Ref, ref, watch } from 'vue';
import { useRouter, RouteRecordRaw, RouteRecordNormalized, RouteRecordName } from 'vue-router';
import { useRouter, RouteRecordRaw, RouteRecordName } from 'vue-router';
import { cloneDeep } from 'lodash-es';
import { useAppStore } from '@/store';
import { listenerRouteChange } from '@/utils/route-listener';
@ -22,7 +22,7 @@
import appClientMenus from '@/router/app-menus';
import { useI18n } from '@/hooks/useI18n';
const copyRouter = cloneDeep(appClientMenus) as RouteRecordNormalized[];
const copyRouters = cloneDeep(appClientMenus) as RouteRecordRaw[];
const permission = usePermission();
const appStore = useAppStore();
const router = useRouter();
@ -41,13 +41,13 @@
function setCurrentTopMenu(key: string) {
//
const secParentFullSame = appStore.topMenus.find((el: RouteRecordRaw) => {
return key === el?.name;
const secParentFullSame = appStore.topMenus.find((route: RouteRecordRaw) => {
return key === route?.name;
});
//
const secParentLike = appStore.topMenus.find((el: RouteRecordRaw) => {
return key.includes(el?.name as string);
const secParentLike = appStore.topMenus.find((route: RouteRecordRaw) => {
return key.includes(route?.name as string);
});
if (secParentFullSame) {
@ -58,22 +58,35 @@
}
/**
* 监听路由变化存储打开的三级子路由
* 监听路由变化存储打开的顶部菜单
*/
listenerRouteChange((newRoute) => {
const { name } = newRoute;
copyRouter.forEach((el: RouteRecordRaw) => {
for (let i = 0; i < copyRouters.length; i++) {
const firstRoute = copyRouters[i];
//
if (permission.accessRouter(el)) {
if (name && (name as string).includes((el?.name as string) || '')) {
const currentParent = el?.children?.find(
(item) => name && (name as string).includes((item?.name as string) || '')
if (permission.accessRouter(firstRoute)) {
if (name && firstRoute?.name && (name as string).includes(firstRoute.name as string)) {
//
let currentParent = firstRoute?.children?.some((item) => item.meta?.isTopMenu)
? (firstRoute as RouteRecordRaw)
: undefined;
if (!currentParent) {
//
currentParent = firstRoute?.children?.find(
(item) => name && item?.name && (name as string).includes(item.name as string)
);
}
appStore.setTopMenus(currentParent?.children?.filter((item) => item.meta?.isTopMenu));
setCurrentTopMenu(name as string);
return;
}
//
appStore.setTopMenus([]);
setCurrentTopMenu('');
}
}
});
}, true);
function jumpPath(route: RouteRecordName | undefined) {

View File

@ -1,19 +1,5 @@
<template>
<a-timeline v-if="mode === 'static'" class="ms-timeline">
<a-timeline-item
v-for="item of props.list"
:key="item.id || item.label"
:dot-color="item.dotColor || 'var(--color-text-input-border)'"
:label="item.label"
:line-type="item.lineType"
>
<slot name="content">
<span class="timeline-text">{{ item.time }}</span>
</slot>
</a-timeline-item>
</a-timeline>
<a-timeline
v-else
:class="[
'ms-timeline',
isArrivedTop ? 'ms-timeline--hidden-top-shadow' : '',
@ -28,7 +14,7 @@
@reach-bottom="handleReachBottom"
>
<template #item="{ item, index }">
<div>
<div :class="index === 0 ? 'pt-[12px]' : ''">
<a-list-item :key="item.id">
<a-timeline-item :dot-color="item.dotColor || 'var(--color-text-input-border)'" :line-type="item.lineType">
<slot name="time" :item="item">
@ -39,7 +25,10 @@
</slot>
</a-timeline-item>
</a-list-item>
<div v-if="index === props.list.length - 1" class="flex h-[32px] items-center justify-center">
<div
v-if="props.mode === 'remote' && index === props.list.length - 1"
class="flex h-[32px] items-center justify-center"
>
<div v-if="noMoreData" class="text-[var(--color-text-4)]">{{ t('ms.timeline.noMoreData') }}</div>
<a-spin v-else />
</div>
@ -108,7 +97,7 @@
});
function handleReachBottom() {
if (!props.noMoreData && props.list.length > 0) {
if (props.mode === 'remote' && !props.noMoreData && props.list.length > 0) {
emit('reachBottom');
}
}
@ -147,6 +136,10 @@
.timeline-text {
color: @color-text-5;
}
.ms-timeline-content {
@apply overflow-auto;
.ms-scroll-bar();
}
}
.ms-timeline--hidden-top-shadow {
box-shadow: inset 0 -10px 6px -10px rgb(0 0 0 / 15%);

View File

@ -185,7 +185,7 @@
import { LOCALE_OPTIONS } from '@/locale';
import useLocale from '@/locale/useLocale';
// import useUser from '@/hooks/useUser';
import TopMenu from '@/components/bussiness/ms-top-menu/index.vue';
import TopMenu from '@/components/business/ms-top-menu/index.vue';
import MessageBox from '../message-box/index.vue';
import { NOT_SHOW_PROJECT_SELECT_MODULE } from '@/router/constants';
// import { getProjectList } from '@/api/modules/setting/project';

View File

@ -0,0 +1,175 @@
import { RouteEnum } from '@/enums/routeEnum';
import { TreeNode, mapTree } from '@/utils';
export const MENU_LEVEL = ['SYSTEM', 'ORGANIZATION', 'PROJECT'] as const; // 菜单级别
/**
* tab tab
* key key
* locale key
* route name
* permission key
* level
* children /tab集合
*/
export const pathMap = [
{
key: 'SETTING', // 系统设置
locale: 'menu.settings',
route: RouteEnum.SETTING,
permission: [],
level: MENU_LEVEL[0],
children: [
{
key: 'SETTING_SYSTEM', // 系统设置-系统
locale: 'menu.settings.system',
route: RouteEnum.SETTING_SYSTEM,
permission: [],
level: MENU_LEVEL[0],
children: [
{
key: 'SETTING_SYSTEM_USER', // 系统设置-系统-用户
locale: 'menu.settings.system.user',
route: RouteEnum.SETTING_SYSTEM_USER,
permission: [],
level: MENU_LEVEL[0],
},
{
key: 'SETTING_SYSTEM_USER_GROUP', // 系统设置-系统-用户组
locale: 'menu.settings.system.usergroup',
route: RouteEnum.SETTING_SYSTEM_USER_GROUP,
permission: [],
level: MENU_LEVEL[0],
},
{
key: 'SETTING_SYSTEM_ORGANIZATION', // 系统设置-系统-组织与项目
locale: 'menu.settings.system.organizationAndProject',
route: RouteEnum.SETTING_SYSTEM_ORGANIZATION,
permission: [],
level: MENU_LEVEL[0],
},
{
key: 'SETTING_SYSTEM_PARAMETER', // 系统设置-系统-系统参数
locale: 'menu.settings.system.parameter',
route: RouteEnum.SETTING_SYSTEM_PARAMETER,
permission: [],
level: MENU_LEVEL[0],
children: [
{
key: 'SETTING_SYSTEM_PARAMETER', // 系统设置-系统-系统参数-基础设置
locale: 'system.config.baseConfig',
route: RouteEnum.SETTING_SYSTEM_PARAMETER,
level: MENU_LEVEL[0],
},
{
key: 'SETTING_SYSTEM_PARAMETER_PAGE_CONFIG', // 系统设置-系统-系统参数-界面设置
locale: 'system.config.pageConfig',
route: RouteEnum.SETTING_SYSTEM_PARAMETER,
level: MENU_LEVEL[0],
},
{
key: 'SETTING_SYSTEM_PARAMETER_AUTH_CONFIG', // 系统设置-系统-系统参数-认证设置
locale: 'system.config.authConfig',
route: RouteEnum.SETTING_SYSTEM_PARAMETER,
level: MENU_LEVEL[0],
},
],
},
{
key: 'SETTING_SYSTEM_RESOURCE_POOL', // 系统设置-系统-资源池
locale: 'menu.settings.system.resourcePool',
route: RouteEnum.SETTING_SYSTEM_RESOURCE_POOL,
permission: [],
level: MENU_LEVEL[0],
},
{
key: 'SETTING_SYSTEM_AUTHORIZED_MANAGEMENT', // 系统设置-系统-授权管理
locale: 'menu.settings.system.authorizedManagement',
route: RouteEnum.SETTING_SYSTEM_AUTHORIZED_MANAGEMENT,
permission: [],
level: MENU_LEVEL[0],
},
{
key: 'SETTING_SYSTEM_LOG', // 系统设置-系统-日志
locale: 'menu.settings.system.log',
route: RouteEnum.SETTING_SYSTEM_LOG,
permission: [],
level: MENU_LEVEL[0],
},
{
key: 'SETTING_SYSTEM_PLUGIN_MANAGEMENT', // 系统设置-系统-插件管理
locale: 'menu.settings.system.pluginManager',
route: RouteEnum.SETTING_SYSTEM_PLUGIN_MANAGEMENT,
permission: [],
level: MENU_LEVEL[0],
},
],
},
{
key: 'SETTING_ORGANIZATION', // 系统设置-组织
locale: 'menu.settings.organization',
route: RouteEnum.SETTING_ORGANIZATION,
permission: [],
level: MENU_LEVEL[1],
children: [
{
key: 'SETTING_ORGANIZATION_MEMBER', // 系统设置-组织-成员
locale: 'menu.settings.organization.member',
route: RouteEnum.SETTING_ORGANIZATION_MEMBER,
permission: [],
level: MENU_LEVEL[1],
},
{
key: 'SETTING_ORGANIZATION_SERVICE', // 系统设置-组织-服务集成
locale: 'menu.settings.organization.serviceIntegration',
route: RouteEnum.SETTING_ORGANIZATION_SERVICE,
permission: [],
level: MENU_LEVEL[1],
},
],
},
],
},
{
key: 'PROJECT_MANAGEMENT', // 项目管理
locale: 'menu.projectManagement',
route: RouteEnum.PROJECT_MANAGEMENT,
permission: [],
level: MENU_LEVEL[2],
children: [
{
key: 'PROJECT_MANAGEMENT_LOG', // 项目管理-日志
locale: 'menu.projectManagement.log',
route: RouteEnum.PROJECT_MANAGEMENT_LOG,
permission: [],
level: MENU_LEVEL[2],
},
],
},
];
/**
*
* @param level
* @param customNodeFn
* @returns
*/
export const getPathMapByLevel = <T>(
level: (typeof MENU_LEVEL)[number],
customNodeFn: (node: TreeNode<T>) => TreeNode<T> | null = (node) => node
) => {
return mapTree(pathMap, (e) => {
let isValid = true; // 默认是系统级别
if (level === MENU_LEVEL[1]) {
// 组织级别只展示组织、项目
isValid = e.level !== MENU_LEVEL[0];
} else if (level === MENU_LEVEL[2]) {
// 项目级别只展示项目
isValid = e.level !== MENU_LEVEL[0] && e.level !== MENU_LEVEL[1];
}
if (isValid) {
return typeof customNodeFn === 'function' ? customNodeFn(e) : e;
}
return null;
});
};

View File

@ -1,13 +0,0 @@
enum menuEnum {
APITEST = 'apiTest',
BUGMANAGEMENT = 'bugManagement',
FEATURETEST = 'featureTest',
PERFORMANCE_TEST = 'performanceTest',
PROJECTMANAGEMENT = 'projectManagement',
SETTING = 'setting',
TESTPLAN = 'testPlan',
UITEST = 'uiTest',
WORKPLACE = 'workplace',
}
export default menuEnum;

View File

@ -0,0 +1,63 @@
export enum ApiTestRouteEnum {
API_TEST = 'apiTest',
}
export enum BugManagementRouteEnum {
BUG_MANAGEMENT = 'bugManagement',
}
export enum FeatureTestRouteEnum {
FEATURE_TEST = 'featureTest',
}
export enum PerformanceTestRouteEnum {
PERFORMANCE_TEST = 'performanceTest',
}
export enum ProjectManagementRouteEnum {
PROJECT_MANAGEMENT = 'projectManagement',
PROJECT_MANAGEMENT_INDEX = 'projectManagementIndex',
PROJECT_MANAGEMENT_LOG = 'projectManagementLog',
}
export enum TestPlanRouteEnum {
TEST_PLAN = 'testPlan',
}
export enum UITestRouteEnum {
UI_TEST = 'uiTest',
}
export enum WorkbenchRouteEnum {
WORKBENCH = 'workbench',
}
export enum SettingRouteEnum {
SETTING = 'setting',
SETTING_SYSTEM = 'settingSystem',
SETTING_SYSTEM_USER = 'settingSystemUser',
SETTING_SYSTEM_USER_GROUP = 'settingSystemUserGroup',
SETTING_SYSTEM_ORGANIZATION = 'settingSystemOrganization',
SETTING_SYSTEM_PARAMETER = 'settingSystemParameter',
SETTING_SYSTEM_RESOURCE_POOL = 'settingSystemResourcePool',
SETTING_SYSTEM_RESOURCE_POOL_DETAIL = 'settingSystemResourcePoolDetail',
SETTING_SYSTEM_AUTHORIZED_MANAGEMENT = 'settingSystemAuthorizedManagement',
SETTING_SYSTEM_LOG = 'settingSystemLog',
SETTING_SYSTEM_PLUGIN_MANAGEMENT = 'settingSystemPluginManagement',
SETTING_ORGANIZATION = 'settingOrganization',
SETTING_ORGANIZATION_MEMBER = 'settingOrganizationMember',
SETTING_ORGANIZATION_SERVICE = 'settingOrganizationService',
SETTING_ORGANIZATION_LOG = 'settingOrganizationLog',
}
export const RouteEnum = {
...ApiTestRouteEnum,
...SettingRouteEnum,
...BugManagementRouteEnum,
...FeatureTestRouteEnum,
...PerformanceTestRouteEnum,
...ProjectManagementRouteEnum,
...TestPlanRouteEnum,
...UITestRouteEnum,
...WorkbenchRouteEnum,
};

View File

@ -64,7 +64,7 @@
import Footer from '@/components/pure/footer/index.vue';
import usePermission from '@/hooks/usePermission';
import PageLayout from './page-layout.vue';
import MsBreadCrumb from '@/components/bussiness/ms-breadcrumb/index.vue';
import MsBreadCrumb from '@/components/business/ms-breadcrumb/index.vue';
import { GetTitleImgUrl } from '@/api/requrls/setting/config';
interface Props {

View File

@ -19,7 +19,7 @@ Object.keys(_Vmodules).forEach((key) => {
export default {
message: {
'menu.workplace': 'Workplace',
'menu.workbench': 'Workbench',
'menu.testPlan': 'Test plan',
'menu.bugManagement': 'Bug management',
'menu.featureTest': 'Feature test',
@ -27,21 +27,23 @@ export default {
'menu.uiTest': 'UI test',
'menu.performanceTest': 'Performance test',
'menu.projectManagement': 'Project management',
'menu.projectManagement.log': 'Project Log',
'menu.settings': 'Settings',
'menu.settings.system': 'System',
'menu.settings.organization': 'Organization',
'menu.settings.organization.member': 'Member',
'menu.settings.organization.serviceIntegration': 'Service Integration',
'menu.settings.system.usergroup': 'User Group',
'menu.settings.system.authorizedManagement': 'Authorized Management',
'menu.settings.system.pluginmanger': 'Plugin Manger',
'menu.settings.system.pluginManager': 'Plugin Manger',
'menu.settings.system.user': 'User',
'menu.settings.system.organizationAndProject': 'Org & Project',
'menu.settings.system.resourcePool': 'Resource Pool',
'menu.settings.system.resourcePoolDetail': 'Add resource pool',
'menu.settings.system.resourcePoolEdit': 'Edit resource pool',
'menu.settings.system.parameter': 'System parameter',
'menu.settings.system.log': 'Log',
'menu.settings.system.log': 'System Log',
'menu.settings.organization': 'Organization',
'menu.settings.organization.member': 'Member',
'menu.settings.organization.serviceIntegration': 'Service Integration',
'menu.settings.organization.log': 'Org Log',
'navbar.action.locale': 'Switch to English',
...sys,
...localeSettings,

View File

@ -18,7 +18,7 @@ Object.keys(_Vmodules).forEach((key) => {
});
export default {
message: {
'menu.workplace': '工作台',
'menu.workbench': '工作台',
'menu.testPlan': '测试计划',
'menu.bugManagement': '缺陷管理',
'menu.featureTest': '功能测试',
@ -26,21 +26,23 @@ export default {
'menu.uiTest': 'UI测试',
'menu.performanceTest': '性能测试',
'menu.projectManagement': '项目管理',
'menu.projectManagement.log': '项目日志',
'menu.settings': '系统设置',
'menu.settings.system': '系统',
'menu.settings.organization': '组织',
'menu.settings.organization.member': '成员',
'menu.settings.organization.serviceIntegration': '服务集成',
'menu.settings.system.user': '用户',
'menu.settings.system.usergroup': '用户组',
'menu.settings.system.authorizedManagement': '授权管理',
'menu.settings.system.pluginmanger': '插件管理',
'menu.settings.system.pluginManager': '插件管理',
'menu.settings.system.organizationAndProject': '组织与项目',
'menu.settings.system.resourcePool': '资源池',
'menu.settings.system.resourcePoolDetail': '添加资源池',
'menu.settings.system.resourcePoolEdit': '编辑资源池',
'menu.settings.system.parameter': '系统参数',
'menu.settings.system.log': '日志',
'menu.settings.system.log': '系统日志',
'menu.settings.organization': '组织',
'menu.settings.organization.member': '成员',
'menu.settings.organization.serviceIntegration': '服务集成',
'menu.settings.organization.log': '组织日志',
'navbar.action.locale': '切换为中文',
...sys,
...localeSettings,

View File

@ -11,7 +11,7 @@ export interface LogOptions {
export interface LogItem {
id: string;
createUser: string;
userName: string;
userName: string; // 操作人
projectId: string;
projectName: string;
organizationId: string;
@ -22,3 +22,21 @@ export interface LogItem {
createTime: number;
sourceId: string;
}
export interface UserItem {
id: string;
name: string;
email: string;
password: string;
enable: boolean;
createTime: number;
updateTime: number;
language: string;
lastOrganizationId: string;
phone: string;
source: string;
lastProjectId: string;
createUser: string;
updateUser: string;
deleted: boolean;
}

View File

@ -17,13 +17,13 @@ export const NOT_FOUND = {
export const REDIRECT_ROUTE_NAME = 'Redirect';
// 首页路由
export const DEFAULT_ROUTE_NAME = 'Workplace';
export const DEFAULT_ROUTE_NAME = 'Workbench';
// 默认 tab-bar 路,多页签模式下,打开的第一个页面
export const DEFAULT_ROUTE = {
title: 'menu.dashboard.workplace',
title: 'menu.dashboard.workbench',
name: DEFAULT_ROUTE_NAME,
fullPath: '/dashboard/workplace',
fullPath: '/dashboard/workbench',
};
// 不需要显示项目选择器的模块数组项为一级路由的path

View File

@ -1,10 +1,11 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
import menuEnum from '@/enums/menuEnum';
import { ApiTestRouteEnum } from '@/enums/routeEnum';
import type { AppRouteRecordRaw } from '../types';
const ApiTest: AppRouteRecordRaw = {
path: '/api-test',
name: menuEnum.APITEST,
name: ApiTestRouteEnum.API_TEST,
redirect: '/api-test/index',
component: DEFAULT_LAYOUT,
meta: {

View File

@ -1,10 +1,11 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
import menuEnum from '@/enums/menuEnum';
import { BugManagementRouteEnum } from '@/enums/routeEnum';
import type { AppRouteRecordRaw } from '../types';
const BugManagement: AppRouteRecordRaw = {
path: '/bug-management',
name: menuEnum.BUGMANAGEMENT,
name: BugManagementRouteEnum.BUG_MANAGEMENT,
redirect: '/bug-management/index',
component: DEFAULT_LAYOUT,
meta: {

View File

@ -1,10 +1,11 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
import menuEnum from '@/enums/menuEnum';
import { FeatureTestRouteEnum } from '@/enums/routeEnum';
import type { AppRouteRecordRaw } from '../types';
const FeatureTest: AppRouteRecordRaw = {
path: '/feature-test',
name: menuEnum.FEATURETEST,
name: FeatureTestRouteEnum.FEATURE_TEST,
redirect: '/feature-test/index',
component: DEFAULT_LAYOUT,
meta: {

View File

@ -1,10 +1,11 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
import menuEnum from '@/enums/menuEnum';
import { PerformanceTestRouteEnum } from '@/enums/routeEnum';
import type { AppRouteRecordRaw } from '../types';
const PerformanceTest: AppRouteRecordRaw = {
path: '/performance-test',
name: menuEnum.PERFORMANCE_TEST,
name: PerformanceTestRouteEnum.PERFORMANCE_TEST,
redirect: '/performance-test/index',
component: DEFAULT_LAYOUT,
meta: {

View File

@ -1,10 +1,11 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
import menuEnum from '@/enums/menuEnum';
import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
import type { AppRouteRecordRaw } from '../types';
const ProjectManagement: AppRouteRecordRaw = {
path: '/project-management',
name: menuEnum.PROJECTMANAGEMENT,
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT,
redirect: '/project-management/index',
component: DEFAULT_LAYOUT,
meta: {
@ -16,12 +17,22 @@ const ProjectManagement: AppRouteRecordRaw = {
children: [
{
path: 'index',
name: 'ProjectManagementIndex',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_INDEX,
component: () => import('@/views/project-management/index.vue'),
meta: {
roles: ['*'],
},
},
{
path: 'log',
name: ProjectManagementRouteEnum.PROJECT_MANAGEMENT_LOG,
component: () => import('@/views/project-management/log/index.vue'),
meta: {
locale: 'menu.projectManagement.log',
roles: ['*'],
isTopMenu: true,
},
},
],
};

View File

@ -1,9 +1,11 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
import { SettingRouteEnum } from '@/enums/routeEnum';
import type { AppRouteRecordRaw } from '../types';
const Setting: AppRouteRecordRaw = {
path: '/setting',
name: 'setting',
name: SettingRouteEnum.SETTING,
component: DEFAULT_LAYOUT,
meta: {
locale: 'menu.settings',
@ -13,7 +15,7 @@ const Setting: AppRouteRecordRaw = {
children: [
{
path: 'system',
name: 'settingSystem',
name: SettingRouteEnum.SETTING_SYSTEM,
redirect: '/setting/system/user',
component: null,
meta: {
@ -24,7 +26,7 @@ const Setting: AppRouteRecordRaw = {
children: [
{
path: 'user',
name: 'settingSystemUser',
name: SettingRouteEnum.SETTING_SYSTEM_USER,
component: () => import('@/views/setting/system/user/index.vue'),
meta: {
locale: 'menu.settings.system.user',
@ -34,7 +36,7 @@ const Setting: AppRouteRecordRaw = {
},
{
path: 'usergroup',
name: 'settingSystemUsergroup',
name: SettingRouteEnum.SETTING_SYSTEM_USER_GROUP,
component: () => import('@/views/setting/system/usergroup/index.vue'),
meta: {
locale: 'menu.settings.system.usergroup',
@ -44,7 +46,7 @@ const Setting: AppRouteRecordRaw = {
},
{
path: 'organization-and-project',
name: 'settingSystemOrganization',
name: SettingRouteEnum.SETTING_SYSTEM_ORGANIZATION,
component: () => import('@/views/setting/system/organizationAndProject/index.vue'),
meta: {
locale: 'menu.settings.system.organizationAndProject',
@ -54,7 +56,7 @@ const Setting: AppRouteRecordRaw = {
},
{
path: 'parameter',
name: 'settingSystemParameter',
name: SettingRouteEnum.SETTING_SYSTEM_PARAMETER,
component: () => import('@/views/setting/system/config/index.vue'),
meta: {
locale: 'menu.settings.system.parameter',
@ -64,34 +66,28 @@ const Setting: AppRouteRecordRaw = {
},
{
path: 'resourcePool',
name: 'settingSystemResourcePool',
name: SettingRouteEnum.SETTING_SYSTEM_RESOURCE_POOL,
component: () => import('@/views/setting/system/resourcePool/index.vue'),
meta: {
locale: 'menu.settings.system.resourcePool',
roles: ['*'],
isTopMenu: true,
breadcrumbs: [
{
name: 'settingSystemResourcePool',
locale: 'menu.settings.system.resourcePool',
},
],
},
},
{
path: 'resourcePoolDetail',
name: 'settingSystemResourcePoolDetail',
name: SettingRouteEnum.SETTING_SYSTEM_RESOURCE_POOL_DETAIL,
component: () => import('@/views/setting/system/resourcePool/detail.vue'),
meta: {
locale: 'menu.settings.system.resourcePoolDetail',
roles: ['*'],
breadcrumbs: [
{
name: 'settingSystemResourcePool',
name: SettingRouteEnum.SETTING_SYSTEM_RESOURCE_POOL,
locale: 'menu.settings.system.resourcePool',
},
{
name: 'settingSystemResourcePoolDetail',
name: SettingRouteEnum.SETTING_SYSTEM_RESOURCE_POOL_DETAIL,
locale: 'menu.settings.system.resourcePoolDetail',
editTag: 'id',
editLocale: 'menu.settings.system.resourcePoolEdit',
@ -101,7 +97,7 @@ const Setting: AppRouteRecordRaw = {
},
{
path: 'authorizedmanagement',
name: 'settingSystemAuthorizedManagement',
name: SettingRouteEnum.SETTING_SYSTEM_AUTHORIZED_MANAGEMENT,
component: () => import('@/views/setting/system/authorizedManagement/index.vue'),
meta: {
locale: 'menu.settings.system.authorizedManagement',
@ -111,7 +107,7 @@ const Setting: AppRouteRecordRaw = {
},
{
path: 'log',
name: 'settingSystemLog',
name: SettingRouteEnum.SETTING_SYSTEM_LOG,
component: () => import('@/views/setting/system/log/index.vue'),
meta: {
locale: 'menu.settings.system.log',
@ -120,11 +116,11 @@ const Setting: AppRouteRecordRaw = {
},
},
{
path: 'pluginmanger',
name: 'settingSystemPluginManger',
path: 'pluginManager',
name: SettingRouteEnum.SETTING_SYSTEM_PLUGIN_MANAGEMENT,
component: () => import('@/views/setting/system/pluginManager/index.vue'),
meta: {
locale: 'menu.settings.system.pluginmanger',
locale: 'menu.settings.system.pluginManager',
roles: ['*'],
isTopMenu: true,
},
@ -133,7 +129,7 @@ const Setting: AppRouteRecordRaw = {
},
{
path: 'organization',
name: 'settingOrganization',
name: SettingRouteEnum.SETTING_ORGANIZATION,
redirect: '/setting/organization/member',
component: null,
meta: {
@ -144,7 +140,7 @@ const Setting: AppRouteRecordRaw = {
children: [
{
path: 'member',
name: 'settingOrganizationMember',
name: SettingRouteEnum.SETTING_ORGANIZATION_MEMBER,
component: () => import('@/views/setting/organization/member/index.vue'),
meta: {
locale: 'menu.settings.organization.member',
@ -154,7 +150,7 @@ const Setting: AppRouteRecordRaw = {
},
{
path: 'serviceIntegration',
name: 'settingOrganizationService',
name: SettingRouteEnum.SETTING_ORGANIZATION_SERVICE,
component: () => import('@/views/setting/organization/serviceIntegration/index.vue'),
meta: {
locale: 'menu.settings.organization.serviceIntegration',
@ -162,6 +158,16 @@ const Setting: AppRouteRecordRaw = {
isTopMenu: true,
},
},
{
path: 'log',
name: SettingRouteEnum.SETTING_ORGANIZATION_LOG,
component: () => import('@/views/setting/organization/log/index.vue'),
meta: {
locale: 'menu.settings.organization.log',
roles: ['*'],
isTopMenu: true,
},
},
],
},
],

View File

@ -1,10 +1,11 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
import menuEnum from '@/enums/menuEnum';
import { TestPlanRouteEnum } from '@/enums/routeEnum';
import type { AppRouteRecordRaw } from '../types';
const TestPlan: AppRouteRecordRaw = {
path: '/test-plan',
name: menuEnum.TESTPLAN,
name: TestPlanRouteEnum.TEST_PLAN,
redirect: '/test-plan/index',
component: DEFAULT_LAYOUT,
meta: {

View File

@ -1,10 +1,11 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
import menuEnum from '@/enums/menuEnum';
import { UITestRouteEnum } from '@/enums/routeEnum';
import type { AppRouteRecordRaw } from '../types';
const UiTest: AppRouteRecordRaw = {
path: '/ui-test',
name: menuEnum.UITEST,
name: UITestRouteEnum.UI_TEST,
redirect: '/ui-test/index',
component: DEFAULT_LAYOUT,
meta: {

View File

@ -0,0 +1,29 @@
import { DEFAULT_LAYOUT } from '../base';
import { WorkbenchRouteEnum } from '@/enums/routeEnum';
import type { AppRouteRecordRaw } from '../types';
const Workbench: AppRouteRecordRaw = {
path: '/workbench',
name: WorkbenchRouteEnum.WORKBENCH,
redirect: '/workbench/index',
component: DEFAULT_LAYOUT,
meta: {
locale: 'menu.workbench',
icon: 'icon-icon_pc_filled',
order: 0,
hideChildrenInMenu: true,
},
children: [
{
path: 'index',
name: 'WorkbenchIndex',
component: () => import('@/views/workbench/index.vue'),
meta: {
roles: ['*'],
},
},
],
};
export default Workbench;

View File

@ -1,28 +0,0 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
import menuEnum from '@/enums/menuEnum';
const WorkPlace: AppRouteRecordRaw = {
path: '/workplace',
name: menuEnum.WORKPLACE,
redirect: '/workplace/index',
component: DEFAULT_LAYOUT,
meta: {
locale: 'menu.workplace',
icon: 'icon-icon_pc_filled',
order: 0,
hideChildrenInMenu: true,
},
children: [
{
path: 'index',
name: 'WorkPlaceIndex',
component: () => import('@/views/workplace/index.vue'),
meta: {
roles: ['*'],
},
},
],
};
export default WorkPlace;

View File

@ -1,6 +1,6 @@
import { defineComponent } from 'vue';
import type { NavigationGuard } from 'vue-router';
import type { BreadcrumbItem } from '@/components/bussiness/ms-breadcrumb/types';
import type { BreadcrumbItem } from '@/components/business/ms-breadcrumb/types';
export type Component<T = any> =
| ReturnType<typeof defineComponent>

View File

@ -11,7 +11,7 @@ import { watchStyle, watchTheme } from '@/utils/theme';
import type { NotificationReturn } from '@arco-design/web-vue/es/notification/interface';
import type { RouteRecordNormalized, RouteRecordRaw } from 'vue-router';
import type { AppState } from './types';
import type { BreadcrumbItem } from '@/components/bussiness/ms-breadcrumb/types';
import type { BreadcrumbItem } from '@/components/business/ms-breadcrumb/types';
import type { PageConfig, PageConfigKeys, Style, Theme } from '@/models/setting/config';
const defaultThemeConfig = {

View File

@ -1,5 +1,5 @@
import type { RouteRecordNormalized, RouteRecordRaw } from 'vue-router';
import type { BreadcrumbItem } from '@/components/bussiness/ms-breadcrumb/types';
import type { BreadcrumbItem } from '@/components/business/ms-breadcrumb/types';
import type { PageConfig, ThemeConfig, LoginConfig, PlatformConfig } from '@/models/setting/config';
export interface AppState {

View File

@ -172,3 +172,37 @@ export function calculateMaxDepth(arr?: Node[], depth = 0) {
return maxDepth;
}
export interface TreeNode<T> {
[key: string]: any;
children?: TreeNode<T>[];
}
/**
*
* @param tree
* @param customNodeFn
* @param customChildrenKey key
* @returns
*/
export function mapTree<T>(
tree: TreeNode<T> | TreeNode<T>[],
customNodeFn: (node: TreeNode<T>) => TreeNode<T> | null = (node) => node,
customChildrenKey = 'children'
): TreeNode<T>[] {
if (!Array.isArray(tree)) {
tree = [tree];
}
return tree
.map((node: TreeNode<T>) => {
const newNode = typeof customNodeFn === 'function' ? customNodeFn(node) : node;
if (newNode && newNode[customChildrenKey] && newNode[customChildrenKey].length > 0) {
newNode[customChildrenKey] = mapTree(newNode[customChildrenKey], customNodeFn, customChildrenKey);
}
return newNode;
})
.filter(Boolean);
}

View File

@ -13,6 +13,6 @@
const router = useRouter();
const back = () => {
// warning Go to the node that has the permission
router.push({ name: 'Workplace' });
router.push({ name: 'Workbench' });
};
</script>

View File

@ -0,0 +1,9 @@
<template>
<logCards mode="PROJECT"></logCards>
</template>
<script lang="tsx" setup>
import logCards from '@/views/setting/system/log/components/logCards.vue';
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,9 @@
<template>
<logCards mode="ORGANIZATION"></logCards>
</template>
<script lang="tsx" setup>
import logCards from '@/views/setting/system/log/components/logCards.vue';
</script>
<style lang="less" scoped></style>

View File

@ -139,7 +139,7 @@
import useTable from '@/components/pure/ms-table/useTable';
import AddMemberModal from './components/addMemberModal.vue';
import MsCard from '@/components/pure/ms-card/index.vue';
import MsRemoveButton from '@/components/bussiness/ms-remove-button/MsRemoveButton.vue';
import MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
import {
getMemberList,
deleteMemberReq,
@ -150,7 +150,7 @@
getGlobalUserGroup,
} from '@/api/modules/setting/member';
import { TableKeyEnum } from '@/enums/tableEnum';
import MSBatchModal from '@/components/bussiness/ms-batch-modal/index.vue';
import MSBatchModal from '@/components/business/ms-batch-modal/index.vue';
import { useTableStore, useUserStore } from '@/store';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import type {

View File

@ -499,7 +499,7 @@
updateAuthStatus,
deleteAuth,
} from '@/api/modules/setting/config';
import MsFormItemSub from '@/components/bussiness/ms-form-item-sub/index.vue';
import MsFormItemSub from '@/components/business/ms-form-item-sub/index.vue';
import { scrollIntoView } from '@/utils/dom';
import { characterLimit } from '@/utils';

View File

@ -199,7 +199,7 @@
import MsDescription, { Description } from '@/components/pure/ms-description/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsFormItemSub from '@/components/bussiness/ms-form-item-sub/index.vue';
import MsFormItemSub from '@/components/business/ms-form-item-sub/index.vue';
import { validateEmail } from '@/utils/validate';
import { testEmail, saveBaseInfo, saveEmailInfo, getBaseInfo, getEmailInfo } from '@/api/modules/setting/config';

View File

@ -312,7 +312,7 @@
import MsButton from '@/components/pure/ms-button/index.vue';
import loginForm from '@/views/login/components/login-form.vue';
import banner from '@/views/login/components/banner.vue';
import MsFormItemSub from '@/components/bussiness/ms-form-item-sub/index.vue';
import MsFormItemSub from '@/components/business/ms-form-item-sub/index.vue';
import useAppStore from '@/store/modules/app';
import MsUpload from '@/components/pure/ms-upload/index.vue';
import defaultLayout from '@/layout/default-layout.vue';

View File

@ -0,0 +1,436 @@
<template>
<MsCard simple auto-height>
<div class="filter-box">
<MsSearchSelect
v-model:model-value="operUser"
placeholder="system.log.operatorPlaceholder"
prefix="system.log.operator"
:options="userList"
:search-keys="['label', 'email']"
:option-label-render="
(item) => `${item.label}<span class='text-[var(--color-text-2)]'>${item.email}</span>`
"
allow-clear
/>
<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"
:allow-clear="false"
:disabled-input="false"
value-format="timestamp"
@select="selectTime"
>
<template #prefix>
{{ t('system.log.operateTime') }}
</template>
</a-range-picker>
<MsCascader
v-model:model-value="operateRange"
v-model:level="level"
:options="rangeOptions"
:prefix="t('system.log.operateRange')"
:level-top="[...MENU_LEVEL]"
:virtual-list-props="{ height: 200 }"
:loading="rangeLoading"
/>
<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">{{ t(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')"
:panel-width="100"
strictly
/>
<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" @click="searchLog">{{ 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>
<div class="log-card">
<div class="log-card-header">
<div class="text-[var(--color-text-000)]">{{ t('system.log.log') }}</div>
</div>
<ms-base-table v-bind="propsRes" no-disable sticky-header v-on="propsEvent">
<template #range="{ record }">
{{ `${record.organizationName}${record.projectName ? `/${record.projectName}` : ''}` }}
</template>
<template #content="{ record }">
<MsButton @click="handleNameClick(record)">{{ record.content }}</MsButton>
</template>
</ms-base-table>
</div>
</template>
<script lang="tsx" 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 { getLogList, getLogOptions, getLogUsers } from '@/api/modules/setting/log';
import MsCascader from '@/components/business/ms-cascader/index.vue';
import useTableStore from '@/store/modules/ms-table';
import { TableKeyEnum } from '@/enums/tableEnum';
import useTable from '@/components/pure/ms-table/useTable';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import { MENU_LEVEL, getPathMapByLevel } from '@/config/pathMap';
import MsSearchSelect from '@/components/business/ms-search-select/index';
import type { CascaderOption, SelectOptionData } from '@arco-design/web-vue';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import type { MsTimeLineListItem } from '@/components/pure/ms-timeline/types';
const props = defineProps<{
mode: (typeof MENU_LEVEL)[number]; // //
}>();
const { t } = useI18n();
const operUser = ref(''); //
const userList = ref<SelectOptionData[]>([]); //
async function initUserList() {
try {
const res = await getLogUsers();
userList.value = res.map((e) => ({
id: e.id,
value: e.id,
label: e.name,
email: e.email,
}));
} catch (error) {
console.log(error);
}
}
const defaultLevelIndex = MENU_LEVEL.findIndex((e) => e === props.mode); // mode
const operateRange = ref<(string | number | Record<string, any>)[]>([MENU_LEVEL[defaultLevelIndex]]); //
const rangeOptions = ref<CascaderOption[]>( //
props.mode === 'SYSTEM'
? [
{
value: {
level: 0,
value: MENU_LEVEL[0],
},
label: t('system.log.system'),
isLeaf: true,
},
]
: []
);
const rangeLoading = ref(false);
/**
* 初始化操作范围级联选项
*/
async function initRangeOptions() {
try {
rangeLoading.value = true;
const res = await getLogOptions();
if (props.mode === 'SYSTEM' || props.mode === 'ORGANIZATION') {
//
rangeOptions.value.push({
value: {
level: 0,
value: MENU_LEVEL[1], // -
},
label: t('system.log.organization'),
children: res.organizationList.map((e) => ({
//
value: {
level: MENU_LEVEL[1],
value: e.id,
},
label: e.name,
isLeaf: true,
})),
});
}
rangeOptions.value.push({
value: {
level: 0,
value: MENU_LEVEL[2], // -
},
label: t('system.log.project'),
children: res.projectList.map((e) => ({
//
value: {
level: MENU_LEVEL[2],
value: e.id,
},
label: e.name,
isLeaf: true,
})),
});
} catch (error) {
console.log(error);
} finally {
rangeLoading.value = false;
}
}
const level = ref<(typeof MENU_LEVEL)[number]>('SYSTEM'); // //
const type = ref(''); //
const _module = ref(''); //
const content = ref(''); //
const time = ref<(Date | string | number)[]>([dayjs().subtract(1, 'M').valueOf(), dayjs().valueOf()]); //
const selectedTime = ref<Date | string | number | undefined>(''); //
/**
* 不可选日期
* @param current 日期选择器当前日期
* @return boolean true-不可选false-可选
*/
function disabledDate(current?: Date) {
const now = dayjs();
const endDate = dayjs(current);
// & 6 6
return (
(!!current && endDate.isAfter(now)) ||
(selectedTime.value ? Math.abs(dayjs(selectedTime.value).diff(endDate, 'months')) > 5 : false)
);
}
/**
* 日期选择器点击选中的日期但未确认时
* @param val 点击的日期
*/
function selectTime(val: (Date | string | number | undefined)[] | undefined) {
const arr = val?.filter((e) => e) || [];
if (arr.length === 1) {
[selectedTime.value] = arr;
}
}
const moduleOptions = ref<CascaderOption[]>([]);
const moduleLocaleMap = ref<Record<string, string>>({});
function initModuleOptions() {
moduleOptions.value = getPathMapByLevel(props.mode, (e) => {
moduleLocaleMap.value[e.key] = e.locale;
return {
value: e.key,
label: t(e.locale),
children: e.children,
isLeaf: e.children?.length === 0,
};
});
}
const typeOptions = [
{
label: 'system.log.operateType.all',
value: '',
},
{
label: 'system.log.operateType.add',
value: 'ADD',
},
{
label: 'system.log.operateType.delete',
value: 'DELETE',
},
{
label: 'system.log.operateType.update',
value: 'UPDATE',
},
{
label: 'system.log.operateType.debug',
value: 'DEBUG',
},
{
label: 'system.log.operateType.review',
value: 'REVIEW',
},
{
label: 'system.log.operateType.copy',
value: 'COPY',
},
{
label: 'system.log.operateType.execute',
value: 'EXECUTE',
},
{
label: 'system.log.operateType.share',
value: 'SHARE',
},
{
label: 'system.log.operateType.restore',
value: 'RESTORE',
},
{
label: 'system.log.operateType.import',
value: 'IMPORT',
},
{
label: 'system.log.operateType.export',
value: 'EXPORT',
},
{
label: 'system.log.operateType.login',
value: 'LOGIN',
},
{
label: 'system.log.operateType.select',
value: 'SELECT',
},
{
label: 'system.log.operateType.recover',
value: 'RECOVER',
},
{
label: 'system.log.operateType.logout',
value: 'LOGOUT',
},
];
function resetFilter() {
operUser.value = '';
operateRange.value = [MENU_LEVEL[0]];
type.value = '';
_module.value = '';
content.value = '';
time.value = [dayjs().subtract(1, 'M').valueOf(), dayjs().valueOf()];
}
const columns: MsTableColumn = [
{
title: 'system.log.operator',
dataIndex: 'userName',
},
{
title: 'system.log.operateRange',
dataIndex: 'operateRange',
slotName: 'range',
},
{
title: 'system.log.operateTarget',
dataIndex: 'module',
},
{
title: 'system.log.operateType',
dataIndex: 'type',
width: 120,
},
{
title: 'system.log.operateName',
dataIndex: 'content',
slotName: 'content',
},
{
title: 'system.log.time',
dataIndex: 'createTime',
fixed: 'right',
width: 160,
sortable: {
sortDirections: ['ascend', 'descend'],
},
},
];
const tableStore = useTableStore();
tableStore.initColumn(TableKeyEnum.SYSTEM_LOG, columns, 'drawer');
const { propsRes, propsEvent, loadList, setLoadListParams, resetPagination } = useTable(
getLogList,
{
tableKey: TableKeyEnum.SYSTEM_LOG,
columns,
selectable: false,
showSelectAll: false,
},
(record) => ({
...record,
type: t(typeOptions.find((e) => e.value === record.type)?.label || ''),
module: t(moduleLocaleMap.value[record.module] || ''),
})
);
function searchLog() {
const ranges = operateRange.value.map((e) => e);
setLoadListParams({
operUser: operUser.value,
projectIds: level.value === 'PROJECT' && ranges[0] !== 'PROJECT' ? ranges : [],
organizationIds: level.value === 'ORGANIZATION' && ranges[0] !== 'ORGANIZATION' ? ranges : [],
type: type.value,
module: _module.value,
content: content.value,
startTime: time.value[0],
endTime: time.value[1],
level: level.value,
});
resetPagination();
loadList();
}
function handleNameClick(record: MsTimeLineListItem) {
console.log(record);
}
onBeforeMount(() => {
initUserList();
initRangeOptions();
initModuleOptions();
searchLog();
});
</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);
}
}
.log-card {
@apply bg-white;
margin-top: 16px;
padding: 24px;
border-radius: var(--border-radius-large);
.log-card-header {
@apply flex items-center justify-between;
margin-bottom: 16px;
}
}
.log-card--list {
padding: 0 0 24px;
.log-card-header {
margin-bottom: 0;
padding: 24px 24px 16px;
}
}
</style>

View File

@ -1,240 +1,9 @@
<template>
<MsCard simple auto-height>
<div class="filter-box">
<a-input
v-model:model-value="operUser"
class="filter-item"
:placeholder="t('system.log.operatorPlaceholder')"
allow-clear
>
<template #prefix>
{{ t('system.log.operator') }}
</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>
<logCards mode="SYSTEM"></logCards>
</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.organization'),
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 lang="tsx" setup>
import logCards from './components/logCards.vue';
</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>
<style lang="less" scoped></style>

View File

@ -1 +1,35 @@
export default {};
export default {
'system.log.operator': 'Operator',
'system.log.operatorPlaceholder': 'Please enter username/email to search',
'system.log.operateTime': 'Operation time',
'system.log.operateRange': 'Operating range',
'system.log.operateType': 'Operation type',
'system.log.operateTarget': 'Operation object',
'system.log.operateTargetPlaceholder': 'Please select',
'system.log.operateName': 'Name',
'system.log.operateNamePlaceholder': 'Please enter use case/environment/menu name',
'system.log.system': 'System',
'system.log.organization': 'Organization',
'system.log.project': 'Project',
'system.log.search': 'Search',
'system.log.reset': 'Reset',
'system.log.operateType.all': 'All',
'system.log.operateType.add': 'Add',
'system.log.operateType.delete': 'Delete',
'system.log.operateType.update': 'Update',
'system.log.operateType.debug': 'Debug',
'system.log.operateType.review': 'Review',
'system.log.operateType.copy': 'Copy',
'system.log.operateType.execute': 'Execute',
'system.log.operateType.share': 'Share',
'system.log.operateType.restore': 'Restore',
'system.log.operateType.import': 'Import',
'system.log.operateType.export': 'Export',
'system.log.operateType.login': 'Login',
'system.log.operateType.select': 'Select',
'system.log.operateType.recover': 'Recover',
'system.log.operateType.logout': 'Logout',
'system.log.log': 'Operation log',
'system.log.time': 'Operation time',
'system.log.content': 'in {module} under {range}',
};

View File

@ -1,6 +1,6 @@
export default {
'system.log.operator': '操作人',
'system.log.operatorPlaceholder': '请输入用户名/邮箱',
'system.log.operatorPlaceholder': '请输入用户名/邮箱搜索',
'system.log.operateTime': '操作时间',
'system.log.operateRange': '操作范围',
'system.log.operateType': '操作类型',
@ -29,4 +29,7 @@ export default {
'system.log.operateType.select': '选择',
'system.log.operateType.recover': '恢复',
'system.log.operateType.logout': '登出',
'system.log.log': '操作日志',
'system.log.time': '操作时间',
'system.log.content': '{operator} 在 {range} 下的 {module} 中 ',
};

View File

@ -52,7 +52,7 @@
import { useI18n } from '@/hooks/useI18n';
import { reactive, ref, watchEffect, computed } from 'vue';
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
import MsUserSelector from '@/components/bussiness/ms-user-selector/index.vue';
import MsUserSelector from '@/components/business/ms-user-selector/index.vue';
import { createOrUpdateOrg } from '@/api/modules/setting/system/organizationAndProject';
import { Message } from '@arco-design/web-vue';
import { CreateOrUpdateSystemOrgParams } from '@/models/setting/system/orgAndProject';

View File

@ -55,7 +55,7 @@
import { useI18n } from '@/hooks/useI18n';
import { reactive, ref, watchEffect, computed } from 'vue';
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
import MsUserSelector from '@/components/bussiness/ms-user-selector/index.vue';
import MsUserSelector from '@/components/business/ms-user-selector/index.vue';
import { createOrUpdateOrg } from '@/api/modules/setting/system/organizationAndProject';
import { Message } from '@arco-design/web-vue';
import { CreateOrUpdateSystemProjectParams } from '@/models/setting/system/orgAndProject';

View File

@ -35,7 +35,7 @@
import { reactive, ref, watchEffect, onUnmounted } from 'vue';
import { addUserToOrgOrProject } from '@/api/modules/setting/system/organizationAndProject';
import { Message, type FormInstance, type ValidatedError } from '@arco-design/web-vue';
import MsUserSelector from '@/components/bussiness/ms-user-selector/index.vue';
import MsUserSelector from '@/components/business/ms-user-selector/index.vue';
const { t } = useI18n();
const props = defineProps<{

View File

@ -59,7 +59,7 @@
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import AddUserModal from './addUserModal.vue';
import { TableData, Message } from '@arco-design/web-vue';
import MsRemoveButton from '@/components/bussiness/ms-remove-button/MsRemoveButton.vue';
import MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
export interface projectDrawerProps {
visible: boolean;

View File

@ -338,7 +338,7 @@
import useVisit from '@/hooks/useVisit';
import MsCard from '@/components/pure/ms-card/index.vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsBatchForm from '@/components/bussiness/ms-batch-form/index.vue';
import MsBatchForm from '@/components/business/ms-batch-form/index.vue';
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
import JobTemplateDrawer from './components/jobTemplateDrawer.vue';
import { getYaml, YamlType, job } from './template';
@ -348,7 +348,7 @@
import { getAllOrgList } from '@/api/modules/setting/orgnization';
import useAppStore from '@/store/modules/app';
import type { MsBatchFormInstance, FormItemModel } from '@/components/bussiness/ms-batch-form/types';
import type { MsBatchFormInstance, FormItemModel } from '@/components/business/ms-batch-form/types';
import type { UpdateResourcePoolParams, NodesListItem } from '@/models/setting/resourcePool';
const route = useRoute();

View File

@ -1,5 +1,5 @@
<template>
<MsCard :loading="loading" has-breadcrumb simple>
<MsCard :loading="loading" simple>
<div class="mb-4 flex items-center justify-between">
<a-button type="primary" @click="addPool">
{{ t('system.resourcePool.createPool') }}

View File

@ -216,7 +216,7 @@
import useTable from '@/components/pure/ms-table/useTable';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import MsBatchForm from '@/components/bussiness/ms-batch-form/index.vue';
import MsBatchForm from '@/components/business/ms-batch-form/index.vue';
import {
getUserList,
batchCreateUser,
@ -240,7 +240,7 @@
import type { MsTableColumn, BatchActionParams } from '@/components/pure/ms-table/type';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import type { SimpleUserInfo, SystemRole, UserListItem } from '@/models/setting/user';
import type { FormItemModel, MsBatchFormInstance } from '@/components/bussiness/ms-batch-form/types';
import type { FormItemModel, MsBatchFormInstance } from '@/components/business/ms-batch-form/types';
const { t } = useI18n();

View File

@ -38,7 +38,7 @@
import { useUserGroupStore } from '@/store';
import { getUserList, addUserToUserGroup } from '@/api/modules/setting/usergroup';
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
import MsUserSelector from '@/components/bussiness/ms-user-selector/index.vue';
import MsUserSelector from '@/components/business/ms-user-selector/index.vue';
const { t } = useI18n();
const props = defineProps<{

View File

@ -23,7 +23,7 @@
import { TableKeyEnum } from '@/enums/tableEnum';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import AddUserModal from './addUserModal.vue';
import MsRemoveButton from '@/components/bussiness/ms-remove-button/MsRemoveButton.vue';
import MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
const { t } = useI18n();
const store = useUserGroupStore();

View File

@ -0,0 +1,3 @@
<template> Workbench is waiting for development </template>
<script setup></script>

View File

@ -1,3 +0,0 @@
<template> Workplace is waiting for development </template>
<script setup></script>