|
@@ -37,6 +37,8 @@
|
|
|
<el-table-column label="操作" align="center" min-width="160">
|
|
<el-table-column label="操作" align="center" min-width="160">
|
|
|
<template slot-scope="scope">
|
|
<template slot-scope="scope">
|
|
|
<el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
|
|
<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)">
|
|
<el-button size="mini" @click="handleToggleStatus(scope.row)">
|
|
|
{{ scope.row.status === 1 ? '禁用' : '启用' }}
|
|
{{ scope.row.status === 1 ? '禁用' : '启用' }}
|
|
|
</el-button>
|
|
</el-button>
|
|
@@ -122,11 +124,45 @@
|
|
|
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
|
|
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
</el-dialog>
|
|
</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>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
<script>
|
|
|
-import { getTenantList, getTenant, addTenant, updateTenant, toggleTenantStatus } from '@/api/tenant'
|
|
|
|
|
|
|
+import { getTenantList, getTenant, addTenant, updateTenant, toggleTenantStatus, getTenantMenuTree, editTenantMenu } from '@/api/tenant'
|
|
|
|
|
|
|
|
export default {
|
|
export default {
|
|
|
name: 'Tenant',
|
|
name: 'Tenant',
|
|
@@ -147,6 +183,22 @@ export default {
|
|
|
dialogTitle: '',
|
|
dialogTitle: '',
|
|
|
isEdit: false,
|
|
isEdit: false,
|
|
|
submitting: 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: {
|
|
tenantForm: {
|
|
|
id: null,
|
|
id: null,
|
|
|
tenantName: '',
|
|
tenantName: '',
|
|
@@ -274,6 +326,178 @@ export default {
|
|
|
handleCurrentChange(val) {
|
|
handleCurrentChange(val) {
|
|
|
this.pageInfo.pageNum = val
|
|
this.pageInfo.pageNum = val
|
|
|
this.loadTenantList()
|
|
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;
|
|
margin-right: 10px;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+.menu-tree-scroll {
|
|
|
|
|
+ max-height: 480px;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+}
|
|
|
|
|
+.menu-tree-toolbar {
|
|
|
|
|
+ text-align: right;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|