Kaynağa Gözat

菜单管理 租户权限接口更换 角色管理bug

@dongkboy 6 ay önce
ebeveyn
işleme
b16c522414

+ 4 - 1
src/api/index.ts

@@ -16,6 +16,8 @@ import product from "./modules/product"
 import configurate from './modules/configurate.ts'
 import urlApi from './modules/url.ts'
 import appConfigApi from './modules/appConfig.ts'//app应用配置
+import menuApi  from './modules/menu.ts'
+
 const axiosInstance:ApiInstance = axios.create({
   baseURL: (import.meta.env.DEV && import.meta.env.VITE_OPEN_PROXY === 'true') ? '/proxy/' : import.meta.env.VITE_APP_API_BASEURL,
   timeout: 1000 * 60,
@@ -106,6 +108,7 @@ const api = {
   product:product(axiosInstance),
   configurate: configurate(axiosInstance),
   urlApi: urlApi(axiosInstance),
-  appConfigApi: appConfigApi(axiosInstance)
+  appConfigApi: appConfigApi(axiosInstance),
+  menuApi: menuApi(axiosInstance)
 }
 export default api

+ 12 - 0
src/api/modules/menu.ts

@@ -0,0 +1,12 @@
+import { ApiInstance } from '../type'
+const memberApi = (axiosInstance: ApiInstance) => ({
+
+  //获取菜单树
+  findMenuTree: () => axiosInstance.get('menu/findMenuTree'),
+  //新增菜单
+  addMenu: (data: any) => axiosInstance.post('menu/save', data),
+  //删除菜单
+  deleteMenu: (id: number) => axiosInstance.delete(`menu/delete/${id}`),
+})
+
+export default memberApi

+ 4 - 4
src/api/modules/tenant.ts

@@ -28,10 +28,10 @@ const tenantApi = (axiosInstance: ApiInstance) => ({
   getSystemPerms: (params: any) => axiosInstance.get('system/getSystemPerms', {params}),
   //修改租户权限
   updatePerms: (data: any) => axiosInstance.post('system/updatePerms', data),
-  //获取租户平台菜单数据
-  getSystemConfig: (params:any) => axiosInstance.get('system/getInitConfig',{params}),
-  // 获取租户菜单权限
-  getTenantConfig: (params:any) => axiosInstance.get('system/getInitConfig',{params}),
+  //获取租户菜单树
+  getSystemMenu: (params:any) => axiosInstance.get('menu/getSystemMenu',{params}),
+  //保存租户菜单权限
+  saveSystemMenu: (data:any) => axiosInstance.post('menu/saveSystemMenu', data),
   //租户手机号校验
   userGetPhone: (params:any) => axiosInstance.get(`system/userGet?mobile=${params}`)
 })

+ 1 - 0
src/components/SvgIcon/index.vue

@@ -16,6 +16,7 @@ const props = defineProps<{
 
 const outputType = computed(() => {
   const hasPathFeatures = (str: string) => {
+    if (typeof str !== 'string') return false
     return /^\.{1,2}\//.test(str) || str.startsWith('/') || str.includes('/')
   }
   if (/^https?:\/\//.test(props.name) || hasPathFeatures(props.name) || !props.name) {

+ 6 - 3
src/components/TableTree/index.vue

@@ -19,7 +19,10 @@
         <el-table-column v-if="showIndex" type="index" label="序号" width="55" />
         <el-table-column v-for="column in columns" :key="column.prop" :prop="column.prop" :label="column.label"
           :sortable="column.sortable ? 'custom' : false" :width="column.width" :align="column.align?column.align:'center'" >
-          <template #default="scope">
+          <template #default="scope" v-if="column.component === 'customSlot'">
+            <slot name="customSlot" :data="scope.row"  :bodyCell="{ scope, column }"></slot>
+          </template>
+          <template #default="scope" v-else>
             <RouterLink v-if="column.component === 'RouterLink'" :to="getRouterLinkTo(scope.row, column)" class="custom-link">
               {{ scope.row[column.prop] }}
             </RouterLink>
@@ -49,7 +52,7 @@
 </template>
 
 <script lang="ts" setup>
-import { defineProps, ref, onMounted, defineEmits } from "vue";
+import {  ref, onMounted } from "vue";
 import type { TableInstance } from "element-plus";
 
 interface TableRow {
@@ -99,7 +102,7 @@ onMounted(() => {
       const offsetTop = tableElement.offsetTop; // 获取 offsetTop
       let height = window.innerHeight;
       console.log(height)
-      tableHeight.value = height - offsetTop - 300 + "px";
+      tableHeight.value = height - offsetTop - 230 + "px";
     }
   }, 10);
 

+ 11 - 8
src/components/tablev2/index.vue

@@ -18,7 +18,10 @@
         <el-table-column v-if="showIndex" type="index" label="序号" width="55" />
         <el-table-column v-for="column in columns" :key="column.prop" :prop="column.prop" :label="column.label"
           :sortable="column.sortable ? 'custom' : false" :width="column.width" :align="column.align?column.align:'center'" >
-          <template #default="scope">
+          <template #default="scope" v-if="column.component === 'customSlot'">
+            <slot name="customSlot" :data="scope.row"  :bodyCell="{ scope, column }"></slot>
+          </template>
+          <template #default="scope" v-else>
             <RouterLink v-if="column.component === 'RouterLink'" :to="getRouterLinkTo(scope.row, column)" class="custom-link">
               {{ scope.row[column.prop] }}
             </RouterLink>
@@ -27,7 +30,7 @@
             </el-tag>
             <ImagePreview v-else-if="column.component === 'Image'" :src="getImgTo(scope.row, column)" width="50px" height="50px"></ImagePreview>
             <span v-else>
-              {{ getDisplayText(scope.row, column) }}
+              {{ getDisplayText(scope.row, column) }}2323
             </span>
           </template>
           <template #header>
@@ -48,7 +51,7 @@
 </template>
 
 <script lang="ts" setup>
-import { defineProps, ref, onMounted, defineEmits } from "vue";
+import {  ref, onMounted,  } from "vue";
 import type { TableInstance } from "element-plus";
 
 interface TableRow {
@@ -73,7 +76,7 @@ const props = defineProps<{
     style?: string;
     align?:string,
     component?:string,
-    formatter?: (row: any, column: any, cellValue: any, index: number) => any;
+    cellRenderer?: (row: any, column: any, cellValue: any, index: number) => any;
 
   }[];
   showSelect: boolean;
@@ -111,17 +114,17 @@ const displayValue = (value: any) => {
 const selectable = () => true;
 // 格式化内容配置
 function getRouterLinkTo(row: Row, column: any): RouterLink {
-  return column.formatter ? column.formatter(row, column, row[column.prop]) : { name: 'OrganizationAdd' };
+  return column.cellRenderer ? column.cellRenderer(row, column, row[column.prop]) : { name: 'OrganizationAdd' };
 }
 function getImgTo(row: Row, column: any) {
   return row[column.prop];
 }
 function getTagTypeAndText(row: Row, column: any) {
-  // 使用 formatter 返回一个对象,包含 type 和 text
-  return column.formatter ? column.formatter(row, column, row[column.prop]) : { type: 'primary', text: row[column.prop] };
+  // 使用 cellRenderer 返回一个对象,包含 type 和 text
+  return column.cellRenderer ? column.cellRenderer(row, column, row[column.prop]) : { type: 'primary', text: row[column.prop] };
 }
 function getDisplayText(row: Row, column: any): string {
-  return column.formatter ? column.formatter(row, column, row[column.prop]) :displayValue(row[column.prop]);
+  return column.cellRenderer ? column.cellRenderer(row, column, row[column.prop]) :displayValue(row[column.prop]);
 }
 </script>
 <style lang="scss" scoped>

+ 8 - 0
src/router/modules/organization.ts

@@ -78,6 +78,14 @@ const routes: RouteRecordRaw = {
         title: '功能配置',
       },
     },
+    {
+      path: 'menuManagement',
+      name: 'MenuManagementIndex',
+      component: () => import('@/views/organizationManagement/menuManagement.vue'),
+      meta: {
+        title: '菜单管理',
+      },
+    },
   ],
 }
 

+ 486 - 0
src/views/organizationManagement/menuManagement.vue

@@ -0,0 +1,486 @@
+<!-- 模板区域 -->
+<template>
+  <div>
+    <PageMain class="vh100">
+      <el-form :inline="true" :model="formInline" class="demo-form-inline">
+        <el-form-item label="菜单名称">
+          <el-input v-model="formInline.user" placeholder="请输入菜单名称" clearable />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="Menutree">
+            <template #icon>
+              <SvgIcon name="i-ep:search" />
+            </template>
+            查询</el-button>
+        </el-form-item>
+      </el-form>
+      <el-button type="primary" @click="menuAdd">
+        <template #icon>
+          <el-icon>
+            <Plus />
+          </el-icon>
+        </template>
+        添加菜单</el-button>
+      <div :style="{ height: tableHeight, marginTop: '20px' }">
+        <el-auto-resizer>
+          <template #default="{ height, width }">
+            <el-table-v2 :columns="columns" :data="tableData" :width="width" :height="height" v-loading="loading"
+              ref="multipleTableRef" :expand-column-key="expandColumnKey" header-class="header-class">
+              <template #cell="scope">
+                <template v-if="scope.column.key == 'option'" class="flex j-s a-c">
+                  <el-button type="primary" link @click="menuAddSub(scope.rowData)"> 添加子菜单
+                  </el-button>
+                  <el-dropdown placement="bottom-start">
+                    <span class="el-dropdown-link">
+                      更 多
+                    </span>
+                    <template #dropdown>
+                      <el-dropdown-menu>
+                        <el-dropdown-item @click="menuEdit(scope.rowData)">编辑</el-dropdown-item>
+                        <el-dropdown-item @click="menuDel(scope.rowData)">删除</el-dropdown-item>
+                      </el-dropdown-menu>
+                    </template>
+                  </el-dropdown>
+                </template>
+                <template v-if="scope.column.key == 'type'">
+                  <span>{{typelist.find(item => item.value == scope.rowData.type)?.label || ''}}</span>
+                </template>
+                <template v-if="scope.column.key == 'icon'">
+                  <SvgIcon :name="scope.rowData.icon || ''" />
+                </template>
+                <template v-if="scope.column.key == 'menu'">
+                  <el-tag v-if="scope.rowData.menu==1" type="primary">显示</el-tag>
+                  <el-tag v-if="scope.rowData.menu==0" type="danger">隐藏</el-tag>
+
+                </template>
+              </template>
+            </el-table-v2>
+          </template>
+        </el-auto-resizer>
+      </div>
+    </PageMain>
+    <!-- 添加菜单弹窗 -->
+    <el-drawer v-model="menuAddShow" direction="rtl" size="800px">
+      <template #header>
+        <h4>菜单配置</h4>
+      </template>
+      <template #default>
+        <el-form :model="menuAddForm" :rules="menuAddRules" ref="menuAddFormRef" label-width="120px">
+          <el-form-item label="菜单类型" prop="type">
+            <el-radio-group v-model="menuAddForm.type" fill="#409eff">
+              <el-radio-button v-for="item in typelist" :label="item.label" :value="item.value"
+                :key="item.value"></el-radio-button>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item :label="`${typeLabel}名称`" prop="title">
+            <el-input v-model="menuAddForm.title" :placeholder="`请输入${typeLabel}名称`" clearable />
+          </el-form-item>
+          <el-form-item label="上级菜单" prop="parentId" v-if="menuAddForm.type != 0">
+            <el-tree-select v-model="menuAddForm.parentId" :data="tableData" node-key="id" :props="defaultProps"
+              check-strictly :render-after-expand="false" />
+          </el-form-item>
+          <el-form-item label="路由标识" prop="name" v-if="menuAddForm.type == 1">
+            <el-input v-model="menuAddForm.name" placeholder="请输入路由标识"  clearable />
+          </el-form-item>
+          <el-form-item label="访问路径" prop="path" v-if="menuAddForm.type == 1">
+            <el-input v-model="menuAddForm.path" placeholder="请输入访问路径" clearable
+              @blur="menuAddForm.name = generateRouteName(menuAddForm.path)" />
+          </el-form-item>
+          <el-form-item label="组件" prop="component" v-if="menuAddForm.type == 1">
+            <el-input v-model="menuAddForm.component" placeholder="请输入组件" clearable />
+          </el-form-item>
+          <el-form-item label="菜单图标" v-if="menuAddForm.type != 2">
+            <el-input v-model="menuAddForm.icon" placeholder="请选择菜单图标" @click="iconVisible = true" clearable readonly />
+          </el-form-item>
+          <el-form-item label="按钮权限标识" prop="perms" v-if="menuAddForm.type == 2">
+            <el-input v-model="menuAddForm.perms" placeholder="请输入按钮权限标识" clearable />
+          </el-form-item>
+          <el-form-item label="菜单高亮地址" v-if="menu">
+            <el-input v-model="menuAddForm.activeMenu" placeholder="请输入菜单高亮地址" clearable />
+          </el-form-item>
+          <el-form-item label="隐藏路由" prop="menu" v-if="menuAddForm.type != 2">
+            <el-switch v-model="menu" @change="handleMenuChange" />
+          </el-form-item>
+        </el-form>
+      </template>
+      <template #footer>
+        <div style="flex: auto;">
+          <el-button @click="menuAddShow = false">取消</el-button>
+          <el-button type="primary" @click="confirmClick(menuAddFormRef)">确认</el-button>
+        </div>
+      </template>
+    </el-drawer>
+    <!-- 图标弹窗 -->
+    <el-dialog v-model="iconVisible" title="选择图标" width="1200px" height="800px " top="8vh">
+      <div>
+        <!-- 图标搜索框 -->
+        <el-input v-model="iconSearchQuery" placeholder="搜索图标名称" class="mb-4" clearable @input="currentPage = 1">
+          <template #prefix>
+            <el-icon>
+              <Search />
+            </el-icon>
+          </template>
+        </el-input>
+        <el-tabs v-model="activeName" class="demo-tabs" @tab-change="currentPage = 1">
+          <el-tab-pane :label="icon.info.name + '图标'" :name="icon.prefix" v-for="(icon, key) in icons" :key="key">
+            <!-- 计算当前标签页的过滤和分页后的图标 -->
+            <div v-if="getFilteredIcons(icon).length === 0" class="text-center py-10 text-gray-500">
+              未找到匹配的图标
+            </div>
+            <div v-else>
+              <div class="icon-container flex f-wrap">
+                <div class="icon-item flex a-c j-c " v-for="(item, index) in getPaginatedIcons(getFilteredIcons(icon))"
+                  :key="index" :class="{ 'icon-selected': selectedIcon === `${icon.prefix}:${item}` }"
+                  @click="selectedIcon = `${icon.prefix}:${item}`">
+                  <SvgIcon :name="`${icon.prefix}:${item}`" class="icon" />
+                </div>
+              </div>
+              <!-- 分页组件 -->
+              <div class="flex justify-center mt-4">
+                <el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize"
+                  :page-sizes="[20, 50, 100]" layout="total, sizes, prev, pager, next, jumper"
+                  :total="getFilteredIcons(icon).length" @size-change="currentPage = 1" />
+              </div>
+            </div>
+          </el-tab-pane>
+        </el-tabs>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="iconVisible = false">取消</el-button>
+          <el-button type="primary" @click="confirmSelectIcon">
+            确定
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<!-- 行为区域 -->
+<script lang='ts' setup>
+import { ref, reactive, computed } from 'vue'
+import { FormRules, FormInstance, ComponentSize, ElMessageBox } from 'element-plus';
+import api from '@/api'
+import { ElTag, ElMessage } from 'element-plus';
+import { icons } from '@/iconify'
+import { Search, Plus } from '@element-plus/icons-vue'
+interface Column {
+  key?: string,
+  dataKey?: string,
+  title: string,
+  width: number,
+  class?: string,
+  cellRenderer?: any
+  align?: "left" | "center" | "right"; // align 是可选的,且只能是这三个值之一
+}
+const defaultProps = {
+  children: 'children',
+  label: 'title',
+}
+onMounted(() => {
+  setTimeout(() => {
+    const tableElement = multipleTableRef.value?.$el as HTMLElement;
+    if (tableElement) {
+      const offsetTop = tableElement.offsetTop; // 获取 offsetTop
+      let height = window.innerHeight;
+      tableHeight.value = height - offsetTop - 190 + "px";
+    }
+  }, 10);
+})
+onBeforeMount(() => {
+  Menutree()//获取菜单树
+
+})
+const menuAddFormRef = ref<FormInstance>()
+const multipleTableRef = ref();//多个表格引用
+const iconVisible = ref(false);//图标弹窗显示
+const menuAddShow = ref(false);//添加菜单弹窗显示
+const activeName = ref('ep');//当前选中的tab
+const selectedIcon = ref('');//当前选中的图标
+const iconSearchQuery = ref('');//图标搜索关键词
+const currentPage = ref(1);//当前页码
+const pageSize = ref(50);//每页显示图标数量
+const tableHeight = ref(''); // 初始化表格高度
+const loading = ref(false)
+const expandColumnKey = 'title'//标记可展开列
+const menu = ref(false);
+
+
+
+// 定义查询表单数据
+const formInline = reactive({
+  user: '',
+});
+
+// 定义添加菜单表单数据
+const menuAddForm = reactive({
+  path: '',//路径
+  component: '',//组件
+  parentId: '',//上级菜单
+  name: '',//菜单名称
+  title: '',//菜单标题
+  icon: '',//图标
+  type: 0,//菜单类型
+  activeMenu: '',//高亮菜单路由地址
+  perms: '',//按钮权限标识
+  menu: 1,
+});
+
+// 定义添加菜单表单验证规则
+const menuAddRules = reactive({
+  path: [{ required: true, message: '请输入路径', trigger: 'blur' }],
+  component: [{ required: true, message: '请输入组件', trigger: 'blur' }],
+  redirect: [{ required: false, message: '请输入重定向路径', trigger: 'blur' }],
+  name: [{ required: true, message: '路由标识必填', trigger: 'blur' }],
+  title: [{ required: true, message: '请输入菜单标题', trigger: 'blur' }],
+  perms: [{ required: true, message: '请输入按钮权限标识', trigger: 'blur' }],
+  type: [{ required: true, message: '请选择菜单类型', trigger: 'change' }],
+  parentId: [{ required: true, message: '请选择上级菜单', trigger: 'change' }],
+});
+const columns: Column[] = [
+  { key: 'title', dataKey: "title", title: "菜单名称", width: 200, },
+  { key: 'type', dataKey: "type", title: "菜单类型", width: 100, align: "center", },
+  { key: 'icon', dataKey: "icon", title: "图标", width: 100, align: "center" },
+  { key: 'component', dataKey: "component", title: "组件", width: 300, align: "center" },
+  { key: 'path', dataKey: "path", title: "页面路径", width: 300, align: "center" },
+  {
+    key: 'menu', dataKey: "menu", title: "路由是否隐藏", width: 150, align: "center",
+  },
+  {
+    key: 'option',
+    title: "操作",
+    width: 250,
+    align: 'center',
+  },
+]
+const tableData = ref([]);//菜单列表
+const typeLabel = ref('');//菜单类型标签
+const typelist = [
+  { label: '目录', value: 0 },
+  { label: '菜单', value: 1 },
+  { label: '按钮', value: 2 },
+]
+// 根据path生成驼峰命名的路由标识
+const generateRouteName = (path: string): string => {
+  if (!path || path === '/') return ''
+
+  // 移除开头的斜杠和所有非字母数字字符
+  const cleanPath = path.replace(/^\/+/, '').replace(/[^a-zA-Z0-9_\-\/]/g, '')
+
+  // 按斜杠分割并转换为驼峰命名
+  return cleanPath.split('/')
+    .map(segment => {
+      // 处理下划线或连字符分隔的单词
+      return segment.split(/[-_]/)
+        .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
+        .join('')
+    })
+    .join('')
+}
+// 处理菜单隐藏路由切换
+const handleMenuChange = (val: any) => {
+  console.log(val)
+  if (val) {
+    menuAddForm.menu = 0;
+  } else {
+    menuAddForm.menu = 1;
+  }
+}
+// 菜单添加子菜单
+const menuAddSub = (item: any) => {
+  menuAddForm.parentId = item.id;
+  menuAddForm.type = item.type;
+  menuAddShow.value = true;
+}
+//编辑菜单
+const menuEdit = (item: any) => {
+  const info = item;
+  Object.assign(menuAddForm, info)
+  console.log(menuAddForm)
+  if (menuAddForm.menu == 1) {
+    menu.value = false
+  } else {
+    menu.value = true
+  }
+  menuAddShow.value = true
+}
+// 删除菜单
+const menuDel = (item: any) => {
+  ElMessageBox.confirm('确定删除该菜单吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  }).then(() => {
+    api.menuApi.deleteMenu(item.id).then((res: any) => {
+      if (res.code === 200) {
+        ElMessage({
+          type: 'success',
+          message: '删除成功',
+          plain: true,
+        })
+        Menutree()//获取菜单树
+      }
+    })
+  }).catch(() => {
+    ElMessage({
+      type: 'info',
+      message: '已取消删除',
+      plain: true,
+    })
+  })
+}
+// 过滤图标
+const getFilteredIcons = (icon: any) => {
+  if (!iconSearchQuery.value) {
+    return icon.icons;
+  }
+  const query = iconSearchQuery.value.toLowerCase();
+  return icon.icons.filter((item: string) =>
+    item.toLowerCase().includes(query)
+  );
+}
+
+// 分页处理
+const getPaginatedIcons = (icons: string[]) => {
+  const start = (currentPage.value - 1) * pageSize.value;
+  const end = start + pageSize.value;
+  return icons.slice(start, end);
+}
+
+// 确认选择图标
+const confirmSelectIcon = () => {
+  if (selectedIcon.value) {
+    menuAddForm.icon = selectedIcon.value;
+  }
+  iconVisible.value = false;
+}
+
+// 菜单类型改变时处理
+watch(
+  () => menuAddForm.type,
+  (newType) => {
+    console.log(newType);
+    typeLabel.value = typelist.find(item => item.value === newType)?.label || '';
+    switch (newType) {
+      case 0:
+        menuAddForm.component = '';
+        break;
+      case 1:
+        menuAddForm.component = 'Layout';
+        break;
+      case 2:
+        menuAddForm.component = '';
+        break;
+      default:
+    }
+  },
+  { immediate: true } // 初始化时立即执行一次
+)
+// 添加菜单
+const menuAdd = () => {
+  menuAddShow.value = true;
+}
+
+// 获取菜单树
+const Menutree = () => {
+  loading.value = true;
+  api.menuApi.findMenuTree().then((res: any) => {
+    if (res.code === 200) {
+      tableData.value = res.data
+      loading.value = false;
+    }
+  })
+}
+// 确认添加菜单
+const confirmClick = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  await formEl.validate((valid) => {
+    if (valid) {
+      // 提交表单数据
+      api.menuApi.addMenu(menuAddForm).then((res: any) => {
+        if (res.code === 200) {
+          ElMessage({
+            type: 'success',
+            message: '添加成功',
+            plain: true,
+          })
+          menuAddShow.value = false;
+          Menutree();//刷新菜单树
+        }
+      })
+    } else {
+      ElMessage({
+        type: 'error',
+        message: '信息不完整',
+        plain: true,
+      })
+    }
+  })
+}
+</script>
+
+<!-- 样式区域 -->
+<style lang='scss' scoped>
+:deep(.header-class .el-table-v2__header-cell) {
+  padding: 10px 20px;
+  font-size: 15px;
+  color: #333;
+  background-color: #eef1f6;
+  border-bottom: 1px solid #eef1f6;
+}
+
+:deep(.el-table-v2__header-row) {
+  justify-content: space-between;
+  background-color: #eef1f6;
+}
+
+:deep(.el-table-v2__row) {
+  justify-content: space-between;
+}
+
+:deep(.el-table-v2__expand-icon) {
+  margin: 0;
+}
+
+:deep(.el-table-v2__cell-text) {
+  margin-left: 10px;
+}
+
+:deep(.el-dropdown-link) {
+  display: flex;
+  align-items: center;
+  margin-left: 15px;
+  color: var(--el-color-primary);
+  cursor: pointer;
+}
+
+.disabled :deep(.el-upload--picture-card) {
+  opacity: 0;
+}
+
+.icon-container {
+  max-height: 400px;
+  padding: 10px;
+  overflow-y: auto;
+}
+
+.icon-item {
+  width: 60px;
+  height: 60px;
+  margin: 8px;
+  cursor: pointer;
+  border: 2px solid transparent;
+  border-radius: 4px;
+  transition: all 0.3s;
+}
+
+.icon-selected {
+  background-color: rgb(64 158 255 / 10%);
+  border-color: #409eff;
+}
+
+.icon {
+  font-size: 24px;
+}
+</style>

+ 28 - 41
src/views/organizationManagement/tenantPermissions.vue

@@ -29,7 +29,9 @@
           <div class="flex j-s a-c">
             <span class="strong">操作权限</span>
             <div>
-              <ElButton  v-if=" roleInfo.systemCode" type="default" @click="roleSubmit" v-auth="'tenantPermissions.edit'">保存</ElButton>
+              <ElButton v-if="roleInfo.systemCode" type="default" @click="roleSubmit" v-auth="'tenantPermissions.edit'">
+                保存
+              </ElButton>
             </div>
           </div>
           <ElDivider style="margin-top: 10px;"></ElDivider>
@@ -37,9 +39,8 @@
             <el-checkbox v-model="treekAll" :indeterminate="isIndeterminate" @change="handleCheckAllChange">
               全选
             </el-checkbox>
-            <el-checkbox-group v-model="checkedCities" class="flex f-c" @change="handleCheckedCitiesChange">
-              <el-checkbox v-for="item in treeDatalevelOne" :key="item" :label="item" :value="item">
-                {{ item }}
+            <el-checkbox-group v-model="roleInfo.menus" class="flex f-c" @change="handleCheckedCitiesChange">
+              <el-checkbox v-for="item in treeDatalevelOne" :key="item.id" :label="item.label" :value="item.value">
               </el-checkbox>
             </el-checkbox-group>
           </div>
@@ -58,6 +59,7 @@ import { TreeNode } from '@/utils/composables/routeUtils'; // 导入工具方法
 import { Search } from '@element-plus/icons-vue'
 import api from '@/api'
 import empty from '@/assets/images/empty.png'
+import { SwitchLabel } from '@headlessui/vue';
 
 onBeforeMount(() => {
   findpage()//租户列表
@@ -71,22 +73,23 @@ interface tenantList {
 }
 const roleInfo = reactive<{
   systemCode: string;
-  perms: object[],
-  permsStr: string,
+  menus: Array<any>,
 }>({
   systemCode: "",//角色
-  permsStr: '',//菜单json
-  perms: [],//权限标识
+  menus: [],//权限标识
 });
 const treekAll = ref(false)//全选
 const isIndeterminate = ref(true)
-const checkedCities = ref([])//默认选中
 const searchValue = ref<string>()//检索条件
 const tenantList = ref<tenantList>()//租户列表
 const treeData = ref<TreeNode[]>([]);//菜单原始数据
 const treeDatalevelOne = ref();//一级菜单数据
 const handleCheckAllChange = (val: any) => {
-  checkedCities.value = val ? treeDatalevelOne.value : []
+  if (val) {
+    roleInfo.menus = treeDatalevelOne.value.map((item: any) => item.value);
+  } else {
+    roleInfo.menus = [];
+  }
   isIndeterminate.value = false
 }
 const handleCheckedCitiesChange = (value: string[]) => {
@@ -98,37 +101,26 @@ const handleCheckedCitiesChange = (value: string[]) => {
 const treeDatalevelFun = () => {
   let list: any = [];
   treeData.value.map((val: any) => {
-    list.push(val.meta.title)
+    list.push({
+      value: val.id,
+      label: val.title,
+    })
   })
   treeDatalevelOne.value = list;
 }
-const dataSetProcessing = () => {
-  let menuList: any = [];
-  treeData.value.map(value => {
-    checkedCities.value.map(val => {
-      if (value.meta.title === val) {
-        menuList.push(value)
-      }
-    })
-  })
-  roleInfo.perms = menuList;
-  if (menuList.length) {
-    roleInfo.permsStr = JSON.stringify(menuList);
 
-  }
-}
 const getMenu = () => {
-  api.tenantApi.getSystemConfig({ systemCode: 'PLATFORM' }).then((res: any) => {
+  api.menuApi.findMenuTree().then((res: any) => {
     if (res.code == '200') {
-      treeData.value = JSON.parse(res.data.sysConfig)
+      treeData.value = res.data;
       treeDatalevelFun();
     }
   });
 }
 // 提交
 async function roleSubmit() {
-  dataSetProcessing();
-  await api.tenantApi.updatePerms(roleInfo).then((res: any) => {
+  console.log(roleInfo)
+  await api.tenantApi.saveSystemMenu(roleInfo).then((res: any) => {
     if (res.code == '200') {
       ElMessage({
         type: 'success',
@@ -144,28 +136,23 @@ async function roleSubmit() {
 async function roleitemClick(item: { sysCode: string }) {
   getMenu();//获取租户菜单
   roleInfo.systemCode = item.sysCode;
-  api.tenantApi.getTenantConfig({ systemCode: item.sysCode }).then((res: any) => {
+  api.tenantApi.getSystemMenu({ systemCode: item.sysCode }).then((res: any) => {
     if (res.code == '200') {
       let list: any = [];
-      if (res.data.sysConfig) {
-        let tree = JSON.parse(res.data.sysConfig);
-        console.log(tree)
-        tree.map((val: any) => {
-          list.push(val.meta.title)
+      if (res.data && res.data.length>0) {
+        res.data.map((val: any) => {
+          list.push(val.id)
         })
-        checkedCities.value = list;
-        treekAll.value = true;
+        roleInfo.menus = list;
       } else {
-        checkedCities.value = [];
-        treekAll.value = false;
-
+        roleInfo.menus = [];
       }
     } else {
     }
   });
 }
 const findpage = () => {
-  api.tenantApi.sysListAll({  searchValue: searchValue.value }).then((res: any) => {
+  api.tenantApi.sysListAll({ searchValue: searchValue.value }).then((res: any) => {
     if (res.code == 200) {
       tenantList.value = res.data;
     }

+ 5 - 4
src/views/personnel/roleManagement.vue

@@ -65,7 +65,7 @@
                   style="margin-left: 20px;" />
               </div>
               <ElTree style="max-height: 350px;overflow-y: auto;" ref="treeRef" :data="treeData" :props="defaultProps"
-                show-checkbox node-key="id" :default-expand-all="true" :default-checked-keys="checkedKeys"
+                show-checkbox node-key="id" :default-expand-all="true" check-strictly :default-checked-keys="checkedKeys"
                 @check="handleCheck" />
             </div>
           </div>
@@ -157,9 +157,8 @@ const handleCheck = (
     halfCheckedNodes: TreeNode[];
   }
 ) => {
-  roleInfo.permsId = checkedInfo.checkedKeys;
-  roleInfo.perms = [];
-  roleInfo.perms = checkedInfo.checkedNodes;
+  roleInfo.permsId = [...checkedInfo.checkedKeys, ...checkedInfo.halfCheckedKeys];
+  roleInfo.perms = [...checkedInfo.checkedNodes, ...checkedInfo.halfCheckedNodes];
 
 };
 
@@ -173,6 +172,8 @@ async function roleSubmit() {
     return;
   }
   if (roleInfo.id) {
+    console.log(roleInfo)
+    return
     await api.roleApi.roleUpdate(roleInfo).then((res: any) => {
       if (res.code == 200) {
         rolequery();