zhengyiming
2025-11-11 ec837d5a83494a81f4b48f66c6e3ef005b70940c
fix: bug
4个文件已修改
3个文件已添加
807 ■■■■■ 已修改文件
.eslintrc-auto-import.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
auto-imports.d.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/constants/apiEnumText.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/common/tree.ts 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Permission/RoleManage.vue 276 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Permission/components/AddOrEditRoleDialog.vue 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Permission/components/dialogAuthorizeV2.vue 355 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.eslintrc-auto-import.json
@@ -40,6 +40,7 @@
    "EnumEnterpriseCostType": true,
    "EnumEnterpriseRealMethod": true,
    "EnumEnterpriseType": true,
    "EnumEnterpriseTypeText": true,
    "EnumEnterpriseWalletAccess": true,
    "EnumEnterpriseWalletAccessText": true,
    "EnumEnterpriseWalletAccessTextForSettle": true,
auto-imports.d.ts
@@ -43,6 +43,7 @@
  const EnumEnterpriseCostType: typeof import('./src/constants/apiEnum')['EnumEnterpriseCostType']
  const EnumEnterpriseRealMethod: typeof import('./src/constants/apiEnum')['EnumEnterpriseRealMethod']
  const EnumEnterpriseType: typeof import('./src/constants/apiEnum')['EnumEnterpriseType']
  const EnumEnterpriseTypeText: typeof import('./src/constants/apiEnumText')['EnumEnterpriseTypeText']
  const EnumEnterpriseWalletAccess: typeof import('./src/constants/apiEnum')['EnumEnterpriseWalletAccess']
  const EnumEnterpriseWalletAccessText: typeof import('./src/constants/enterpriseWallet')['EnumEnterpriseWalletAccessText']
  const EnumEnterpriseWalletAccessTextForSettle: typeof import('./src/constants/task')['EnumEnterpriseWalletAccessTextForSettle']
@@ -372,6 +373,7 @@
    readonly EnumEnterpriseCostType: UnwrapRef<typeof import('./src/constants/apiEnum')['EnumEnterpriseCostType']>
    readonly EnumEnterpriseRealMethod: UnwrapRef<typeof import('./src/constants/apiEnum')['EnumEnterpriseRealMethod']>
    readonly EnumEnterpriseType: UnwrapRef<typeof import('./src/constants/apiEnum')['EnumEnterpriseType']>
    readonly EnumEnterpriseTypeText: UnwrapRef<typeof import('./src/constants/apiEnumText')['EnumEnterpriseTypeText']>
    readonly EnumEnterpriseWalletAccess: UnwrapRef<typeof import('./src/constants/apiEnum')['EnumEnterpriseWalletAccess']>
    readonly EnumEnterpriseWalletAccessText: UnwrapRef<typeof import('./src/constants/enterpriseWallet')['EnumEnterpriseWalletAccessText']>
    readonly EnumEnterpriseWalletAccessTextForSettle: UnwrapRef<typeof import('./src/constants/task')['EnumEnterpriseWalletAccessTextForSettle']>
