|
@@ -0,0 +1,294 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import { onMounted, ref, useTemplateRef, watch } from 'vue';
|
|
|
+
|
|
|
+import ConfirmModal from '@/components/ConfirmModal.vue';
|
|
|
+import SvgIcon from '@/components/SvgIcon.vue';
|
|
|
+import { useRequest } from '@/hooks/request';
|
|
|
+import { t } from '@/i18n';
|
|
|
+import { getSubPermList } from '@/api';
|
|
|
+
|
|
|
+import type { TreeProps } from 'ant-design-vue';
|
|
|
+import type { Rule } from 'ant-design-vue/es/form';
|
|
|
+import type { DataNode } from 'ant-design-vue/es/tree';
|
|
|
+import type { CharacterItem, TreeStructure } from '@/types';
|
|
|
+
|
|
|
+const rules: Record<string, Rule[]> = {
|
|
|
+ name: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
|
|
|
+};
|
|
|
+
|
|
|
+const { handleRequest } = useRequest();
|
|
|
+const modalComponentRef = useTemplateRef('modalComponent');
|
|
|
+const characterList = ref<CharacterItem[]>([]);
|
|
|
+const characterOpen = ref<boolean>(false);
|
|
|
+const checked = ref<boolean>(false);
|
|
|
+const checkedAll = ref<number[]>([]);
|
|
|
+const indeterminate = ref<boolean>(false);
|
|
|
+const characterIndex = ref<number>(0);
|
|
|
+const expandedKeys = ref<number[]>([]);
|
|
|
+const selectedKeys = ref<number[]>([]);
|
|
|
+const checkedKeys = ref<number[]>([]);
|
|
|
+const fieldNames: TreeProps['fieldNames'] = {
|
|
|
+ children: 'subPermissions',
|
|
|
+ title: 'menuName',
|
|
|
+ key: 'id',
|
|
|
+};
|
|
|
+const treeStructure = ref<DataNode[]>([]);
|
|
|
+const characterForm = ref({
|
|
|
+ name: '',
|
|
|
+});
|
|
|
+const addCharacter = () => {
|
|
|
+ characterOpen.value = true;
|
|
|
+};
|
|
|
+const characterSave = () => {
|
|
|
+ characterList.value.push({
|
|
|
+ name: '',
|
|
|
+ id: undefined,
|
|
|
+ });
|
|
|
+ characterOpen.value = false;
|
|
|
+};
|
|
|
+const cancelSave = () => {
|
|
|
+ checkedKeys.value = [];
|
|
|
+ characterOpen.value = false;
|
|
|
+};
|
|
|
+const confirm = () => {
|
|
|
+ modalComponentRef.value?.hideView();
|
|
|
+ characterList.value.splice(characterIndex.value, 1);
|
|
|
+};
|
|
|
+const characterEditor = () => {};
|
|
|
+const characterDelete = (index: number) => {
|
|
|
+ characterIndex.value = index;
|
|
|
+ modalComponentRef.value?.showView();
|
|
|
+};
|
|
|
+const addMenu = () => {
|
|
|
+ console.log(checkedKeys.value);
|
|
|
+ console.log(checkedAll.value);
|
|
|
+ if (checkedAll.value.length !== checkedKeys.value.length) {
|
|
|
+ indeterminate.value = true;
|
|
|
+ } else {
|
|
|
+ indeterminate.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+const selectAll = () => {
|
|
|
+ if (checked.value) {
|
|
|
+ checkedKeys.value = getAllKeys(treeStructure.value);
|
|
|
+ if (checkedKeys.value.length) {
|
|
|
+ checkedAll.value = checkedKeys.value;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ checkedKeys.value = [];
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 获取所有节点 key 的递归方法
|
|
|
+const getAllKeys = (data: DataNode[]) => {
|
|
|
+ let keys: number[] = [];
|
|
|
+ data.forEach((item) => {
|
|
|
+ keys.push(item.id);
|
|
|
+ if (item.subPermissions) {
|
|
|
+ keys = keys.concat(getAllKeys(item.subPermissions));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return keys;
|
|
|
+};
|
|
|
+
|
|
|
+const transformTreeData = (data: TreeStructure[]): DataNode[] => {
|
|
|
+ return data.map((item) => ({
|
|
|
+ ...item,
|
|
|
+ key: item.id, // 关键:将 id 映射到 key
|
|
|
+ title: item.menuName,
|
|
|
+ children: item.subPermissions ? transformTreeData(item.subPermissions) : undefined,
|
|
|
+ }));
|
|
|
+};
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => checkedAll.value,
|
|
|
+ (count) => {
|
|
|
+ if (count) {
|
|
|
+ console.log(count);
|
|
|
+ }
|
|
|
+ },
|
|
|
+);
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ handleRequest(async () => {
|
|
|
+ const data = await getSubPermList(0);
|
|
|
+ const treeData = transformTreeData(data);
|
|
|
+ treeStructure.value = treeData[0].subPermissions;
|
|
|
+ });
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ <div class="character"><span class="character-text">*</span>创建角色</div>
|
|
|
+ <AFlex :vertical="true" :gap="16">
|
|
|
+ <AFlex align="center">
|
|
|
+ <AFlex class="input-style" align="center"> 管理员 </AFlex>
|
|
|
+ <div class="input-style-text">默认角色</div>
|
|
|
+ </AFlex>
|
|
|
+ <AFlex align="center">
|
|
|
+ <AFlex class="input-style" align="center"> 运维人员 </AFlex>
|
|
|
+ <div class="input-style-text">默认角色</div>
|
|
|
+ </AFlex>
|
|
|
+ <AFlex align="center" v-for="(item, index) in characterList" :key="index">
|
|
|
+ <AFlex class="input-style input-background" align="center"> 角色 </AFlex>
|
|
|
+ <div @click="characterEditor">
|
|
|
+ <AFlex class="editorial-role" align="center" justify="center">
|
|
|
+ <SvgIcon name="edit-o" />
|
|
|
+ </AFlex>
|
|
|
+ </div>
|
|
|
+ <div @click="characterDelete(index)">
|
|
|
+ <AFlex class="editorial-role" align="center" justify="center">
|
|
|
+ <SvgIcon class="icon-color" name="delete" />
|
|
|
+ </AFlex>
|
|
|
+ </div>
|
|
|
+ </AFlex>
|
|
|
+ </AFlex>
|
|
|
+ <AButton type="primary" ghost class="icon-button button-style" @click="addCharacter">
|
|
|
+ <AFlex align="center">
|
|
|
+ <SvgIcon name="plus" />
|
|
|
+ <span> 增加角色 </span>
|
|
|
+ </AFlex>
|
|
|
+ </AButton>
|
|
|
+ <AModal v-model:open="characterOpen" title="添加角色" width="920px" :mask-closable="false" :footer="null">
|
|
|
+ <AForm
|
|
|
+ ref="formRef"
|
|
|
+ class="alarm-modal"
|
|
|
+ :model="characterForm"
|
|
|
+ label-align="left"
|
|
|
+ :rules="rules"
|
|
|
+ :label-col="{ span: 3 }"
|
|
|
+ >
|
|
|
+ <AFormItem label="角色名称" name="name">
|
|
|
+ <AInput class="input-width" v-model:value="characterForm.name" placeholder="请输入角色名称" />
|
|
|
+ </AFormItem>
|
|
|
+ <AFormItem label="角色描述">
|
|
|
+ <ATextarea
|
|
|
+ class="input-width"
|
|
|
+ v-model:value="characterForm.name"
|
|
|
+ :placeholder="t('common.pleaseEnter')"
|
|
|
+ :auto-size="{ minRows: 4 }"
|
|
|
+ />
|
|
|
+ </AFormItem>
|
|
|
+ <AFormItem label="菜单权限配置">
|
|
|
+ <div class="permission-configuration">
|
|
|
+ <ACheckbox class="select-all" v-model:checked="checked" @change="selectAll">全选</ACheckbox>
|
|
|
+ <ATree
|
|
|
+ v-model:expanded-keys="expandedKeys"
|
|
|
+ v-model:selected-keys="selectedKeys"
|
|
|
+ v-model:checked-keys="checkedKeys"
|
|
|
+ :tree-data="treeStructure"
|
|
|
+ checkable
|
|
|
+ :field-names="fieldNames"
|
|
|
+ @check="addMenu"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </AFormItem>
|
|
|
+ </AForm>
|
|
|
+ <AFlex justify="flex-end" class="footer">
|
|
|
+ <AButton class="button-right" type="primary" ghost @click="cancelSave">{{ $t('common.cancel') }}</AButton>
|
|
|
+ <AButton type="primary" @click="characterSave">保存</AButton>
|
|
|
+ </AFlex>
|
|
|
+ </AModal>
|
|
|
+ <ConfirmModal
|
|
|
+ ref="modalComponent"
|
|
|
+ :title="t('common.deleteConfirmation')"
|
|
|
+ :description-text="t('common.confirmDeletion')"
|
|
|
+ :icon="{ name: 'delete' }"
|
|
|
+ :icon-bg-color="'#F56C6C'"
|
|
|
+ @confirm="confirm"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.select-all {
|
|
|
+ margin-bottom: 12px;
|
|
|
+ margin-left: 10px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-style: normal;
|
|
|
+ font-weight: 400;
|
|
|
+ line-height: 24px;
|
|
|
+ color: #000;
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.permission-configuration {
|
|
|
+ width: 772px;
|
|
|
+ height: 294px;
|
|
|
+ padding: 16px 14px 24px;
|
|
|
+ overflow: auto;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #d9d9d9;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.input-width {
|
|
|
+ width: 328px;
|
|
|
+}
|
|
|
+
|
|
|
+.button-right {
|
|
|
+ margin-right: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.button-style {
|
|
|
+ margin-top: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.icon-color {
|
|
|
+ color: #f67f7f;
|
|
|
+}
|
|
|
+
|
|
|
+.editorial-role {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ margin-right: 12px;
|
|
|
+ cursor: pointer;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #d9d9d9;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.character {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ color: rgb(0 0 0 / 85%);
|
|
|
+}
|
|
|
+
|
|
|
+.input-style-text {
|
|
|
+ font-size: 12px;
|
|
|
+ font-style: normal;
|
|
|
+ font-weight: 400;
|
|
|
+ line-height: 22px;
|
|
|
+ color: #666;
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.input-style {
|
|
|
+ width: 256px;
|
|
|
+ height: 32px;
|
|
|
+ padding-left: 12px;
|
|
|
+ margin-right: 16px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-style: normal;
|
|
|
+ font-weight: 400;
|
|
|
+ line-height: 22px;
|
|
|
+ color: #333;
|
|
|
+ text-align: left;
|
|
|
+ background: #f5f7fa;
|
|
|
+ border: 1px solid rgb(0 0 0 / 15%);
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.input-background {
|
|
|
+ background: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.character-text {
|
|
|
+ font-size: 14px;
|
|
|
+ font-style: normal;
|
|
|
+ font-weight: 400;
|
|
|
+ line-height: 22px;
|
|
|
+ color: #e02020;
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+</style>
|