review: 修改主机分组配置.

This commit is contained in:
lijiahangmax
2023-11-12 22:15:16 +08:00
parent d06c073999
commit 570ffd3ebc
11 changed files with 362 additions and 161 deletions

View File

@@ -14,6 +14,7 @@ import Breadcrumb from './app/breadcrumb/index.vue';
import Chart from './view/chart/index.vue';
import CardList from './view/card-list/index.vue';
import Editor from './view/editor/index.vue';
import TabRouter from './view/tab-router/index.vue';
use([
CanvasRenderer,
@@ -35,5 +36,6 @@ export default {
Vue.component('a-query-header', AQueryHeader);
Vue.component('card-list', CardList);
Vue.component('editor', Editor);
Vue.component('tab-router', TabRouter);
},
};

View File

@@ -0,0 +1,94 @@
<template>
<div class="simple-card tabs-container">
<template v-for="item in items"
:key="item.key">
<span v-permission="item.permission"
:title="item.text"
:class="['tab-item', item.key === modelValue ? 'tab-item-active' : '']"
@click="changeTab(item.key)">
<component v-if="item.icon"
:is="item.icon" />
{{ item.text }}
</span>
</template>
</div>
</template>
<script lang="ts">
export default {
name: 'tab-router'
};
</script>
<script lang="ts" setup>
import type { PropType } from 'vue';
import type { TabRouterItem } from './types';
import usePermission from '@/hooks/permission';
import { onMounted, } from 'vue';
const permission = usePermission();
const props = defineProps({
items: Array as PropType<Array<TabRouterItem>>,
modelValue: [String, Number]
});
const emits = defineEmits(['update:modelValue', 'change']);
// 切换 tab
const changeTab = (key: string | number) => {
if (key === props.modelValue) {
return;
}
emits('update:modelValue', key);
emits('change', key);
};
onMounted(() => {
// 获取有权限的 key
const keys = props.items?.filter(s => !s.permission || !s.permission.length || permission.hasAnyPermission(s.permission))
.map(s => s.key) || [];
if (!keys.length) {
return;
}
// 设置默认选中
if (keys.indexOf(props.modelValue as string | number) === -1) {
emits('update:modelValue', keys[0]);
}
});
</script>
<style lang="less" scoped>
.tabs-container {
display: flex;
flex-direction: column;
user-select: none;
}
.tab-item {
display: flex;
height: 32px;
margin: 12px 12px 0 12px;
align-items: center;
padding: 5px 16px;
cursor: pointer;
border-radius: 32px;
font-size: 14px;
color: var(--color-text-2);
svg {
margin-right: 4px;
font-size: 16px;
}
&:hover {
background: var(--color-fill-3);
}
}
.tab-item-active {
color: rgb(var(--primary-6));
background: var(--color-fill-2);
}
</style>

View File

@@ -0,0 +1,9 @@
/**
* tab 元素
*/
export interface TabRouterItem {
key: string | number,
text: string,
icon?: string;
permission?: string[]
}

9
orion-ops-ui/src/types/arco.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { TreeNodeData } from '@arco-design/web-vue';
import type { NodeData } from './global';
declare module '@arco-design/web-vue' {
interface TreeNodeData extends NodeData {
[key: string]: any;
}
}

View File

@@ -30,6 +30,10 @@ export interface Pagination {
limit?: number;
}
export interface NodeData {
[key: string]: any;
}
export interface DataGrid<T> {
page: number;
limit: number;

View File

@@ -1,50 +1,50 @@
<template>
<div class="tree-container">
<a-tree
:blockNode="true"
:draggable="props.editMode"
:data="treeData">
<a-tree
class="tree-container"
:blockNode="true"
:draggable="true"
:data="treeData">
<template #title="node">
<template v-if="node.editable">
<a-input size="mini"
ref="renameInput"
v-model="currName"
:max-length="32"
:disabled="node.loading"
@blur="() => saveNode(node.key)"
@pressEnter="() => saveNode(node.key)"
@change="() => saveNode(node.key)">
<template #suffix>
<!-- 加载中 -->
<icon-loading v-if="node.loading" />
<!-- 保存 -->
<icon-check v-else
class="pointer"
title="保存"
@click="saveNode(node.key)" />
</template>
</a-input>
</template>
<template #title="node">
<template v-if="node.editable">
<a-input size="mini"
v-model="currName"
:max-length="32"
:disabled="node.loading"
autofocus
@change="() => saveNode(node.key)">
<template #suffix>
<!-- 加载中 -->
<icon-loading v-if="node.loading" />
<!-- 保存 -->
<icon-check v-else
class="pointer"
title="保存"
@click="saveNode(node.key)" />
</template>
</a-input>
</template>
<span class="node-title" v-else>
<span class="node-title" v-else>
{{ node.title }}
</span>
</template>
<!-- 操作图标 -->
<template #drag-icon="{ node }">
<a-space v-if="!node.editable">
<icon-edit class="tree-icon"
title="重命名"
</template>
<!-- 操作图标 -->
<template #drag-icon="{ node }">
<a-space v-if="!node.editable">
<icon-edit class="tree-icon"
title="重命名"
@click="rename(node.title, node.key)" />
<icon-delete class="tree-icon"
title="删除"
@click="rename(node.title, node.key)" />
<icon-delete class="tree-icon"
title="删除"
@click="rename(node.title, node.key)" />
<icon-plus class="tree-icon"
title="新增"
@click="rename(node.title, node.key)" />
</a-space>
</template>
</a-tree>
</div>
<icon-plus class="tree-icon"
title="新增"
@click="rename(node.title, node.key)" />
</a-space>
</template>
</a-tree>
</template>
<script lang="ts">
@@ -54,25 +54,25 @@
</script>
<script lang="ts" setup>
import type { TreeNodeData } from '@arco-design/web-vue';
import type { NodeData } from '@/types/global';
import { nextTick, ref } from 'vue';
import { TagProps } from '@/store/modules/tab-bar/types';
import { TreeNodeData } from '@arco-design/web-vue';
const props = defineProps({
editMode: Boolean
});
const renameInput = ref();
const currName = ref();
function sleep(ms) {
// 提为工具 utils tree.js
function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 保存节点
const saveNode = async (key: string) => {
// 寻找节点
const node = findNode(key, treeData.value);
const node = findNode<TreeNodeData>(key, treeData.value);
if (!node) {
return;
}
if (currName.value) {
node.loading = true;
try {
@@ -100,13 +100,19 @@
// 重命名
const rename = (title: string, key: string) => {
const node = findNode(key, treeData.value);
const node = findNode<TreeNodeData>(key, treeData.value);
if (!node) {
return;
}
currName.value = title;
node.editable = true;
nextTick(() => {
renameInput.value?.focus();
});
};
// 寻找当前节点
const findNode = (id: string, arr: Array<TreeNodeData>): TreeNodeData | undefined => {
const findNode = <T extends NodeData>(id: string, arr: Array<T>): T | undefined => {
for (let node of arr) {
if (node.key === id) {
return node;
@@ -117,7 +123,7 @@
if (node?.children?.length) {
const inChildNode = findNode(id, node.children);
if (inChildNode) {
return inChildNode;
return inChildNode as T;
}
}
}
@@ -240,6 +246,14 @@
user-select: none;
}
:deep(.arco-tree-node-selected) {
.arco-tree-node-title {
&:hover {
background-color: var(--color-fill-2);
}
}
}
:deep(.arco-tree-node-title) {
padding-right: 48px;

View File

@@ -0,0 +1,19 @@
<template>
<div v-for="i in 300">
host-group-view-role-gra <br>
</div>
</template>
<script lang="ts">
export default {
name: 'host-group-view-role-grant'
};
</script>
<script lang="ts" setup>
</script>
<style lang="less" scoped>
</style>

View File

@@ -0,0 +1,81 @@
<template>
<!-- 左侧菜单 -->
<div class="simple-card tree-card">
<!-- 主机分组头部 -->
<div class="tree-card-header">
<!-- 标题 -->
<div class="tree-card-title">
主机菜单
</div>
</div>
<!-- 主机分组树 -->
<div class="tree-card-main">
<host-group-tree ref="tree" />
</div>
</div>
<!-- 身体部分 -->
<div class="simple-card view-body">
右侧数据
</div>
</template>
<script lang="ts">
export default {
name: 'host-group-view-setting'
};
</script>
<script lang="ts" setup>
import HostGroupTree from './host-group-tree.vue';
</script>
<style lang="less" scoped>
@tree-width: 26%;
@tree-width: 50%;
.tree-card {
margin-right: 16px;
width: @tree-width;
height: 100%;
position: absolute;
&-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 16px;
position: relative;
width: 100%;
height: 44px;
border-bottom: 1px var(--color-border-2) solid;
}
&-title {
color: rgba(var(--gray-10), .95);
font-size: 16px;
font-weight: 600;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
&-main {
padding: 8px 8px 8px 16px;
position: relative;
width: 100%;
height: calc(100% - 48px);
overflow: auto;
}
}
.view-body {
display: flex;
height: 100%;
padding: 16px;
width: calc(100% - @tree-width - 16px);
position: absolute;
left: calc(@tree-width + 16px);
}
</style>

View File

@@ -0,0 +1,19 @@
<template>
<div>
host-group-view-user-grant
</div>
</template>
<script lang="ts">
export default {
name: 'host-group-view-user-grant'
};
</script>
<script lang="ts" setup>
</script>
<style lang="less" scoped>
</style>

View File

@@ -1,55 +1,23 @@
<template>
<div class="view-container">
<!-- 左侧菜单 -->
<div class="simple-card tree-card">
<!-- 主机分组标题 -->
<div class="tree-card-header">
<!-- 标题 -->
<div class="tree-card-title">
主机菜单
</div>
<div class="tree-card-switch">
<a-switch type="round"
v-model="editMode"
checked-text="编辑模式"
unchecked-text="授权模式" />
</div>
</div>
<!-- 主机分组树 -->
<div class="tree-card-main">
<host-group-tree ref="tree" :editMode="editMode" />
</div>
</div>
<!-- 身体部分 -->
<div class="view-body">
<div class="simple-card fixed-header">
<!-- 头部左侧 -->
<a-space>
<a-select placeholder="选择角色" />
<a-select placeholder="选择用户" />
</a-space>
<!-- 头部右侧 -->
<a-space>
<!-- 配置 -->
<a-button type="primary">
配置
<template #icon>
<icon-layers />
</template>
</a-button>
<!-- 授权 -->
<a-button type="primary">
授权
<template #icon>
<icon-safe />
</template>
</a-button>
</a-space>
</div>
<!-- 右侧数据 -->
<div class="simple-card data-content">
右侧数据
</div>
<!-- 左侧导航 -->
<tab-router class="left-tabs"
v-model="key"
:items="tabItems" />
<!-- 右侧内容 -->
<div class="view-main">
<!-- 分组配置 -->
<template v-if="key === tabItemKeys.SETTING">
<host-group-view-setting />
</template>
<!-- 角色分配 -->
<template v-if="key === tabItemKeys.ROLE_GRANT">
<host-group-view-role-grant />
</template>
<!-- 用户分配 -->
<template v-if="key === tabItemKeys.USER_GRANT">
<host-group-view-user-grant />
</template>
</div>
</div>
</template>
@@ -61,15 +29,19 @@
</script>
<script lang="ts" setup>
import HostGroupTree from './host-group-tree.vue';
import { ref } from 'vue';
import { tabItems, tabItemKeys } from '../types/const';
import HostGroupViewSetting from './host-group-view-setting.vue';
import HostGroupViewRoleGrant from './host-group-view-role-grant.vue';
import HostGroupViewUserGrant from './host-group-view-user-grant.vue';
const key = ref();
const editMode = ref(true);
</script>
<style lang="less" scoped>
@tree-width: 50%;
@tab-width: 138px;
.view-container {
display: flex;
@@ -78,64 +50,18 @@
position: relative;
}
.tree-card {
margin-right: 16px;
width: @tree-width;
height: 100%;
position: absolute;
&-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 16px;
position: relative;
width: 100%;
height: 44px;
border-bottom: 1px var(--color-border-2) solid;
}
&-title {
color: rgba(var(--gray-10), .95);
font-size: 16px;
font-weight: 600;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
&-main {
padding: 8px 8px 8px 16px;
position: relative;
width: 100%;
height: calc(100% - 48px);
overflow: auto;
}
}
.view-body {
.left-tabs {
display: flex;
flex-direction: column;
width: @tab-width;
height: 100%;
width: calc(100% - @tree-width - 16px);
position: absolute;
left: calc(@tree-width + 16px);
margin-right: 16px;
}
.fixed-header {
display: flex;
justify-content: space-between;
margin-bottom: 16px;
padding: 8px 16px;
height: 48px;
}
.data-content {
display: flex;
padding: 16px;
width: 100%;
height: 100%;
}
.view-main {
width: calc(100% - @tab-width - 16px);
height: 100%;
position: relative;
}
</style>

View File

@@ -0,0 +1,24 @@
// 导航 key
export const tabItemKeys = {
SETTING: 1,
ROLE_GRANT: 2,
USER_GRANT: 3
};
// 导航路径
export const tabItems = [{
key: tabItemKeys.SETTING,
text: '分组配置',
icon: 'icon-unordered-list',
permission: []
}, {
key: tabItemKeys.ROLE_GRANT,
text: '角色授权',
icon: 'icon-safe',
permission: []
}, {
key: tabItemKeys.USER_GRANT,
text: '用户授权',
icon: 'icon-user',
permission: []
}];