src/constants/apiEnumText.ts
@@ -11,6 +11,11 @@
  [EnumUserType.Operation]: '运营',
};
export const EnumEnterpriseTypeText = {
  [EnumEnterpriseType.Supplier]: '承揽',
  [EnumEnterpriseType.PartyA]: '甲方',
};
export const EnumRoleWebApiDataPowerText = {
  [EnumRoleWebApiDataPower.Custom]: '自定义',
  [EnumRoleWebApiDataPower.Creator]: '个人数据',
src/utils/common/tree.ts
@@ -1,3 +1,5 @@
import { TreeNodeData } from 'element-plus/es/components/tree/src/tree.type';
export function getTree(
  data: API.ModuleDto[],
  root?: string,
@@ -44,3 +46,27 @@
  return result;
}
type TreeCallback<T extends TreeNodeData, R> = (
  data: T,
  index: number,
  array: T[],
  parent?: T
) => R;
export function treeEach<T extends TreeNodeData>(
  treeData: T[],
  callback: TreeCallback<T, void>,
  getChildren: (data: T) => T[],
  parent?: T
) {
  for (let i = 0; i < treeData.length; i++) {
    const data = treeData[i];
    callback(data, i, treeData, parent);
    const children = getChildren(data);
    if (Array.isArray(children)) {
      treeEach(children, callback, getChildren, data);
    }
  }
}
src/views/Permission/RoleManage.vue
New file
@@ -0,0 +1,276 @@
<template>
  <LoadingLayout :loading="state.loading">
    <AppContainer>
      <ProTableQueryFilterBar @on-reset="reset">
        <template #query>
          <QueryFilterItem>
            <SearchInput
              v-model="extraParamState.queryCondition"
              style="width: 200px"
              placeholder="角色名称"
              @on-click-search="getList"
              @keyup.enter="getList()"
            >
            </SearchInput>
          </QueryFilterItem>
        </template>
        <template #btn>
          <el-button
            v-if="checkSubModuleItemShow('pageButton', 'addBtn')"
            @click="openDialog()"
            icon="Plus"
            type="primary"
            >新增</el-button
          >
        </template>
      </ProTableQueryFilterBar>
      <ProTableV2 v-bind="proTableProps" :columns="column" :operationBtns="operationBtns">
      </ProTableV2>
    </AppContainer>
    <AddOrEditRoleDialog v-bind="dialogProps" />
    <DialogAuthorizeV2 v-bind="dialogAuthorizeProps" authorizeType="Role" />
    <!-- <DialogMember v-model:visibleId="rowState.setMemberRoleId" /> -->
  </LoadingLayout>
</template>
<script setup lang="ts">
import {
  ProTableQueryFilterBar,
  OperationBtnType,
  ProTableV2,
  SearchInput,
  LoadingLayout,
  AppContainer,
  QueryFilterItem,
  useTable,
  useFormDialog,
  FieldRadio,
} from '@bole-core/components';
import { useAccess } from '@/hooks';
import { Message } from '@bole-core/core';
import AddOrEditRoleDialog from './components/AddOrEditRoleDialog.vue';
import { EnumUserType } from '@/constants';
import DialogAuthorizeV2 from './components/dialogAuthorizeV2.vue';
import * as roleServices from '@/services/api/role';
defineOptions({
  name: 'RoleManage',
});
const operationBtnMap: Record<string, OperationBtnType> = {
  editBtn: { emits: { onClick: (role) => openDialog(role) } },
  delBtn: { emits: { onClick: (role) => handleDeleteRole(role) }, props: { type: 'danger' } },
  authorize: { emits: { onClick: (role) => openAuthorizeDialog(role) } },
  // member: { emits: { onClick: (role) => openMemberDialog(role) } },
  disabledBtn: {
    emits: { onClick: (role) => roleEnableOrForbid(role) },
    props: { type: 'danger' },
    extraProps: {
      hide: (row) => row.isDisabled,
    },
  },
  enableBtn: {
    emits: { onClick: (role) => roleEnableOrForbid(role) },
    extraProps: {
      hide: (row) => !row.isDisabled,
    },
  },
};
const { checkSubModuleItemShow, column, operationBtns } = useAccess({
  operationBtnMap,
});
const BaseState = {
  loading: true,
};
const state = reactive({ ...BaseState });
onMounted(async () => {
  await getList();
  state.loading = false;
});
const {
  getDataSource: getList,
  proTableProps,
  paginationState,
  extraParamState,
  reset,
} = useTable(
  async ({ pageIndex, pageSize }, extraParamState) => {
    try {
      let params: API.GetRolesQuery = {
        pageModel: {
          rows: pageSize,
          page: pageIndex,
          orderInput: extraParamState.orderInput,
        },
        userType: AppLocalConfig.userType,
        clientType: AppLocalConfig.clientType,
        enterpriseType: AppLocalConfig.enterpriseType,
        keywords: extraParamState.queryCondition,
      };
      let res = await roleServices.getRoles(params, {
        showLoading: !state.loading,
      });
      return res;
    } catch (error) {}
  },
  {
    defaultExtraParams: {
      queryCondition: '',
      orderInput: [{ property: 'id', order: EnumPagedListOrder.Desc }],
    },
    queryKey: ['roleServices/getRoles'],
    columnsRenderProps: {
      dataPower: { type: 'enum', valueEnum: EnumRoleWebApiDataPowerText },
    },
  }
);
async function openDialog(row?: API.GetRolesQueryResultItem) {
  try {
    if (row) {
      const detail = await roleServices.getRole({ id: row.id });
      handleEdit({
        id: row.id,
        name: row.name,
        remark: row.remark,
        userType: row.userType,
        clientType: row.clientType,
        dataRange: row.dataPower,
        detail: detail,
        minLevel: row.minLevel,
        enterpriseType: row.enterpriseType,
      });
    } else {
      handleAdd({
        userType: AppLocalConfig.userType,
        clientType: AppLocalConfig.clientType,
      });
    }
  } catch (error) {}
}
const { dialogProps, handleAdd, handleEdit, editForm, dialogState } = useFormDialog({
  onConfirm: handleAddOrEdit,
  defaultFormParams: {
    id: '',
    name: '',
    remark: '',
    userType: AppLocalConfig.userType,
    clientType: AppLocalConfig.clientType,
    dataRange: EnumRoleWebApiDataPower.All,
    detail: null as API.GetRoleQueryResult,
    minLevel: 1,
    enterpriseType: AppLocalConfig.enterpriseType,
  },
});
async function handleAddOrEdit() {
  try {
    const isEdit = editForm.id;
    let params: API.SaveRoleCommand = {
      name: editForm.name,
      remark: editForm.remark,
      dataPower: editForm.dataRange,
      userType: editForm.userType,
      clientType: editForm.clientType,
      minLevel: editForm.minLevel,
    };
    if (editForm.userType === EnumUserType.Enterprise) {
      params.enterpriseType = editForm.enterpriseType;
    }
    if (isEdit) {
      params = {
        ...editForm.detail,
        ...params,
      };
    }
    let res = await roleServices.saveRole(params);
    if (res) {
      Message.successMessage('操作成功');
      getList(isEdit ? paginationState.pageIndex : 1);
    }
  } catch (error) {}
}
async function handleDeleteRole(row: API.GetRolesQueryResultItem) {
  try {
    await Message.deleteMessage();
    let params: API.DeleteRoleCommand = {
      ids: [row.id],
    };
    let res = await roleServices.deleteRole(params);
    if (res) {
      Message.successMessage('操作成功');
      getList(paginationState.pageIndex);
    }
  } catch (error) {}
}
async function roleEnableOrForbid(row: API.GetRolesQueryResultItem) {
  try {
    await Message.tipMessage(`是否${row.isDisabled ? '启用' : '禁用'}角色`);
    let res = await roleServices.setRoleIsDisabled({
      ids: [row.id],
      isDisabled: !row.isDisabled,
    });
    if (res) {
      Message.successMessage('操作成功');
      getList(paginationState.pageIndex);
      return !!res;
    }
  } catch (error) {}
}
const rowState = reactive({
  authorizeId: '',
  setMemberRoleId: '',
});
const {
  dialogProps: dialogAuthorizeProps,
  handleAdd: handleAuthorizeAdd,
  editForm: authorizeForm,
} = useFormDialog({
  onConfirm: handleAuthorize,
  defaultFormParams: {
    detail: null as API.GetRoleQueryResult,
  },
});
async function openAuthorizeDialog(row: API.GetRolesQueryResultItem) {
  try {
    const detail = await roleServices.getRole({ id: row.id });
    handleAuthorizeAdd({
      detail: detail,
    });
  } catch (error) {}
}
async function handleAuthorize(selectedMenuIds: string[]) {
  console.log('selectedMenuIds: ', selectedMenuIds);
  try {
    let params: API.SaveRoleCommand = {
      ...authorizeForm.detail,
      menuIds: selectedMenuIds,
      // resources: resourceIds.map((x) => ({
      //   resourceId: x,
      //   dataPower: EnumRoleWebApiDataPower.All,
      // })),
    };
    let res = await roleServices.saveRole(params);
    if (res) {
      Message.successMessage('操作成功');
      getList(paginationState.pageIndex);
    }
  } catch (error) {}
}
// function openMemberDialog(row: API.IdentityRoleDto) {
//   rowState.setMemberRoleId = row.id;
// }
</script>
src/views/Permission/components/AddOrEditRoleDialog.vue
New file
@@ -0,0 +1,142 @@
<template>
  <ProDialog
    :title="innerForm.title"
    v-model="innerVisible"
    @close="onDialogClose"
    destroy-on-close
    draggable
  >
    <ProForm :rules="rules" :model="innerForm" ref="dialogForm" label-width="110px">
      <ProFormItemV2 label="角色名称" prop="name">
        <ProFormText placeholder="请输入角色名称" v-model.trim="innerForm.name"></ProFormText>
      </ProFormItemV2>
      <ProFormItemV2 label="等级" prop="minLevel">
        <ProFormInputNumber
          v-model="innerForm.minLevel"
          :min="1"
          :max="100"
          :controls="false"
        ></ProFormInputNumber>
      </ProFormItemV2>
      <!-- <ProFormItemV2
        label="企业类型"
        prop="enterpriseType"
        v-if="form.userType === EnumUserType.Enterprise"
      >
        <ProFormRadio
          v-model="form.enterpriseType"
          :value-enum="EnumEnterpriseTypeText"
          :buttonStyle="false"
        ></ProFormRadio>
      </ProFormItemV2> -->
      <ProFormItemV2 label="数据可见范围" prop="dataRange">
        <ProFormRadio
          v-model="form.dataRange"
          :value-enum="EnumRoleWebApiDataPowerTextForFilter"
          :buttonStyle="false"
        ></ProFormRadio>
      </ProFormItemV2>
      <ProFormItemV2 label="备注:" prop="remark">
        <ProFormTextArea
          v-model="innerForm.remark"
          placeholder="请输入备注"
          show-word-limit
          :maxlength="2000"
        ></ProFormTextArea>
      </ProFormItemV2>
    </ProForm>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="emit('onCancel')">取 消</el-button>
        <el-button type="primary" @click="handleConfirm">确 定</el-button>
      </span>
    </template>
  </ProDialog>
</template>
<script setup lang="ts">
import { FormRules, FormInstance } from 'element-plus';
import {
  ProDialog,
  ProForm,
  ProFormItemV2,
  ProFormText,
  ProFormRadio,
  ProFormTextArea,
  ProFormInputNumber,
} from '@bole-core/components';
import {
  EnumRoleWebApiDataPowerTextForFilter,
  EnumEnterpriseTypeText,
  EnumUserType,
} from '@/constants';
defineOptions({
  name: 'AddOrEditRoleDialog',
});
type Props = {
  modelValue: boolean;
  form: {
    id: string;
    title?: string;
    name: string;
    remark: string;
    dataRange: EnumRoleWebApiDataPower;
    minLevel: number;
    enterpriseType: EnumEnterpriseType;
    userType: EnumUserType;
  };
};
const props = withDefaults(defineProps<Props>(), {
  modelValue: false,
});
const emit = defineEmits<{
  (e: 'update:modelValue', value: boolean): void;
  (e: 'update:form', value: Props['form']): void;
  (e: 'onConfirm'): void;
  (e: 'onCancel'): void;
}>();
const dialogForm = ref<FormInstance>();
const innerVisible = computed({
  get() {
    return props.modelValue;
  },
  set(val) {
    emit('update:modelValue', val);
  },
});
const innerForm = computed({
  get() {
    return props.form;
  },
  set(val) {
    emit('update:form', val);
  },
});
const rules = reactive<FormRules>({
  name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
});
function onDialogClose() {
  if (!dialogForm.value) return;
  dialogForm.value.resetFields();
}
function handleConfirm() {
  if (!dialogForm.value) return;
  dialogForm.value.validate((valid) => {
    if (valid) {
      emit('onConfirm');
    } else {
      return;
    }
  });
}
</script>
src/views/Permission/components/dialogAuthorizeV2.vue
New file
@@ -0,0 +1,355 @@
<template>
  <ProDialog
    class="custom-dialog"
    width="55%"
    :title="dialogTitle"
    v-model="visible"
    :top="'10vh'"
    :close-on-click-modal="false"
    :close-on-press-escape="false"
  >
    <div class="authorize-wrapper">
      <div class="container-wrapper left-wrapper">
        <el-scrollbar>
          <el-tree
            class="companyTree"
            :data="menusTree"
            :props="{
              children: 'children',
              label: 'name',
            }"
            node-key="id"
            :expand-on-click-node="false"
            :highlight-current="true"
            default-expand-all
            show-checkbox
            ref="moduleTree"
            :default-checked-keys="defaultCheckedKeys"
            @check="handleModuleCheck"
          >
            <template #default="{ node }">
              <div class="custom-tree-node">
                <type-tip :isMenu="node.data.type === EnumMenuType.Menu" />
                <div class="node-text" @click="handleSelectModule(node.data)">
                  {{ node.label }}
                </div>
              </div>
            </template>
          </el-tree>
        </el-scrollbar>
      </div>
      <div class="container-wrapper">
        <div class="type-wrapper">
          {{ SubModuleTitle[SubModuleType.PageButton] }}
        </div>
        <el-scrollbar>
          <el-tree
            v-show="!!state.currentMenuId"
            :data="[
              {
                name: '全选',
                id: 'pageButtonAll',
                children: menuPageButtons,
              },
            ]"
            :props="{
              children: 'children',
              label: 'name',
            }"
            node-key="id"
            :expand-on-click-node="false"
            :highlight-current="true"
            default-expand-all
            show-checkbox
            ref="pageButtonTree"
            :default-checked-keys="defaultCheckedKeys"
            @check="handlePageButtonCheck"
          >
            <template #default="{ node }">
              <div class="custom-tree-node">
                <span>{{ node.label }}</span>
              </div>
            </template>
          </el-tree>
        </el-scrollbar>
      </div>
      <div class="container-wrapper">
        <div class="type-wrapper">
          {{ SubModuleTitle[SubModuleType.DataButton] }}
        </div>
        <el-scrollbar>
          <el-tree
            v-show="!!state.currentMenuId"
            :data="[
              {
                name: '全选',
                id: 'dataButtonAll',
                children: menuDataButtons,
              },
            ]"
            :props="{
              children: 'children',
              label: 'name',
            }"
            node-key="id"
            :expand-on-click-node="false"
            :highlight-current="true"
            default-expand-all
            show-checkbox
            ref="dataButtonTree"
            :default-checked-keys="defaultCheckedKeys"
            @check="handleDataButtonCheck"
          >
            <template #default="{ node }">
              <div class="custom-tree-node">
                <span>{{ node.label }}</span>
              </div>
            </template>
          </el-tree>
        </el-scrollbar>
      </div>
      <div class="container-wrapper">
        <div class="type-wrapper">
          {{ SubModuleTitle[SubModuleType.Column] }}
        </div>
        <el-scrollbar>
          <el-tree
            v-show="!!state.currentMenuId"
            :data="[
              {
                name: '全选',
                id: 'dataColumnAll',
                children: menuFields,
              },
            ]"
            :props="{
              children: 'children',
              label: 'name',
            }"
            node-key="id"
            :expand-on-click-node="false"
            :highlight-current="true"
            default-expand-all
            show-checkbox
            ref="dataColumnTree"
            :default-checked-keys="defaultCheckedKeys"
            @check="handleDataColumnCheck"
          >
            <template #default="{ node }">
              <div class="custom-tree-node">
                <span>{{ node.label }}</span>
              </div>
            </template>
          </el-tree>
        </el-scrollbar>
      </div>
    </div>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="visible = false">取消</el-button>
        <el-button type="primary" @click="handleConfirm" class="btn-submit"> 确定 </el-button>
      </span>
    </template>
  </ProDialog>
