feat(全局): 新增标签输入组件&部分组件调整&资源池/用户/系统参数页面支持地址栏参数跳转展示详情&日志支持名称跳转
This commit is contained in:
parent
3a77833613
commit
e3b4dae516
|
@ -20,6 +20,7 @@ import type {
|
||||||
SystemRole,
|
SystemRole,
|
||||||
ImportResult,
|
ImportResult,
|
||||||
BatchAddUserGroupParams,
|
BatchAddUserGroupParams,
|
||||||
|
ResetUserPasswordParams,
|
||||||
} from '@/models/setting/user';
|
} from '@/models/setting/user';
|
||||||
import type { CommonList, TableQueryParams } from '@/models/common';
|
import type { CommonList, TableQueryParams } from '@/models/common';
|
||||||
|
|
||||||
|
@ -55,12 +56,12 @@ export function importUserInfo(data: ImportUserParams) {
|
||||||
|
|
||||||
// 获取系统用户组
|
// 获取系统用户组
|
||||||
export function getSystemRoles() {
|
export function getSystemRoles() {
|
||||||
return MSR.get<SystemRole>({ url: GetSystemRoleUrl });
|
return MSR.get<SystemRole[]>({ url: GetSystemRoleUrl });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置用户密码
|
// 重置用户密码
|
||||||
export function resetUserPassword(userIds: string[]) {
|
export function resetUserPassword(data: ResetUserPasswordParams) {
|
||||||
return MSR.post({ url: ResetPasswordUrl, data: userIds });
|
return MSR.post({ url: ResetPasswordUrl, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量添加用户到多个用户组
|
// 批量添加用户到多个用户组
|
||||||
|
|
|
@ -15,4 +15,4 @@ export const GetSystemRoleUrl = '/system/user/get/global/system/role';
|
||||||
// 重置用户密码
|
// 重置用户密码
|
||||||
export const ResetPasswordUrl = '/system/user/reset/password';
|
export const ResetPasswordUrl = '/system/user/reset/password';
|
||||||
// 批量添加用户到多个用户组
|
// 批量添加用户到多个用户组
|
||||||
export const BatchAddUserGroupUrl = '/user/role/relation/global/add/batch';
|
export const BatchAddUserGroupUrl = '/system/user/add/batch/user-role';
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.ms-button {
|
.ms-button {
|
||||||
@apply inline-block cursor-pointer align-middle;
|
@apply flex cursor-pointer items-center align-middle;
|
||||||
&:not(:last-child) {
|
&:not(:last-child) {
|
||||||
@apply mr-4;
|
@apply mr-4;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="!props.hideFooter && !props.simple"
|
v-if="!props.hideFooter && !props.simple"
|
||||||
class="fixed bottom-0 right-[16px] z-10 bg-white p-[24px] shadow-[0_-1px_4px_rgba(2,2,2,0.1)]"
|
class="fixed bottom-0 right-[16px] z-10 flex items-center bg-white p-[24px] shadow-[0_-1px_4px_rgba(2,2,2,0.1)]"
|
||||||
:style="{ width: `calc(100% - ${menuWidth + 16}px)` }"
|
:style="{ width: `calc(100% - ${menuWidth + 16}px)` }"
|
||||||
>
|
>
|
||||||
<div class="ml-0 mr-auto">
|
<div class="ml-0 mr-auto">
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
<template>
|
||||||
|
<a-input-tag
|
||||||
|
v-model:model-value="innerModelValue"
|
||||||
|
v-model:input-value="innerInputValue"
|
||||||
|
:placeholder="t(props.placeholder || '')"
|
||||||
|
:allow-clear="props.allowClear"
|
||||||
|
:retain-input-value="props.retainInputValue"
|
||||||
|
:unique-value="props.uniqueValue"
|
||||||
|
@press-enter="tagInputEnter"
|
||||||
|
@blur="tagInputBlur"
|
||||||
|
>
|
||||||
|
<template v-if="props.customPrefix" #prefix>
|
||||||
|
<slot name="prefix"></slot>
|
||||||
|
</template>
|
||||||
|
<template v-if="props.customTag" #tag="{ data }">
|
||||||
|
<slot name="tag" :data="data"></slot>
|
||||||
|
</template>
|
||||||
|
<template v-if="props.customSuffix" #suffix>
|
||||||
|
<slot name="suffix"></slot>
|
||||||
|
</template>
|
||||||
|
</a-input-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue: string[]; // arco组件 BUG:回车添加第一个标签时,会触发表单的校验且状态为空数据错误,所以需要在a-form-item上设置 :validate-trigger="['blur', 'input']" 避免这种错误情况。
|
||||||
|
inputValue?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
retainInputValue?: boolean;
|
||||||
|
uniqueValue?: boolean;
|
||||||
|
allowClear?: boolean;
|
||||||
|
tagsDuplicateText?: string;
|
||||||
|
customPrefix?: boolean;
|
||||||
|
customTag?: boolean;
|
||||||
|
customSuffix?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
retainInputValue: true,
|
||||||
|
uniqueValue: true,
|
||||||
|
allowClear: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const emit = defineEmits(['update:modelValue', 'update:inputValue']);
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const innerModelValue = ref(props.modelValue);
|
||||||
|
const innerInputValue = ref(props.inputValue);
|
||||||
|
const tagsLength = ref(0); // 记录每次回车或失去焦点前的tags长度,以判断是否有新的tag被添加,新标签添加时需要判断是否重复的标签
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(val) => {
|
||||||
|
innerModelValue.value = val;
|
||||||
|
tagsLength.value = val.length;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => innerModelValue.value,
|
||||||
|
(val) => {
|
||||||
|
if (val.length < tagsLength.value) {
|
||||||
|
// 输入框内标签长度变化且比记录的 tagsLength 小,说明是删除标签,此时需要同步 tagsLength 与标签长度
|
||||||
|
tagsLength.value = val.length;
|
||||||
|
}
|
||||||
|
emit('update:modelValue', val);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.inputValue,
|
||||||
|
(val) => {
|
||||||
|
innerInputValue.value = val;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => innerInputValue.value,
|
||||||
|
(val) => {
|
||||||
|
emit('update:inputValue', val);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function validateUniqueValue() {
|
||||||
|
if (
|
||||||
|
props.uniqueValue &&
|
||||||
|
innerInputValue.value &&
|
||||||
|
tagsLength.value === innerModelValue.value.length &&
|
||||||
|
innerModelValue.value.includes(innerInputValue.value.trim())
|
||||||
|
) {
|
||||||
|
// 当输入框值存在且记录的 tagsLength 与输入框内标签数量相等时,才进行重复校验判断,因为回车触发事件是在 innerModelValue 的值更新后,所以需要判断长度确保重复的标签不是刚增加的导致重复校验逻辑错误
|
||||||
|
Message.warning(t(props.tagsDuplicateText || 'ms.tagsInput.tagsDuplicateText'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tagInputBlur() {
|
||||||
|
if (innerInputValue.value && innerInputValue.value.trim() !== '' && validateUniqueValue()) {
|
||||||
|
innerModelValue.value.push(innerInputValue.value.trim());
|
||||||
|
innerInputValue.value = '';
|
||||||
|
tagsLength.value += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tagInputEnter() {
|
||||||
|
if (validateUniqueValue()) {
|
||||||
|
innerInputValue.value = '';
|
||||||
|
tagsLength.value += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -0,0 +1,3 @@
|
||||||
|
export default {
|
||||||
|
'ms.tagsInput.tagsDuplicateText': 'Same label already exists',
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
export default {
|
||||||
|
'ms.tagsInput.tagsDuplicateText': '已存在相同的标签',
|
||||||
|
};
|
|
@ -1,5 +1,4 @@
|
||||||
import { RouteEnum } from '@/enums/routeEnum';
|
import { RouteEnum } from '@/enums/routeEnum';
|
||||||
import { TreeNode, mapTree } from '@/utils';
|
|
||||||
|
|
||||||
export const MENU_LEVEL = ['SYSTEM', 'ORGANIZATION', 'PROJECT'] as const; // 菜单级别
|
export const MENU_LEVEL = ['SYSTEM', 'ORGANIZATION', 'PROJECT'] as const; // 菜单级别
|
||||||
|
|
||||||
|
@ -8,8 +7,9 @@ export const MENU_LEVEL = ['SYSTEM', 'ORGANIZATION', 'PROJECT'] as const; // 菜
|
||||||
* key 是与后台商定的映射 key
|
* key 是与后台商定的映射 key
|
||||||
* locale 是国际化的 key
|
* locale 是国际化的 key
|
||||||
* route 是路由的 name
|
* route 是路由的 name
|
||||||
|
* routeQuery 是路由的固定参数集合,与routeParamKeys互斥,用于跳转同一个路由但不同 tab 时或其他需要固定参数的情况
|
||||||
* permission 是权限的 key 集合
|
* permission 是权限的 key 集合
|
||||||
* level 是菜单级别
|
* level 是菜单级别,用于筛选不同级别的路由/tab
|
||||||
* children 是子路由/tab集合
|
* children 是子路由/tab集合
|
||||||
*/
|
*/
|
||||||
export const pathMap = [
|
export const pathMap = [
|
||||||
|
@ -28,9 +28,9 @@ export const pathMap = [
|
||||||
level: MENU_LEVEL[0],
|
level: MENU_LEVEL[0],
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: 'SETTING_SYSTEM_USER', // 系统设置-系统-用户
|
key: 'SETTING_SYSTEM_USER_SINGLE', // 系统设置-系统-用户
|
||||||
locale: 'menu.settings.system.user',
|
locale: 'menu.settings.system.user',
|
||||||
route: RouteEnum.SETTING_SYSTEM_USER,
|
route: RouteEnum.SETTING_SYSTEM_USER_SINGLE,
|
||||||
permission: [],
|
permission: [],
|
||||||
level: MENU_LEVEL[0],
|
level: MENU_LEVEL[0],
|
||||||
},
|
},
|
||||||
|
@ -56,21 +56,30 @@ export const pathMap = [
|
||||||
level: MENU_LEVEL[0],
|
level: MENU_LEVEL[0],
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: 'SETTING_SYSTEM_PARAMETER', // 系统设置-系统-系统参数-基础设置
|
key: 'SETTING_SYSTEM_PARAMETER_BASE_CONFIG', // 系统设置-系统-系统参数-基础设置
|
||||||
locale: 'system.config.baseConfig',
|
locale: 'system.config.baseConfig',
|
||||||
route: RouteEnum.SETTING_SYSTEM_PARAMETER,
|
route: RouteEnum.SETTING_SYSTEM_PARAMETER,
|
||||||
|
permission: [],
|
||||||
level: MENU_LEVEL[0],
|
level: MENU_LEVEL[0],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'SETTING_SYSTEM_PARAMETER_PAGE_CONFIG', // 系统设置-系统-系统参数-界面设置
|
key: 'SETTING_SYSTEM_PARAMETER_PAGE_CONFIG', // 系统设置-系统-系统参数-界面设置
|
||||||
locale: 'system.config.pageConfig',
|
locale: 'system.config.pageConfig',
|
||||||
route: RouteEnum.SETTING_SYSTEM_PARAMETER,
|
route: RouteEnum.SETTING_SYSTEM_PARAMETER,
|
||||||
|
permission: [],
|
||||||
|
routeQuery: {
|
||||||
|
tab: 'pageConfig',
|
||||||
|
},
|
||||||
level: MENU_LEVEL[0],
|
level: MENU_LEVEL[0],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'SETTING_SYSTEM_PARAMETER_AUTH_CONFIG', // 系统设置-系统-系统参数-认证设置
|
key: 'SETTING_SYSTEM_PARAMETER_AUTH_CONFIG', // 系统设置-系统-系统参数-认证设置
|
||||||
locale: 'system.config.authConfig',
|
locale: 'system.config.authConfig',
|
||||||
route: RouteEnum.SETTING_SYSTEM_PARAMETER,
|
route: RouteEnum.SETTING_SYSTEM_PARAMETER,
|
||||||
|
permission: [],
|
||||||
|
routeQuery: {
|
||||||
|
tab: 'authConfig',
|
||||||
|
},
|
||||||
level: MENU_LEVEL[0],
|
level: MENU_LEVEL[0],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -136,40 +145,6 @@ export const pathMap = [
|
||||||
route: RouteEnum.PROJECT_MANAGEMENT,
|
route: RouteEnum.PROJECT_MANAGEMENT,
|
||||||
permission: [],
|
permission: [],
|
||||||
level: MENU_LEVEL[2],
|
level: MENU_LEVEL[2],
|
||||||
children: [
|
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;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ export enum WorkbenchRouteEnum {
|
||||||
export enum SettingRouteEnum {
|
export enum SettingRouteEnum {
|
||||||
SETTING = 'setting',
|
SETTING = 'setting',
|
||||||
SETTING_SYSTEM = 'settingSystem',
|
SETTING_SYSTEM = 'settingSystem',
|
||||||
SETTING_SYSTEM_USER = 'settingSystemUser',
|
SETTING_SYSTEM_USER_SINGLE = 'settingSystemUser',
|
||||||
SETTING_SYSTEM_USER_GROUP = 'settingSystemUserGroup',
|
SETTING_SYSTEM_USER_GROUP = 'settingSystemUserGroup',
|
||||||
SETTING_SYSTEM_ORGANIZATION = 'settingSystemOrganization',
|
SETTING_SYSTEM_ORGANIZATION = 'settingSystemOrganization',
|
||||||
SETTING_SYSTEM_PARAMETER = 'settingSystemParameter',
|
SETTING_SYSTEM_PARAMETER = 'settingSystemParameter',
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { MENU_LEVEL, pathMap } from '@/config/pathMap';
|
||||||
|
import { TreeNode, findNodeByKey, mapTree } from '@/utils';
|
||||||
|
import { RouteEnum } from '@/enums/routeEnum';
|
||||||
|
|
||||||
|
export default function usePathMap() {
|
||||||
|
const router = useRouter();
|
||||||
|
/**
|
||||||
|
* 根据菜单级别过滤映射树
|
||||||
|
* @param level 菜单级别
|
||||||
|
* @param customNodeFn 自定义过滤函数
|
||||||
|
* @returns 过滤后的映射树
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据路由的 key 进行路由跳转,自动携带配置的 routeQuery 和 传入的routeQuery
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
const jumpRouteByMapKey = (key: typeof RouteEnum, routeQuery?: Record<string, any>) => {
|
||||||
|
const pathNode = findNodeByKey(pathMap, key as unknown as string);
|
||||||
|
if (pathNode) {
|
||||||
|
router.push({
|
||||||
|
name: pathNode?.route,
|
||||||
|
query: {
|
||||||
|
...routeQuery,
|
||||||
|
...pathNode?.routeQuery,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getPathMapByLevel,
|
||||||
|
jumpRouteByMapKey,
|
||||||
|
};
|
||||||
|
}
|
|
@ -29,3 +29,10 @@ export interface CommonList<T> {
|
||||||
current: number;
|
current: number;
|
||||||
list: T[];
|
list: T[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BatchApiParams {
|
||||||
|
selectIds: string[]; // 已选 ID 集合,当 selectAll 为 false 时接口会使用该字段
|
||||||
|
excludeIds?: string[]; // 需要忽略的用户 id 集合,当selectAll为 true 时接口会使用该字段
|
||||||
|
selectAll: boolean; // 是否跨页全选,即选择当前筛选条件下的全部表格数据
|
||||||
|
condition: Record<string, any>; // 当前表格查询的筛选条件
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type { RouteEnum } from '@/enums/routeEnum';
|
||||||
|
|
||||||
export interface OptionsItem {
|
export interface OptionsItem {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -16,7 +18,7 @@ export interface LogItem {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
organizationName: string;
|
organizationName: string;
|
||||||
module: string; // 操作对象
|
module: typeof RouteEnum; // 操作对象
|
||||||
type: string; // 操作类型
|
type: string; // 操作类型
|
||||||
content: string; // 操作名称
|
content: string; // 操作名称
|
||||||
createTime: number;
|
createTime: number;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type { BatchApiParams } from '@/models/common';
|
||||||
|
|
||||||
// 用户所属用户组模型
|
// 用户所属用户组模型
|
||||||
export interface UserRoleListItem {
|
export interface UserRoleListItem {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -71,8 +73,7 @@ export interface CreateUserParams {
|
||||||
userInfoList: SimpleUserInfo[];
|
userInfoList: SimpleUserInfo[];
|
||||||
userRoleIdList: string[];
|
userRoleIdList: string[];
|
||||||
}
|
}
|
||||||
export interface UpdateUserStatusParams {
|
export interface UpdateUserStatusParams extends BatchApiParams {
|
||||||
userIdList: string[];
|
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,9 +81,8 @@ export interface ImportUserParams {
|
||||||
fileList: (File | undefined)[];
|
fileList: (File | undefined)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeleteUserParams {
|
export type DeleteUserParams = BatchApiParams;
|
||||||
userIdList: string[];
|
export type ResetUserPasswordParams = BatchApiParams;
|
||||||
}
|
|
||||||
|
|
||||||
export interface SystemRole {
|
export interface SystemRole {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -97,7 +97,6 @@ export interface ImportResult {
|
||||||
errorMessages: Record<string, any>;
|
errorMessages: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BatchAddUserGroupParams {
|
export interface BatchAddUserGroupParams extends BatchApiParams {
|
||||||
userIds: string[]; // 用户 id 集合
|
|
||||||
roleIds: string[]; // 用户组 id 集合
|
roleIds: string[]; // 用户组 id 集合
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ const Setting: AppRouteRecordRaw = {
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'user',
|
path: 'user',
|
||||||
name: SettingRouteEnum.SETTING_SYSTEM_USER,
|
name: SettingRouteEnum.SETTING_SYSTEM_USER_SINGLE,
|
||||||
component: () => import('@/views/setting/system/user/index.vue'),
|
component: () => import('@/views/setting/system/user/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
locale: 'menu.settings.system.user',
|
locale: 'menu.settings.system.user',
|
||||||
|
|
|
@ -206,3 +206,28 @@ export function mapTree<T>(
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据属性 key 查找树形数组中匹配的某个节点
|
||||||
|
* @param trees 属性数组
|
||||||
|
* @param targetKey 需要匹配的属性值
|
||||||
|
* @param customKey 默认为 key,可自定义需要匹配的属性名
|
||||||
|
* @returns 匹配的节点/null
|
||||||
|
*/
|
||||||
|
export function findNodeByKey<T>(trees: TreeNode<T>[], targetKey: string, customKey = 'key'): TreeNode<T> | null {
|
||||||
|
for (let i = 0; i < trees.length; i++) {
|
||||||
|
const node = trees[i];
|
||||||
|
if (node[customKey] === targetKey) {
|
||||||
|
return node; // 如果当前节点的 key 与目标 key 匹配,则返回当前节点
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(node.children) && node.children.length > 0) {
|
||||||
|
const _node = findNodeByKey(node.children, targetKey); // 递归在子节点中查找
|
||||||
|
if (_node) {
|
||||||
|
return _node; // 如果在子节点中找到了匹配的节点,则返回该节点
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // 如果在整个树形数组中都没有找到匹配的节点,则返回 null
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
</div>
|
</div>
|
||||||
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
|
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
|
||||||
<template #name="{ record }">
|
<template #name="{ record }">
|
||||||
<a-button type="text" @click="openAuthDetail(record)">{{ record.name }}</a-button>
|
<a-button type="text" @click="openAuthDetail(record.id)">{{ record.name }}</a-button>
|
||||||
</template>
|
</template>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<MsButton @click="editAuth(record)">{{ t('system.config.auth.edit') }}</MsButton>
|
<MsButton @click="editAuth(record)">{{ t('system.config.auth.edit') }}</MsButton>
|
||||||
|
@ -682,13 +682,13 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查看认证源
|
* 查看认证源
|
||||||
* @param record 表格项
|
* @param id 表格项 id
|
||||||
*/
|
*/
|
||||||
async function openAuthDetail(record: AuthItem) {
|
async function openAuthDetail(id: string) {
|
||||||
try {
|
try {
|
||||||
showDetailDrawer.value = true;
|
showDetailDrawer.value = true;
|
||||||
detailDrawerLoading.value = true;
|
detailDrawerLoading.value = true;
|
||||||
const res = await getAuthDetail(record.id);
|
const res = await getAuthDetail(id);
|
||||||
activeAuthDetail.value = { ...res, configuration: JSON.parse(res.configuration || '{}') };
|
activeAuthDetail.value = { ...res, configuration: JSON.parse(res.configuration || '{}') };
|
||||||
const { configuration } = activeAuthDetail.value;
|
const { configuration } = activeAuthDetail.value;
|
||||||
let description: Description[] = [
|
let description: Description[] = [
|
||||||
|
@ -993,6 +993,22 @@
|
||||||
showDrawer.value = false;
|
showDrawer.value = false;
|
||||||
authFormRef.value?.resetFields();
|
authFormRef.value?.resetFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
openAuthDetail, // 暴露给父组件以实现页面携带 ID 时自动打开详情抽屉
|
||||||
|
});
|
||||||
|
|
||||||
|
declare const _default: import('vue').DefineComponent<
|
||||||
|
unknown,
|
||||||
|
unknown,
|
||||||
|
import('vue').ComponentOptionsMixin,
|
||||||
|
import('vue').ComponentOptionsMixin,
|
||||||
|
{
|
||||||
|
openAuthDetail: (id: string) => void;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
export declare type AuthConfigInstance = InstanceType<typeof _default>;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped></style>
|
||||||
|
|
|
@ -15,22 +15,25 @@
|
||||||
</MsCard>
|
</MsCard>
|
||||||
<baseConfig v-show="activeTab === 'baseConfig'" />
|
<baseConfig v-show="activeTab === 'baseConfig'" />
|
||||||
<pageConfig v-if="isInitedPageConfig" v-show="activeTab === 'pageConfig'" />
|
<pageConfig v-if="isInitedPageConfig" v-show="activeTab === 'pageConfig'" />
|
||||||
<authConfig v-if="isInitedAuthConfig" v-show="activeTab === 'authConfig'" />
|
<authConfig v-if="isInitedAuthConfig" v-show="activeTab === 'authConfig'" ref="authConfigRef" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import baseConfig from './components/baseConfig.vue';
|
import baseConfig from './components/baseConfig.vue';
|
||||||
import pageConfig from './components/pageConfig.vue';
|
import pageConfig from './components/pageConfig.vue';
|
||||||
import authConfig from './components/authConfig.vue';
|
import authConfig, { AuthConfigInstance } from './components/authConfig.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
const activeTab = ref('baseConfig');
|
const activeTab = ref((route.query.tab as string) || 'baseConfig');
|
||||||
const isInitedPageConfig = ref(activeTab.value === 'pageConfig');
|
const isInitedPageConfig = ref(activeTab.value === 'pageConfig');
|
||||||
const isInitedAuthConfig = ref(activeTab.value === 'authConfig');
|
const isInitedAuthConfig = ref(activeTab.value === 'authConfig');
|
||||||
|
const authConfigRef = ref<AuthConfigInstance | null>();
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => activeTab.value,
|
() => activeTab.value,
|
||||||
|
@ -40,8 +43,17 @@
|
||||||
} else if (val === 'authConfig' && !isInitedAuthConfig.value) {
|
} else if (val === 'authConfig' && !isInitedAuthConfig.value) {
|
||||||
isInitedAuthConfig.value = true;
|
isInitedAuthConfig.value = true;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (route.query.tab === 'authConfig' && route.query.id) {
|
||||||
|
authConfigRef.value?.openAuthDetail(route.query.id as string);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
<a-option v-for="opt of typeOptions" :key="opt.value" :value="opt.value">{{ t(opt.label) }}</a-option>
|
<a-option v-for="opt of typeOptions" :key="opt.value" :value="opt.value">{{ t(opt.label) }}</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<MsCascader
|
<MsCascader
|
||||||
v-model="_module"
|
v-model:model-value="_module"
|
||||||
:options="moduleOptions"
|
:options="moduleOptions"
|
||||||
mode="native"
|
mode="native"
|
||||||
:prefix="t('system.log.operateTarget')"
|
:prefix="t('system.log.operateTarget')"
|
||||||
|
@ -78,6 +78,12 @@
|
||||||
<template #range="{ record }">
|
<template #range="{ record }">
|
||||||
{{ `${record.organizationName}${record.projectName ? `/${record.projectName}` : ''}` }}
|
{{ `${record.organizationName}${record.projectName ? `/${record.projectName}` : ''}` }}
|
||||||
</template>
|
</template>
|
||||||
|
<template #module="{ record }">
|
||||||
|
{{ getModuleLocale(record.module) }}
|
||||||
|
</template>
|
||||||
|
<template #type="{ record }">
|
||||||
|
{{ t(typeOptions.find((e) => e.value === record.type)?.label || '') }}
|
||||||
|
</template>
|
||||||
<template #content="{ record }">
|
<template #content="{ record }">
|
||||||
<MsButton @click="handleNameClick(record)">{{ record.content }}</MsButton>
|
<MsButton @click="handleNameClick(record)">{{ record.content }}</MsButton>
|
||||||
</template>
|
</template>
|
||||||
|
@ -90,6 +96,7 @@
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import usePathMap from '@/hooks/usePathMap';
|
||||||
import { getLogList, getLogOptions, getLogUsers } from '@/api/modules/setting/log';
|
import { getLogList, getLogOptions, getLogUsers } from '@/api/modules/setting/log';
|
||||||
import MsCascader from '@/components/business/ms-cascader/index.vue';
|
import MsCascader from '@/components/business/ms-cascader/index.vue';
|
||||||
import useTableStore from '@/store/modules/ms-table';
|
import useTableStore from '@/store/modules/ms-table';
|
||||||
|
@ -97,12 +104,12 @@
|
||||||
import useTable from '@/components/pure/ms-table/useTable';
|
import useTable from '@/components/pure/ms-table/useTable';
|
||||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import { MENU_LEVEL, getPathMapByLevel } from '@/config/pathMap';
|
import { MENU_LEVEL } from '@/config/pathMap';
|
||||||
import MsSearchSelect from '@/components/business/ms-search-select/index';
|
import MsSearchSelect from '@/components/business/ms-search-select/index';
|
||||||
|
|
||||||
import type { CascaderOption, SelectOptionData } from '@arco-design/web-vue';
|
import type { CascaderOption, SelectOptionData } from '@arco-design/web-vue';
|
||||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||||
import type { MsTimeLineListItem } from '@/components/pure/ms-timeline/types';
|
import type { LogItem } from '@/models/setting/log';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
mode: (typeof MENU_LEVEL)[number]; // 日志展示模式,系统/组织/项目
|
mode: (typeof MENU_LEVEL)[number]; // 日志展示模式,系统/组织/项目
|
||||||
|
@ -228,6 +235,7 @@
|
||||||
|
|
||||||
const moduleOptions = ref<CascaderOption[]>([]);
|
const moduleOptions = ref<CascaderOption[]>([]);
|
||||||
const moduleLocaleMap = ref<Record<string, string>>({});
|
const moduleLocaleMap = ref<Record<string, string>>({});
|
||||||
|
const { getPathMapByLevel, jumpRouteByMapKey } = usePathMap();
|
||||||
|
|
||||||
function initModuleOptions() {
|
function initModuleOptions() {
|
||||||
moduleOptions.value = getPathMapByLevel(props.mode, (e) => {
|
moduleOptions.value = getPathMapByLevel(props.mode, (e) => {
|
||||||
|
@ -241,6 +249,18 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取操作对象映射的国际化文本,并处理可能不存在的 key 导致报错的情况
|
||||||
|
* @param module 操作对象 key
|
||||||
|
*/
|
||||||
|
function getModuleLocale(module: string) {
|
||||||
|
try {
|
||||||
|
return t(moduleLocaleMap.value[module] || '') || module;
|
||||||
|
} catch (error) {
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const typeOptions = [
|
const typeOptions = [
|
||||||
{
|
{
|
||||||
label: 'system.log.operateType.all',
|
label: 'system.log.operateType.all',
|
||||||
|
@ -330,10 +350,12 @@
|
||||||
{
|
{
|
||||||
title: 'system.log.operateTarget',
|
title: 'system.log.operateTarget',
|
||||||
dataIndex: 'module',
|
dataIndex: 'module',
|
||||||
|
slotName: 'module',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'system.log.operateType',
|
title: 'system.log.operateType',
|
||||||
dataIndex: 'type',
|
dataIndex: 'type',
|
||||||
|
slotName: 'type',
|
||||||
width: 120,
|
width: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -353,20 +375,12 @@
|
||||||
];
|
];
|
||||||
const tableStore = useTableStore();
|
const tableStore = useTableStore();
|
||||||
tableStore.initColumn(TableKeyEnum.SYSTEM_LOG, columns, 'drawer');
|
tableStore.initColumn(TableKeyEnum.SYSTEM_LOG, columns, 'drawer');
|
||||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetPagination } = useTable(
|
const { propsRes, propsEvent, loadList, setLoadListParams, resetPagination } = useTable(getLogList, {
|
||||||
getLogList,
|
tableKey: TableKeyEnum.SYSTEM_LOG,
|
||||||
{
|
columns,
|
||||||
tableKey: TableKeyEnum.SYSTEM_LOG,
|
selectable: false,
|
||||||
columns,
|
showSelectAll: false,
|
||||||
selectable: false,
|
});
|
||||||
showSelectAll: false,
|
|
||||||
},
|
|
||||||
(record) => ({
|
|
||||||
...record,
|
|
||||||
type: t(typeOptions.find((e) => e.value === record.type)?.label || ''),
|
|
||||||
module: t(moduleLocaleMap.value[record.module] || ''),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
function searchLog() {
|
function searchLog() {
|
||||||
const ranges = operateRange.value.map((e) => e);
|
const ranges = operateRange.value.map((e) => e);
|
||||||
|
@ -385,8 +399,8 @@
|
||||||
loadList();
|
loadList();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNameClick(record: MsTimeLineListItem) {
|
function handleNameClick(record: LogItem) {
|
||||||
console.log(record);
|
jumpRouteByMapKey(record.module, record.sourceId ? { id: record.sourceId } : {});
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
v-model:visible="showJobDrawer"
|
v-model:visible="showJobDrawer"
|
||||||
:width="680"
|
:width="680"
|
||||||
:title="t('system.resourcePool.customJobTemplate')"
|
:title="t('system.resourcePool.customJobTemplate')"
|
||||||
:footer="false"
|
:footer="!props.readOnly"
|
||||||
@close="handleClose"
|
@close="handleClose"
|
||||||
>
|
>
|
||||||
<MsCodeEditor
|
<MsCodeEditor
|
||||||
|
@ -14,6 +14,9 @@
|
||||||
theme="MS-text"
|
theme="MS-text"
|
||||||
:read-only="props.readOnly"
|
:read-only="props.readOnly"
|
||||||
/>
|
/>
|
||||||
|
<template v-if="!props.readOnly" #footer>
|
||||||
|
<a-button type="secondary" @click="resetTemplate">{{ t('system.resourcePool.jobTemplateReset') }}</a-button>
|
||||||
|
</template>
|
||||||
</MsDrawer>
|
</MsDrawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -22,6 +25,7 @@
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||||
|
import { job } from '../template';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
|
@ -68,6 +72,10 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function resetTemplate() {
|
||||||
|
jobDefinition.value = job;
|
||||||
|
}
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
emit('update:value', jobDefinition.value);
|
emit('update:value', jobDefinition.value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,13 +15,13 @@
|
||||||
</div>
|
</div>
|
||||||
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
|
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
|
||||||
<template #name="{ record }">
|
<template #name="{ record }">
|
||||||
<a-button type="text" @click="showPoolDetail(record)">{{ record.name }}</a-button>
|
<a-button type="text" @click="showPoolDetail(record.id)">{{ record.name }}</a-button>
|
||||||
</template>
|
</template>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
<MsButton @click="editPool(record)">{{ t('system.resourcePool.editPool') }}</MsButton>
|
<MsButton @click="editPool(record)">{{ t('system.resourcePool.editPool') }}</MsButton>
|
||||||
<MsButton v-if="record.enable" @click="disabledPool(record)">{{
|
<MsButton v-if="record.enable" @click="disabledPool(record)">
|
||||||
t('system.resourcePool.tableDisable')
|
{{ t('system.resourcePool.tableDisable') }}
|
||||||
}}</MsButton>
|
</MsButton>
|
||||||
<MsButton v-else @click="enablePool(record)">{{ t('system.resourcePool.tableEnable') }}</MsButton>
|
<MsButton v-else @click="enablePool(record)">{{ t('system.resourcePool.tableEnable') }}</MsButton>
|
||||||
<MsTableMoreAction :list="tableActions" @select="handleSelect($event, record)"></MsTableMoreAction>
|
<MsTableMoreAction :list="tableActions" @select="handleSelect($event, record)"></MsTableMoreAction>
|
||||||
</template>
|
</template>
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, Ref, ref } from 'vue';
|
import { onMounted, Ref, ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { getPoolList, delPoolInfo, togglePoolStatus, getPoolInfo } from '@/api/modules/setting/resourcePool';
|
import { getPoolList, delPoolInfo, togglePoolStatus, getPoolInfo } from '@/api/modules/setting/resourcePool';
|
||||||
|
@ -77,6 +77,7 @@
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
const columns: MsTableColumn = [
|
const columns: MsTableColumn = [
|
||||||
{
|
{
|
||||||
|
@ -242,16 +243,16 @@
|
||||||
const drawerLoading = ref(false);
|
const drawerLoading = ref(false);
|
||||||
/**
|
/**
|
||||||
* 查看资源池详情
|
* 查看资源池详情
|
||||||
* @param record
|
* @param id 资源池 id
|
||||||
*/
|
*/
|
||||||
async function showPoolDetail(record: any) {
|
async function showPoolDetail(id: string) {
|
||||||
if (activePool.value?.id === record.id && showDetailDrawer.value) {
|
if (activePool.value?.id === id && showDetailDrawer.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
drawerLoading.value = true;
|
drawerLoading.value = true;
|
||||||
showDetailDrawer.value = true;
|
showDetailDrawer.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await getPoolInfo(record.id);
|
const res = await getPoolInfo(id);
|
||||||
if (res) {
|
if (res) {
|
||||||
activePool.value = res;
|
activePool.value = res;
|
||||||
const poolUses = [
|
const poolUses = [
|
||||||
|
@ -400,6 +401,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (route.query.id) {
|
||||||
|
// 地址栏携带 id,自动打开资源池详情抽屉
|
||||||
|
showPoolDetail(route.query.id as string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编辑资源池
|
* 编辑资源池
|
||||||
* @param record
|
* @param record
|
||||||
|
|
|
@ -118,4 +118,7 @@ export default {
|
||||||
'system.resourcePool.jobTemplate': 'Job Templates',
|
'system.resourcePool.jobTemplate': 'Job Templates',
|
||||||
'system.resourcePool.jobTemplateTip':
|
'system.resourcePool.jobTemplateTip':
|
||||||
'A Kubernetes job template is a text in YAML format, which is used to define the running parameters of the job. You can edit the job template here.',
|
'A Kubernetes job template is a text in YAML format, which is used to define the running parameters of the job. You can edit the job template here.',
|
||||||
|
'system.resourcePool.jobTemplateReset': 'Reset Template',
|
||||||
|
'system.resourcePool.addSuccess': 'Added resource pool successfully',
|
||||||
|
'system.resourcePool.updateSuccess': 'Resource pool updated successfully',
|
||||||
};
|
};
|
||||||
|
|
|
@ -112,6 +112,7 @@ export default {
|
||||||
'system.resourcePool.jobTemplate': 'Job 模版',
|
'system.resourcePool.jobTemplate': 'Job 模版',
|
||||||
'system.resourcePool.jobTemplateTip':
|
'system.resourcePool.jobTemplateTip':
|
||||||
'Kubernetes Job 模版是一个YAML格式的文本,用于定义Job的运行参数,您可以在此处编辑Job模版。',
|
'Kubernetes Job 模版是一个YAML格式的文本,用于定义Job的运行参数,您可以在此处编辑Job模版。',
|
||||||
|
'system.resourcePool.jobTemplateReset': '重置 Job 模版',
|
||||||
'system.resourcePool.addSuccess': '添加资源池成功',
|
'system.resourcePool.addSuccess': '添加资源池成功',
|
||||||
'system.resourcePool.updateSuccess': '更新资源池成功',
|
'system.resourcePool.updateSuccess': '更新资源池成功',
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,8 +11,16 @@
|
||||||
field="emails"
|
field="emails"
|
||||||
:label="t('system.user.inviteEmail')"
|
:label="t('system.user.inviteEmail')"
|
||||||
:rules="[{ required: true, message: t('system.user.createUserEmailNotNull') }]"
|
:rules="[{ required: true, message: t('system.user.createUserEmailNotNull') }]"
|
||||||
|
:validate-trigger="['blur', 'input']"
|
||||||
|
asterisk-position="end"
|
||||||
>
|
>
|
||||||
<a-input-tag v-model="emailForm.emails" :placeholder="t('system.user.inviteEmailPlaceholder')" allow-clear />
|
<MsTagsInput
|
||||||
|
v-model:model-value="emailForm.emails"
|
||||||
|
placeholder="system.user.inviteEmailPlaceholder"
|
||||||
|
allow-clear
|
||||||
|
unique-value
|
||||||
|
retain-input-value
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item class="mb-0" field="userGroup" :label="t('system.user.createUserUserGroup')">
|
<a-form-item class="mb-0" field="userGroup" :label="t('system.user.createUserUserGroup')">
|
||||||
<a-select
|
<a-select
|
||||||
|
@ -21,7 +29,15 @@
|
||||||
:placeholder="t('system.user.createUserUserGroupPlaceholder')"
|
:placeholder="t('system.user.createUserUserGroupPlaceholder')"
|
||||||
allow-clear
|
allow-clear
|
||||||
>
|
>
|
||||||
<a-option v-for="item of userGroupOptions" :key="item.value">{{ item.label }}</a-option>
|
<a-option
|
||||||
|
v-for="item of userGroupOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:tag-props="{ closable: emailForm.userGroup.length > 1 }"
|
||||||
|
:value="item.id"
|
||||||
|
:disabled="emailForm.userGroup.includes(item.id) && emailForm.userGroup.length === 1"
|
||||||
|
>
|
||||||
|
{{ item.name }}
|
||||||
|
</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
@ -37,13 +53,17 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
|
||||||
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
|
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||||
|
|
||||||
|
import type { SystemRole } from '@/models/setting/user';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
|
userGroupOptions: SystemRole[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits(['update:visible']);
|
const emit = defineEmits(['update:visible']);
|
||||||
|
@ -52,24 +72,10 @@
|
||||||
const inviteLoading = ref(false);
|
const inviteLoading = ref(false);
|
||||||
const inviteFormRef = ref<FormInstance | null>(null);
|
const inviteFormRef = ref<FormInstance | null>(null);
|
||||||
const defaultInviteForm = {
|
const defaultInviteForm = {
|
||||||
emails: [],
|
emails: [] as string[],
|
||||||
userGroup: [],
|
userGroup: [] as string[],
|
||||||
};
|
};
|
||||||
const emailForm = ref(cloneDeep(defaultInviteForm));
|
const emailForm = ref(cloneDeep(defaultInviteForm));
|
||||||
const userGroupOptions = ref([
|
|
||||||
{
|
|
||||||
label: 'Beijing',
|
|
||||||
value: 'Beijing',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Shanghai',
|
|
||||||
value: 'Shanghai',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Guangzhou',
|
|
||||||
value: 'Guangzhou',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
|
@ -85,6 +91,15 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.userGroupOptions,
|
||||||
|
(arr) => {
|
||||||
|
if (arr.length) {
|
||||||
|
emailForm.value.userGroup = arr.filter((e: SystemRole) => e.selected === true).map((e: SystemRole) => e.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
function cancelInvite() {
|
function cancelInvite() {
|
||||||
inviteVisible.value = false;
|
inviteVisible.value = false;
|
||||||
inviteFormRef.value?.resetFields();
|
inviteFormRef.value?.resetFields();
|
||||||
|
|
|
@ -26,10 +26,10 @@
|
||||||
@batch-action="handleTableBatch"
|
@batch-action="handleTableBatch"
|
||||||
>
|
>
|
||||||
<template #organization="{ record }">
|
<template #organization="{ record }">
|
||||||
<a-tooltip :content="record.organizationList.filter((e: any) => e).map((e: any) => e.name).join(',')">
|
<a-tooltip :content="record.organizationList.map((e: any) => e.name).join(',')">
|
||||||
<div>
|
<div>
|
||||||
<a-tag
|
<a-tag
|
||||||
v-for="org of record.organizationList.filter((e: any) => e).slice(0, 2)"
|
v-for="org of record.organizationList.slice(0, 2)"
|
||||||
:key="org.id"
|
:key="org.id"
|
||||||
class="mr-[4px] bg-transparent"
|
class="mr-[4px] bg-transparent"
|
||||||
bordered
|
bordered
|
||||||
|
@ -46,19 +46,19 @@
|
||||||
<a-tooltip :content="record.userRoleList.map((e: any) => e.name).join(',')">
|
<a-tooltip :content="record.userRoleList.map((e: any) => e.name).join(',')">
|
||||||
<div>
|
<div>
|
||||||
<a-tag
|
<a-tag
|
||||||
v-for="org of record.userRoleList.slice(0, 2)"
|
v-for="role of record.userRoleList.slice(0, 2)"
|
||||||
:key="org.id"
|
:key="role.id"
|
||||||
:class="['mr-[4px]', 'bg-transparent', record.enable ? 'enableTag' : 'disableTag']"
|
:class="['mr-[4px]', 'bg-transparent', record.enable ? 'enableTag' : 'disableTag']"
|
||||||
bordered
|
bordered
|
||||||
>
|
>
|
||||||
{{ org.name }}
|
{{ role.name }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
<a-tag
|
<a-tag
|
||||||
v-show="record.organizationList.length > 2"
|
v-show="record.userRoleList.length > 2"
|
||||||
:class="['mr-[4px]', 'bg-transparent', record.enable ? 'enableTag' : 'disableTag']"
|
:class="['mr-[4px]', 'bg-transparent', record.enable ? 'enableTag' : 'disableTag']"
|
||||||
bordered
|
bordered
|
||||||
>
|
>
|
||||||
+{{ record.organizationList.length - 2 }}
|
+{{ record.userRoleList.length - 2 }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</div>
|
</div>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
@ -92,7 +92,13 @@
|
||||||
:default-vals="userForm.list"
|
:default-vals="userForm.list"
|
||||||
max-height="250px"
|
max-height="250px"
|
||||||
></MsBatchForm>
|
></MsBatchForm>
|
||||||
<a-form-item class="mb-0" field="userGroup" :label="t('system.user.createUserUserGroup')">
|
<a-form-item
|
||||||
|
class="mb-0"
|
||||||
|
field="userGroup"
|
||||||
|
:label="t('system.user.createUserUserGroup')"
|
||||||
|
required
|
||||||
|
asterisk-position="end"
|
||||||
|
>
|
||||||
<a-select
|
<a-select
|
||||||
v-model="userForm.userGroup"
|
v-model="userForm.userGroup"
|
||||||
multiple
|
multiple
|
||||||
|
@ -102,9 +108,9 @@
|
||||||
<a-option
|
<a-option
|
||||||
v-for="item of userGroupOptions"
|
v-for="item of userGroupOptions"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:tag-props="{ closable: item.closeable }"
|
:tag-props="{ closable: userForm.userGroup.length > 1 }"
|
||||||
:value="item.id"
|
:value="item.id"
|
||||||
:disabled="item.selected"
|
:disabled="userForm.userGroup.includes(item.id) && userForm.userGroup.length === 1"
|
||||||
>
|
>
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</a-option>
|
</a-option>
|
||||||
|
@ -197,7 +203,7 @@
|
||||||
</a-button>
|
</a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<inviteModal v-model:visible="inviteVisible"></inviteModal>
|
<inviteModal v-model:visible="inviteVisible" :user-group-options="userGroupOptions"></inviteModal>
|
||||||
<batchModal
|
<batchModal
|
||||||
v-model:visible="showBatchModal"
|
v-model:visible="showBatchModal"
|
||||||
:table-selected="tableSelected"
|
:table-selected="tableSelected"
|
||||||
|
@ -248,7 +254,6 @@
|
||||||
{
|
{
|
||||||
title: 'system.user.tableColumnEmail',
|
title: 'system.user.tableColumnEmail',
|
||||||
dataIndex: 'email',
|
dataIndex: 'email',
|
||||||
width: 200,
|
|
||||||
showInTable: true,
|
showInTable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -283,18 +288,25 @@
|
||||||
title: 'system.user.tableColumnActions',
|
title: 'system.user.tableColumnActions',
|
||||||
slotName: 'action',
|
slotName: 'action',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 120,
|
|
||||||
showInTable: true,
|
showInTable: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const tableStore = useTableStore();
|
const tableStore = useTableStore();
|
||||||
tableStore.initColumn(TableKeyEnum.SYSTEM_USER, columns, 'drawer');
|
tableStore.initColumn(TableKeyEnum.SYSTEM_USER, columns, 'drawer');
|
||||||
const { propsRes, propsEvent, loadList, setKeyword } = useTable(getUserList, {
|
const { propsRes, propsEvent, loadList, setKeyword } = useTable(
|
||||||
tableKey: TableKeyEnum.SYSTEM_USER,
|
getUserList,
|
||||||
columns,
|
{
|
||||||
scroll: { y: 'auto' },
|
tableKey: TableKeyEnum.SYSTEM_USER,
|
||||||
selectable: true,
|
columns,
|
||||||
});
|
scroll: { y: 'auto' },
|
||||||
|
selectable: true,
|
||||||
|
},
|
||||||
|
(record) => ({
|
||||||
|
...record,
|
||||||
|
organizationList: record.organizationList.filter((e) => e),
|
||||||
|
userRoleList: record.userRoleList.filter((e) => e),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
|
|
||||||
|
@ -316,12 +328,12 @@
|
||||||
/**
|
/**
|
||||||
* 重置密码
|
* 重置密码
|
||||||
*/
|
*/
|
||||||
function resetPassword(record: any, isbatch?: boolean) {
|
function resetPassword(record: any, isBatch?: boolean) {
|
||||||
let title = t('system.user.resetPswTip', { name: characterLimit(record?.name) });
|
let title = t('system.user.resetPswTip', { name: characterLimit(record?.name) });
|
||||||
let userIdList = [record?.id];
|
let selectIds = [record?.id];
|
||||||
if (isbatch) {
|
if (isBatch) {
|
||||||
title = t('system.user.batchResetPswTip', { count: tableSelected.value.length });
|
title = t('system.user.batchResetPswTip', { count: tableSelected.value.length });
|
||||||
userIdList = tableSelected.value as string[];
|
selectIds = tableSelected.value as string[];
|
||||||
}
|
}
|
||||||
openModal({
|
openModal({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
@ -331,7 +343,11 @@
|
||||||
cancelText: t('system.user.resetPswCancel'),
|
cancelText: t('system.user.resetPswCancel'),
|
||||||
onBeforeOk: async () => {
|
onBeforeOk: async () => {
|
||||||
try {
|
try {
|
||||||
await resetUserPassword(userIdList);
|
await resetUserPassword({
|
||||||
|
selectIds,
|
||||||
|
selectAll: false,
|
||||||
|
condition: {},
|
||||||
|
});
|
||||||
Message.success(t('system.user.resetPswSuccess'));
|
Message.success(t('system.user.resetPswSuccess'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
@ -344,12 +360,12 @@
|
||||||
/**
|
/**
|
||||||
* 禁用用户
|
* 禁用用户
|
||||||
*/
|
*/
|
||||||
function disabledUser(record: any, isbatch?: boolean) {
|
function disabledUser(record: any, isBatch?: boolean) {
|
||||||
let title = t('system.user.disableUserTip', { name: characterLimit(record?.name) });
|
let title = t('system.user.disableUserTip', { name: characterLimit(record?.name) });
|
||||||
let userIdList = [record?.id];
|
let selectIds = [record?.id];
|
||||||
if (isbatch) {
|
if (isBatch) {
|
||||||
title = t('system.user.batchDisableUserTip', { count: tableSelected.value.length });
|
title = t('system.user.batchDisableUserTip', { count: tableSelected.value.length });
|
||||||
userIdList = tableSelected.value as string[];
|
selectIds = tableSelected.value as string[];
|
||||||
}
|
}
|
||||||
openModal({
|
openModal({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
@ -361,7 +377,9 @@
|
||||||
onBeforeOk: async () => {
|
onBeforeOk: async () => {
|
||||||
try {
|
try {
|
||||||
await toggleUserStatus({
|
await toggleUserStatus({
|
||||||
userIdList,
|
selectIds,
|
||||||
|
selectAll: false,
|
||||||
|
condition: {},
|
||||||
enable: false,
|
enable: false,
|
||||||
});
|
});
|
||||||
Message.success(t('system.user.disableUserSuccess'));
|
Message.success(t('system.user.disableUserSuccess'));
|
||||||
|
@ -377,12 +395,12 @@
|
||||||
/**
|
/**
|
||||||
* 启用用户
|
* 启用用户
|
||||||
*/
|
*/
|
||||||
function enableUser(record: any, isbatch?: boolean) {
|
function enableUser(record: any, isBatch?: boolean) {
|
||||||
let title = t('system.user.enableUserTip', { name: characterLimit(record?.name) });
|
let title = t('system.user.enableUserTip', { name: characterLimit(record?.name) });
|
||||||
let userIdList = [record?.id];
|
let selectIds = [record?.id];
|
||||||
if (isbatch) {
|
if (isBatch) {
|
||||||
title = t('system.user.batchEnableUserTip', { count: tableSelected.value.length });
|
title = t('system.user.batchEnableUserTip', { count: tableSelected.value.length });
|
||||||
userIdList = tableSelected.value as string[];
|
selectIds = tableSelected.value as string[];
|
||||||
}
|
}
|
||||||
openModal({
|
openModal({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
|
@ -394,7 +412,9 @@
|
||||||
onBeforeOk: async () => {
|
onBeforeOk: async () => {
|
||||||
try {
|
try {
|
||||||
await toggleUserStatus({
|
await toggleUserStatus({
|
||||||
userIdList,
|
selectIds,
|
||||||
|
selectAll: false,
|
||||||
|
condition: {},
|
||||||
enable: true,
|
enable: true,
|
||||||
});
|
});
|
||||||
Message.success(t('system.user.enableUserSuccess'));
|
Message.success(t('system.user.enableUserSuccess'));
|
||||||
|
@ -410,12 +430,12 @@
|
||||||
/**
|
/**
|
||||||
* 删除用户
|
* 删除用户
|
||||||
*/
|
*/
|
||||||
function deleteUser(record: any, isbatch?: boolean) {
|
function deleteUser(record: any, isBatch?: boolean) {
|
||||||
let title = t('system.user.deleteUserTip', { name: characterLimit(record?.name) });
|
let title = t('system.user.deleteUserTip', { name: characterLimit(record?.name) });
|
||||||
let userIdList = [record?.id];
|
let selectIds = [record?.id];
|
||||||
if (isbatch) {
|
if (isBatch) {
|
||||||
title = t('system.user.batchDeleteUserTip', { count: tableSelected.value.length });
|
title = t('system.user.batchDeleteUserTip', { count: tableSelected.value.length });
|
||||||
userIdList = tableSelected.value as string[];
|
selectIds = tableSelected.value as string[];
|
||||||
}
|
}
|
||||||
openModal({
|
openModal({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
|
@ -430,7 +450,9 @@
|
||||||
onBeforeOk: async () => {
|
onBeforeOk: async () => {
|
||||||
try {
|
try {
|
||||||
await deleteUserInfo({
|
await deleteUserInfo({
|
||||||
userIdList,
|
selectIds,
|
||||||
|
selectAll: false,
|
||||||
|
condition: {},
|
||||||
});
|
});
|
||||||
Message.success(t('system.user.deleteUserSuccess'));
|
Message.success(t('system.user.deleteUserSuccess'));
|
||||||
loadList();
|
loadList();
|
||||||
|
@ -601,7 +623,7 @@
|
||||||
userGroup: [],
|
userGroup: [],
|
||||||
};
|
};
|
||||||
const userForm = ref<UserForm>(cloneDeep(defaultUserForm));
|
const userForm = ref<UserForm>(cloneDeep(defaultUserForm));
|
||||||
const userGroupOptions = ref();
|
const userGroupOptions = ref<SystemRole[]>([]);
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -63,7 +63,7 @@ export default {
|
||||||
'system.user.invite': 'Email invite',
|
'system.user.invite': 'Email invite',
|
||||||
'system.user.inviteEmail': 'Email',
|
'system.user.inviteEmail': 'Email',
|
||||||
'system.user.inviteCancel': 'Cancel',
|
'system.user.inviteCancel': 'Cancel',
|
||||||
'system.user.inviteEmailPlaceholder': 'Enter multiple email addresses, separated by spaces or carriage returns',
|
'system.user.inviteEmailPlaceholder': 'Enter multiple email addresses, separated by carriage returns',
|
||||||
'system.user.inviteSendEmail': 'SendEmail',
|
'system.user.inviteSendEmail': 'SendEmail',
|
||||||
'system.user.inviteSuccess': 'Invitation successfully',
|
'system.user.inviteSuccess': 'Invitation successfully',
|
||||||
'system.user.importModalTitle': 'Import user',
|
'system.user.importModalTitle': 'Import user',
|
||||||
|
|
|
@ -62,7 +62,7 @@ export default {
|
||||||
'system.user.invite': '邮箱邀请',
|
'system.user.invite': '邮箱邀请',
|
||||||
'system.user.inviteEmail': '邮箱',
|
'system.user.inviteEmail': '邮箱',
|
||||||
'system.user.inviteCancel': '取消',
|
'system.user.inviteCancel': '取消',
|
||||||
'system.user.inviteEmailPlaceholder': '可输入多个邮箱地址,空格或回车分隔',
|
'system.user.inviteEmailPlaceholder': '可输入多个邮箱地址,回车分隔',
|
||||||
'system.user.inviteSendEmail': '发送邮件',
|
'system.user.inviteSendEmail': '发送邮件',
|
||||||
'system.user.inviteSuccess': '邀请成功',
|
'system.user.inviteSuccess': '邀请成功',
|
||||||
'system.user.importModalTitle': '导入用户',
|
'system.user.importModalTitle': '导入用户',
|
||||||
|
|
Loading…
Reference in New Issue