瀏覽代碼

agent 添加菜单管理

xgb 1 周之前
父節點
當前提交
47f7ba5f3e
共有 2 個文件被更改,包括 241 次插入1 次删除
  1. 8 0
      src/api/tenant.js
  2. 233 1
      src/views/tenant/index.vue

+ 8 - 0
src/api/tenant.js

@@ -19,3 +19,11 @@ export function updateTenant(data) {
 export function toggleTenantStatus(tenantId, status) {
   return request({ url: `/proxy/tenants/${tenantId}/status`, method: 'put', params: { status } })
 }
+
+export function getTenantMenuTree(companyId, flag) {
+  return request({ url: `/proxy/tenants/${companyId}/menu`, method: 'post', data: { flag: flag } })
+}
+
+export function editTenantMenu(companyId, data) {
+  return request({ url: `/proxy/tenants/${companyId}/menu/edit`, method: 'post', data })
+}

+ 233 - 1
src/views/tenant/index.vue

@@ -37,6 +37,8 @@
       <el-table-column label="操作" align="center" min-width="160">
         <template slot-scope="scope">
           <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
+          <el-button size="mini" type="text" style="color:#722ed1" icon="el-icon-menu" @click="handleEditMenu(scope.row, 'sys')">管理端菜单</el-button>
+          <el-button size="mini" type="text" style="color:#e6a23c" icon="el-icon-price-tag" @click="handleEditMenu(scope.row, 'com')">销售端菜单</el-button>
           <el-button size="mini" @click="handleToggleStatus(scope.row)">
             {{ scope.row.status === 1 ? '禁用' : '启用' }}
           </el-button>
@@ -122,11 +124,45 @@
         <el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
       </div>
     </el-dialog>
+
+    <el-dialog :title="menuDialog.title" :visible.sync="menuDialog.visible" width="560px" append-to-body destroy-on-close @opened="onMenuDialogOpened">
+      <div v-loading="menuDialog.loading" class="menu-tree-scroll">
+        <div v-if="menuDialog.treeReady && menuDialog.treeData.length" class="menu-tree-toolbar">
+          <el-button type="text" size="mini" @click="checkAllMenuNodes">全选</el-button>
+          <el-button type="text" size="mini" @click="uncheckAllMenuNodes">全不选</el-button>
+          <el-button type="text" size="mini" @click="expandAllMenuNodes">展开全部</el-button>
+          <el-button type="text" size="mini" @click="collapseAllMenuNodes">收起全部</el-button>
+        </div>
+        <el-tree
+          v-if="menuDialog.treeReady"
+          ref="menuTree"
+          :data="menuDialog.treeData"
+          show-checkbox
+          node-key="menuId"
+          :props="menuTreeProps"
+          :expand-on-click-node="false"
+        >
+          <span slot-scope="{ data }" class="custom-tree-node">
+            <span>{{ data.menuName }}</span>
+            <el-tag v-if="data.menuType === 'M'" size="mini" style="margin-left:6px">目录</el-tag>
+            <el-tag v-else-if="data.menuType === 'C'" type="success" size="mini" style="margin-left:6px">菜单</el-tag>
+            <el-tag v-else-if="data.menuType === 'F'" type="warning" size="mini" style="margin-left:6px">按钮</el-tag>
+            <el-tag v-if="isMenuAssigned(data)" type="success" size="mini" style="margin-left:6px">已分配</el-tag>
+            <el-tag v-else type="info" size="mini" style="margin-left:6px">未分配</el-tag>
+          </span>
+        </el-tree>
+        <el-empty v-if="!menuDialog.loading && menuDialog.treeReady && !menuDialog.treeData.length" description="暂无菜单数据" />
+      </div>
+      <div slot="footer">
+        <el-button @click="menuDialog.visible = false">取 消</el-button>
+        <el-button type="primary" :loading="menuDialog.submitting" @click="submitMenuEdit">确 定</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script>
-import { getTenantList, getTenant, addTenant, updateTenant, toggleTenantStatus } from '@/api/tenant'
+import { getTenantList, getTenant, addTenant, updateTenant, toggleTenantStatus, getTenantMenuTree, editTenantMenu } from '@/api/tenant'
 
 export default {
   name: 'Tenant',
@@ -147,6 +183,22 @@ export default {
       dialogTitle: '',
       isEdit: false,
       submitting: false,
+      menuTreeProps: { label: 'menuName', children: 'children' },
+      menuDialog: {
+        visible: false,
+        title: '',
+        flag: 'sys',
+        companyId: null,
+        companyName: '',
+        treeData: [],
+        treeReady: false,
+        checkedKeys: [],
+        checkedKeysFromApi: [],
+        checkedKeySet: null,
+        initialAssignedIds: null,
+        loading: false,
+        submitting: false
+      },
       tenantForm: {
         id: null,
         tenantName: '',
@@ -274,6 +326,178 @@ export default {
     handleCurrentChange(val) {
       this.pageInfo.pageNum = val
       this.loadTenantList()
+    },
+    handleEditMenu(row, flag) {
+      this.menuDialog = {
+        visible: true,
+        title: (flag === 'sys' ? '编辑管理端菜单 - ' : '编辑销售端菜单 - ') + row.tenantName,
+        flag: flag,
+        companyId: row.id,
+        companyName: row.tenantName,
+        treeData: [],
+        treeReady: false,
+        checkedKeys: [],
+        checkedKeysFromApi: [],
+        checkedKeySet: null,
+        initialAssignedIds: null,
+        loading: false,
+        submitting: false
+      }
+    },
+    normalizeMenuId(id) {
+      if (id == null || id === '') return null
+      const n = Number(id)
+      return Number.isNaN(n) ? id : n
+    },
+    isMenuAssigned(menu) {
+      if (!menu) return false
+      const menuId = this.normalizeMenuId(menu.menuId)
+      if (menuId != null && this.menuDialog.checkedKeySet && this.menuDialog.checkedKeySet.has(menuId)) {
+        return true
+      }
+      return menu.visible === '0' || menu.visible === 0
+    },
+    handleTree(data, idKey) {
+      const idKeyName = idKey || 'id'
+      const map = {}
+      const roots = []
+      data.forEach(item => { map[item[idKeyName]] = { ...item, children: [] } })
+      data.forEach(item => {
+        const parent = map[item.parentId]
+        if (parent) {
+          parent.children.push(map[item[idKeyName]])
+        } else {
+          roots.push(map[item[idKeyName]])
+        }
+      })
+      return roots
+    },
+    normalizeMenuFlatList(menus) {
+      return (menus || []).map(m => ({
+        ...m,
+        menuId: this.normalizeMenuId(m.menuId),
+        parentId: m.parentId != null && m.parentId !== '' ? this.normalizeMenuId(m.parentId) : m.parentId
+      }))
+    },
+    applyMenuTreeChecked(checkedKeys) {
+      const tree = this.$refs.menuTree
+      const d = this.menuDialog
+      if (!tree) return
+      const keys = (checkedKeys || []).map(id => this.normalizeMenuId(id)).filter(id => id != null)
+      tree.setCheckedKeys([])
+      keys.forEach(menuId => {
+        const node = tree.getNode(menuId)
+        if (node) node.setChecked(true, false)
+      })
+      const currentKeys = tree.getCheckedKeys().concat(tree.getHalfCheckedKeys()).map(id => this.normalizeMenuId(id))
+      d.checkedKeys = currentKeys
+      d.checkedKeySet = new Set(currentKeys)
+    },
+    walkMenuTreeNodes(nodes, fn) {
+      (nodes || []).forEach(n => {
+        fn(n)
+        if (n.children && n.children.length) this.walkMenuTreeNodes(n.children, fn)
+      })
+    },
+    setAllMenuNodesChecked(checked) {
+      const tree = this.$refs.menuTree
+      if (!tree) return
+      this.walkMenuTreeNodes(this.menuDialog.treeData, n => {
+        const node = tree.getNode(this.normalizeMenuId(n.menuId))
+        if (node) node.setChecked(checked, false)
+      })
+    },
+    checkAllMenuNodes() {
+      this.setAllMenuNodesChecked(true)
+    },
+    uncheckAllMenuNodes() {
+      this.setAllMenuNodesChecked(false)
+    },
+    expandAllMenuNodes() {
+      const tree = this.$refs.menuTree
+      if (!tree) return
+      this.walkMenuTreeNodes(this.menuDialog.treeData, n => {
+        const node = tree.getNode(this.normalizeMenuId(n.menuId))
+        if (node) node.expanded = true
+      })
+    },
+    collapseAllMenuNodes() {
+      const tree = this.$refs.menuTree
+      if (!tree) return
+      this.walkMenuTreeNodes(this.menuDialog.treeData, n => {
+        const node = tree.getNode(this.normalizeMenuId(n.menuId))
+        if (node) node.expanded = false
+      })
+    },
+    onMenuDialogOpened() {
+      if (this.menuDialog.companyId) {
+        this.loadTenantMenuTree()
+      }
+    },
+    loadTenantMenuTree() {
+      const d = this.menuDialog
+      if (!d.companyId) return
+      d.loading = true
+      d.treeReady = false
+      d.treeData = []
+      getTenantMenuTree(d.companyId, d.flag).then(res => {
+        if (res.code !== 200) {
+          return Promise.reject(new Error(res.msg || '加载菜单失败'))
+        }
+        const flat = this.normalizeMenuFlatList(res.menus || [])
+        const checkedFromApi = (res.checkedKeys || [])
+          .map(id => this.normalizeMenuId(id))
+          .filter(id => id != null)
+        d.checkedKeysFromApi = checkedFromApi
+        d.initialAssignedIds = new Set(checkedFromApi)
+        d.checkedKeySet = new Set(checkedFromApi)
+        const tree = this.handleTree(flat, 'menuId')
+        d.treeData = tree
+        requestAnimationFrame(() => {
+          d.treeReady = true
+          this.$nextTick(() => this.applyMenuTreeChecked(checkedFromApi))
+        })
+      }).catch(err => {
+        this.$message.error(err.message || '加载菜单失败')
+      }).finally(() => {
+        d.loading = false
+      })
+    },
+    submitMenuEdit() {
+      const tree = this.$refs.menuTree
+      if (!tree) return
+      const selected = tree.getCheckedKeys()
+        .concat(tree.getHalfCheckedKeys())
+        .map(id => this.normalizeMenuId(id))
+        .filter(id => id != null)
+      const uniqueSelected = [...new Set(selected)]
+      if (uniqueSelected.length === 0) {
+        this.$confirm('未勾选任何菜单,保存后将清空该租户已分配的菜单,是否继续?', '提示', {
+          type: 'warning'
+        }).then(() => {
+          this.doSubmitMenuEdit(uniqueSelected)
+        }).catch(() => {})
+        return
+      }
+      this.doSubmitMenuEdit(uniqueSelected)
+    },
+    doSubmitMenuEdit(selected) {
+      this.menuDialog.submitting = true
+      editTenantMenu(this.menuDialog.companyId, {
+        flag: this.menuDialog.flag,
+        selected
+      }).then(res => {
+        if (res.code === 200) {
+          this.$message.success('菜单更新成功')
+          this.menuDialog.visible = false
+        } else {
+          this.$message.error(res.msg || '菜单更新失败')
+        }
+      }).catch(() => {
+        this.$message.error('菜单更新失败')
+      }).finally(() => {
+        this.menuDialog.submitting = false
+      })
     }
   }
 }
@@ -299,4 +523,12 @@ export default {
     margin-right: 10px;
   }
 }
+.menu-tree-scroll {
+  max-height: 480px;
+  overflow-y: auto;
+}
+.menu-tree-toolbar {
+  text-align: right;
+  margin-bottom: 8px;
+}
 </style>