</template>
<script setup lang="ts">
import { Message } from '@bole-core/core';
import {  SubModuleType, SubModuleTitle, EnumMenuType } from '@/constants';
import { getTree, treeEach } from '@/utils';
import { TreeInstance } from 'element-plus';
import { ProDialog } from '@bole-core/components';
import { useMenus, useMenu } from '@/hooks';
import data from '@iconify-icons/ep/tickets';
const TypeTip = defineComponent({
  name: 'TypeTip',
  props: ['isMenu'],
  render() {
    const { isMenu } = this;
    const tipText = isMenu ? `菜单` : '页面';
    return h(
      'span',
      {
        class: 'tip-text',
        style: {
          color: '#EB6100',
        },
      },
      '[' + tipText + ']'
    );
  },
});
type Props = {
  authorizeType: 'Role' | 'User';
};
const props = withDefaults(defineProps<Props>(), {});
const visible = defineModel({ type: Boolean });
type Form = {
  title?: string;
  detail: API.GetRoleQueryResult;
};
const form = defineModel<Form>('form');
const emit = defineEmits<{
  (e: 'onConfirm', selectedMenuIds: string[]): void;
  (e: 'onCancel'): void;
}>();
type CheckedResourceItem = {
  resourceId: string;
  menuId: string;
  resourceType: SubModuleType;
};
const state = reactive({
  currentMenuId: '',
  selectedMenuIds: [] as string[],
});
const defaultCheckedKeys = computed(() => Array.from(state.selectedMenuIds));
watch(visible, (newVal) => {
  if (newVal) {
    state.currentMenuId = '';
    const menuIds = form.value.detail?.menuIds ?? [];
    // menuIds.forEach((id) => state.selectedMenuIds.add(id));
    state.selectedMenuIds = [...menuIds];
  }
});
const dialogTitle = computed(() =>
  props.authorizeType === 'Role' ? '角色功能授权' : '账号功能授权'
);
const moduleTree = useTemplateRef<TreeInstance>('moduleTree');
const pageButtonTree = useTemplateRef<TreeInstance>('pageButtonTree');
const dataButtonTree = useTemplateRef<TreeInstance>('dataButtonTree');
const dataColumnTree = useTemplateRef<TreeInstance>('dataColumnTree');
type TreeRef = ReturnType<typeof useTemplateRef<TreeInstance>>;
const { menusTree, getMenuById } = useMenus({
  params: computed(() => ({
    userType: form.value.detail?.userType,
    clientType: form.value.detail?.clientType,
    roleId: form.value.detail?.id,
  })),
  enabled: computed(() => !!form.value.detail?.id),
});
const { menuFields, menuPageButtons, menuDataButtons } = useMenu({
  params: computed(() => ({
    id: state.currentMenuId,
    roleId: form.value.detail?.id,
  })),
  enabled: computed(() => !!state.currentMenuId),
});
function handleSelectModule(menu: API.GetMenusQueryResultItem) {
  state.currentMenuId = menu.id;
}
function handleModuleCheck(data, params) {
  handleCheck(data, params, moduleTree);
}
function handlePageButtonCheck(data, params) {
  handleCheck(data, params, pageButtonTree);
}
function handleDataButtonCheck(data, params) {
  handleCheck(data, params, dataButtonTree);
}
function handleDataColumnCheck(data, params) {
  handleCheck(data, params, dataColumnTree);
}
function handleCheck(data, params, treeRef: TreeRef) {
  const dataMap = {};
  treeEach(
    [treeRef.value.store.root],
    (node) => (dataMap[node.key] = node),
    (node) => node.childNodes
  );
  const uncachedCheckedKeys = params.checkedKeys.filter(
    (item) => !['pageButtonAll', 'dataButtonAll', 'dataColumnAll'].includes(item)
  );
  const cachedKeys = state.selectedMenuIds.filter(
    (item) => !(item in dataMap) && !uncachedCheckedKeys.includes(item)
  );
  state.selectedMenuIds = cachedKeys.concat(uncachedCheckedKeys);
}
function handleConfirm() {
  emit('onConfirm', state.selectedMenuIds);
}
</script>
<style lang="scss" scoped>
:deep(.custom-dialog) {
  min-width: 900px;
}
.authorize-wrapper {
  display: flex;
  height: 500px;
  border-bottom: 2px solid #f5f5f5;
  background: #dddddd;
  .container-wrapper {
    width: calc(25%);
    border-right: 2px solid #f5f5f5;
    // margin-right: 7px;
    background: #ffffff;
    .type-wrapper {
      display: flex;
      justify-content: flex-start;
      align-items: center;
      padding: 10px 16px;
      border-bottom: 2px solid #f5f5f5;
      color: #333333;
    }
    &:last-of-type {
      margin-right: 0px;
    }
    &.left-wrapper {
      margin-right: 7px;
      padding-top: 10px;
      width: calc(35% - 7px);
      :deep(.el-scrollbar) {
        width: 100%;
        height: 98%;
      }
    }
    :deep(.el-scrollbar) {
      width: 100%;
      height: calc(98% - 43px);
      .el-scrollbar__wrap {
        overflow: auto;
        .custom-tree-node {
          display: flex;
        }
      }
    }
  }
}
</style>