(function () { function authHeaders() { return { token: localStorage.getItem('token') || '' }; } function isForbidden(res) { return res && Number(res.code) === 403; } function isOk(res) { return res && (Number(res.code) === 200 || Number(res.code) === 0); } function normalizeId(value) { if (value === null || value === undefined || value === '') { return null; } var numberValue = Number(value); return isNaN(numberValue) ? value : numberValue; } function normalizeNumber(value, fallback) { if (value === null || value === undefined || value === '') { return fallback; } var numberValue = Number(value); return isNaN(numberValue) ? fallback : numberValue; } function cloneRow(row) { return { id: normalizeId(row.id), code: row.code || '', name: row.name || '', resourceId: normalizeId(row.resourceId), level: normalizeNumber(row.level, 1), sort: normalizeNumber(row.sort, 999), status: normalizeNumber(row.status, 1) }; } function createDefaultForm() { return { id: null, resourceId: null, code: '', level: 1, name: '', sort: 999, status: 1 }; } function sortNodes(nodes) { nodes.sort(function (left, right) { var sortDiff = normalizeNumber(left.sort, 999) - normalizeNumber(right.sort, 999); if (sortDiff !== 0) { return sortDiff; } return String(left.id).localeCompare(String(right.id), 'zh-Hans-CN', {numeric: true}); }); nodes.forEach(function (node) { if (node.children && node.children.length > 0) { sortNodes(node.children); } }); return nodes; } function buildTree(rows) { var nodeMap = {}; var roots = []; rows.forEach(function (item) { var node = cloneRow(item); node.children = []; nodeMap[node.id] = node; }); Object.keys(nodeMap).forEach(function (key) { var current = nodeMap[key]; if (current.resourceId !== null && nodeMap[current.resourceId] && current.resourceId !== current.id) { nodeMap[current.resourceId].children.push(current); } else { roots.push(current); } }); return sortNodes(roots); } new Vue({ el: '#app', data: function () { var vm = this; return { loading: false, tableData: [], tableKey: 0, tableHeight: Math.max(window.innerHeight - 198, 360), expandAll: false, selection: [], flatRows: [], resourceLookup: {}, childrenLookup: {}, editingId: null, dialog: { visible: false, mode: 'create', submitting: false }, dialogForm: createDefaultForm(), parentCascaderProps: { checkStrictly: true, emitPath: false, value: 'id', label: 'name', children: 'children' }, dialogRules: { code: [ {required: true, message: '请输入菜单编码', trigger: 'blur'} ], name: [ {required: true, message: '请输入菜单名称', trigger: 'blur'} ], level: [ {required: true, message: '请选择类型', trigger: 'change'} ], resourceId: [ { validator: function (rule, value, callback) { if (Number(vm.dialogForm.level) > 1 && (value === null || value === undefined || value === '')) { callback(new Error('请选择上级菜单')); return; } callback(); }, trigger: 'change' } ], sort: [ {type: 'number', message: '排序必须为数字', trigger: 'blur'} ], status: [ {required: true, message: '请选择状态', trigger: 'change'} ] } }; }, computed: { parentOptions: function () { var blocked = {}; if (this.editingId !== null) { blocked = this.collectDescendantIds(this.editingId); } return this.buildParentOptions(this.tableData, blocked); } }, created: function () { this.loadTree(); }, mounted: function () { window.addEventListener('resize', this.handleResize); }, beforeDestroy: function () { window.removeEventListener('resize', this.handleResize); }, methods: { createDefaultForm: function () { return createDefaultForm(); }, handleResize: function () { this.tableHeight = Math.max(window.innerHeight - 198, 360); this.refreshTableLayout(); }, handleForbidden: function (res) { if (isForbidden(res)) { top.location.href = baseUrl + '/'; return true; } return false; }, refreshTableLayout: function () { var vm = this; this.$nextTick(function () { if (vm.$refs.treeTable && vm.$refs.treeTable.doLayout) { vm.$refs.treeTable.doLayout(); } }); }, rebuildIndexes: function (rows) { var lookup = {}; var childrenLookup = {}; rows.forEach(function (item) { var normalized = cloneRow(item); lookup[normalized.id] = normalized; if (normalized.resourceId !== null) { if (!childrenLookup[normalized.resourceId]) { childrenLookup[normalized.resourceId] = []; } childrenLookup[normalized.resourceId].push(normalized.id); } }); this.flatRows = rows.map(function (item) { return cloneRow(item); }); this.resourceLookup = lookup; this.childrenLookup = childrenLookup; }, loadTree: function () { var vm = this; vm.loading = true; $.ajax({ url: baseUrl + '/resource/tree/auth', method: 'GET', headers: authHeaders(), success: function (res) { vm.loading = false; if (vm.handleForbidden(res)) { return; } if (!isOk(res)) { vm.$message.error(res && res.msg ? res.msg : '菜单加载失败'); return; } var rows = Array.isArray(res.data) ? res.data : []; vm.rebuildIndexes(rows); vm.tableData = buildTree(rows); vm.selection = []; vm.tableKey += 1; vm.refreshTableLayout(); }, error: function () { vm.loading = false; vm.$message.error('菜单加载失败'); } }); }, handleSelectionChange: function (rows) { this.selection = rows || []; }, toggleExpandAll: function () { this.expandAll = !this.expandAll; this.tableKey += 1; this.refreshTableLayout(); }, levelText: function (level) { var currentLevel = Number(level); if (currentLevel === 1) { return '一级菜单'; } if (currentLevel === 2) { return '二级菜单'; } if (currentLevel === 3) { return '按钮'; } return '--'; }, levelTagType: function (level) { var currentLevel = Number(level); if (currentLevel === 3) { return 'info'; } if (currentLevel === 2) { return ''; } return 'success'; }, parentName: function (resourceId) { if (resourceId === null || resourceId === undefined || resourceId === '') { return ''; } var parent = this.resourceLookup[normalizeId(resourceId)]; return parent ? parent.name : ''; }, collectDescendantIds: function (resourceId) { var blocked = {}; var stack = [normalizeId(resourceId)]; while (stack.length > 0) { var currentId = stack.pop(); if (currentId === null || blocked[currentId]) { continue; } blocked[currentId] = true; var childIds = this.childrenLookup[currentId] || []; childIds.forEach(function (childId) { stack.push(childId); }); } return blocked; }, buildParentOptions: function (nodes, blocked) { var vm = this; return (nodes || []).reduce(function (result, node) { if (blocked[node.id]) { return result; } var option = { id: node.id, name: node.name, children: vm.buildParentOptions(node.children || [], blocked) }; if (option.children.length === 0) { delete option.children; } result.push(option); return result; }, []); }, resetDialogForm: function () { this.editingId = null; this.dialogForm = this.createDefaultForm(); var vm = this; this.$nextTick(function () { if (vm.$refs.dialogForm) { vm.$refs.dialogForm.clearValidate(); } }); }, openCreateDialog: function () { this.dialog.mode = 'create'; this.dialog.visible = true; this.resetDialogForm(); }, openEditDialog: function (row) { this.dialog.mode = 'edit'; this.dialog.visible = true; this.editingId = normalizeId(row.id); this.dialogForm = { id: normalizeId(row.id), resourceId: normalizeId(row.resourceId), code: row.code || '', level: normalizeNumber(row.level, 1), name: row.name || '', sort: normalizeNumber(row.sort, 999), status: normalizeNumber(row.status, 1) }; var vm = this; this.$nextTick(function () { if (vm.$refs.dialogForm) { vm.$refs.dialogForm.clearValidate(); } }); }, removeSelection: function () { if (!this.selection.length) { this.$message.warning('请选择要删除的数据'); return; } var ids = this.selection.map(function (row) { return normalizeId(row.id); }).filter(function (id) { return id !== null; }); this.removeRows(ids); }, removeRows: function (ids) { var vm = this; var uniqueIds = Array.from(new Set((ids || []).map(function (id) { return normalizeId(id); }).filter(function (id) { return id !== null; }))); if (!uniqueIds.length) { vm.$message.warning('请选择要删除的数据'); return; } vm.$confirm('确定要删除选中数据吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(function () { $.ajax({ url: baseUrl + '/resource/delete/auth', method: 'POST', headers: authHeaders(), traditional: true, data: { ids: uniqueIds }, success: function (res) { if (vm.handleForbidden(res)) { return; } if (!isOk(res)) { vm.$message.error(res && res.msg ? res.msg : '删除失败'); return; } vm.$message.success(res.msg || '删除成功'); vm.loadTree(); }, error: function () { vm.$message.error('删除失败'); } }); }).catch(function () { }); }, submitDialog: function () { var vm = this; if (!vm.$refs.dialogForm) { return; } vm.$refs.dialogForm.validate(function (valid) { if (!valid) { return false; } var payload = { id: vm.dialog.mode === 'edit' ? normalizeId(vm.dialogForm.id) : null, resourceId: Number(vm.dialogForm.level) === 1 ? null : normalizeId(vm.dialogForm.resourceId), code: $.trim(vm.dialogForm.code), level: normalizeNumber(vm.dialogForm.level, 1), name: $.trim(vm.dialogForm.name), sort: normalizeNumber(vm.dialogForm.sort, 999), status: normalizeNumber(vm.dialogForm.status, 1) }; vm.dialog.submitting = true; $.ajax({ url: baseUrl + '/resource/' + (vm.dialog.mode === 'create' ? 'add' : 'update') + '/auth', method: 'POST', headers: authHeaders(), data: payload, success: function (res) { vm.dialog.submitting = false; if (vm.handleForbidden(res)) { return; } if (!isOk(res)) { vm.$message.error(res && res.msg ? res.msg : '保存失败'); return; } vm.$message.success(res.msg || '保存成功'); vm.dialog.visible = false; vm.loadTree(); }, error: function () { vm.dialog.submitting = false; vm.$message.error('保存失败'); } }); return true; }); } }, watch: { 'dialogForm.level': function (value) { if (Number(value) === 1) { this.dialogForm.resourceId = null; } }, 'dialog.visible': function (visible) { if (!visible) { this.resetDialogForm(); this.dialog.submitting = false; } } } }); })();