init: userbase front/service/deploy source (CI builds front via Gitea Actions)
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<div class="module-authz-tree">
|
||||
<div v-for="node in visibleNodes" :key="node.id" class="tree-node"
|
||||
:style="{ paddingLeft: `${(node.level || 1) * 18 - 18}px` }"
|
||||
:class="{ 'is-leaf': !node.children?.length, 'is-expanded': node.expand !== false }">
|
||||
<div class="node-main">
|
||||
<span v-if="node.children?.length" class="expand-icon" @click.stop="toggleExpand(node)">
|
||||
<Icon :type="node.expand === false ? 'ios-add' : 'ios-remove'" />
|
||||
</span>
|
||||
<span v-else class="expand-placeholder"></span>
|
||||
<Checkbox :model-value="!!node.checked"
|
||||
:disabled="loadingId === node.id && loadingType === 'check'"
|
||||
@on-change="(checked: boolean) => onCheckChange(node, checked)">
|
||||
<span class="node-name" :title="node.name">{{ node.name }}</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div class="node-inherit">
|
||||
<Checkbox :model-value="!!node.inheritFlag"
|
||||
:disabled="!node.checked || (loadingId === node.id && loadingType === 'inherit')"
|
||||
@on-change="(checked: boolean) => onInheritChange(node, checked)">
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive } from "vue";
|
||||
|
||||
export interface ModuleAuthzNode {
|
||||
id: number;
|
||||
name: string;
|
||||
level?: number;
|
||||
checked?: boolean;
|
||||
inheritFlag?: boolean;
|
||||
expand?: boolean;
|
||||
children?: ModuleAuthzNode[];
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
nodes: ModuleAuthzNode[];
|
||||
appId: number;
|
||||
groupId: number;
|
||||
loadingId?: number | null;
|
||||
loadingType?: "check" | "inherit" | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "module-change", payload: {
|
||||
appId: number;
|
||||
groupId: number;
|
||||
moduleId: number;
|
||||
checked: boolean;
|
||||
inheritFlag: boolean;
|
||||
type: "check" | "inherit";
|
||||
}): void;
|
||||
}>();
|
||||
|
||||
const expandMap = reactive<Record<number, boolean>>({});
|
||||
|
||||
function buildLocalNodes(nodes: ModuleAuthzNode[]): ModuleAuthzNode[] {
|
||||
return (nodes || []).map((n) => ({
|
||||
...n,
|
||||
expand: expandMap[n.id] ?? true,
|
||||
children: buildLocalNodes(n.children || []),
|
||||
}));
|
||||
}
|
||||
|
||||
const localNodes = computed<ModuleAuthzNode[]>(() => buildLocalNodes(props.nodes));
|
||||
|
||||
const visibleNodes = computed<ModuleAuthzNode[]>(() => {
|
||||
const result: ModuleAuthzNode[] = [];
|
||||
const walk = (list: ModuleAuthzNode[]) => {
|
||||
for (const node of list) {
|
||||
result.push(node);
|
||||
if (node.children?.length && node.expand !== false) {
|
||||
walk(node.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
walk(localNodes.value);
|
||||
return result;
|
||||
});
|
||||
|
||||
function toggleExpand(node: ModuleAuthzNode) {
|
||||
expandMap[node.id] = node.expand === false;
|
||||
}
|
||||
|
||||
function onCheckChange(node: ModuleAuthzNode, checked: boolean) {
|
||||
node.checked = checked;
|
||||
if (!checked) {
|
||||
node.inheritFlag = false;
|
||||
}
|
||||
emit("module-change", {
|
||||
appId: props.appId,
|
||||
groupId: props.groupId,
|
||||
moduleId: node.id,
|
||||
checked,
|
||||
inheritFlag: !!node.inheritFlag,
|
||||
type: "check",
|
||||
});
|
||||
}
|
||||
|
||||
function onInheritChange(node: ModuleAuthzNode, checked: boolean) {
|
||||
node.inheritFlag = checked;
|
||||
emit("module-change", {
|
||||
appId: props.appId,
|
||||
groupId: props.groupId,
|
||||
moduleId: node.id,
|
||||
checked: !!node.checked,
|
||||
inheritFlag: checked,
|
||||
type: "inherit",
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.module-authz-tree {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tree-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 4px 0;
|
||||
min-height: 32px;
|
||||
border-bottom: 1px dashed #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
}
|
||||
|
||||
.node-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
color: #2d8cf0;
|
||||
}
|
||||
}
|
||||
|
||||
.expand-placeholder {
|
||||
width: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.node-name {
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.node-inherit {
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user