From 0642bc9c44d1c8277ada33059c4a3e6156335b30 Mon Sep 17 00:00:00 2001
From: zzw <228544128@qq.com>
Date: 星期二, 06 十月 2026 14:48:37 +0800
Subject: [PATCH] feat: use role-only menu permissions
---
emergency-repair-web/src/views/system/MenuManage.vue | 196 ++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 177 insertions(+), 19 deletions(-)
diff --git a/emergency-repair-web/src/views/system/MenuManage.vue b/emergency-repair-web/src/views/system/MenuManage.vue
index 0849396..9d1f4af 100644
--- a/emergency-repair-web/src/views/system/MenuManage.vue
+++ b/emergency-repair-web/src/views/system/MenuManage.vue
@@ -1,33 +1,80 @@
<template>
<div class="page">
<div class="page-title">
- <div><h2>鑿滃崟绠$悊</h2><p>鑿滃崟涓哄姩鎬侀厤缃紝宸︿晶瀵艰埅鐩存帴璇诲彇杩欓噷鐨勬暟鎹�</p></div>
- <el-button type="primary" icon="el-icon-plus" @click="openCreate(null)">鏂板涓�绾ц彍鍗�</el-button>
+ <div><h2>鑿滃崟绠$悊</h2><p>鏀寔鐩綍銆佽彍鍗曘�佹寜閽笁绉嶇被鍨嬶紱鎸夐挳鍙兘鎸傚湪鑿滃崟涓嬬骇锛岀敤浜庨厤缃〉闈㈡搷浣滄潈闄�</p></div>
+ <el-button type="primary" icon="el-icon-plus" v-permission="'system:menus:add'" @click="openCreate(null)">鏂板涓�绾ц彍鍗�</el-button>
</div>
<div class="table-panel">
<el-table :data="tree" row-key="id" border default-expand-all :tree-props="{ children: 'children' }">
<el-table-column prop="name" label="鑿滃崟鍚嶇О" min-width="220"></el-table-column>
- <el-table-column prop="icon" label="鍥炬爣" width="200"><template slot-scope="{ row }"><span class="icon-cell"><i :class="row.icon"></i><span>{{ row.icon }}</span></span></template></el-table-column>
- <el-table-column prop="url" label="璺敱鍦板潃" min-width="240"></el-table-column>
- <el-table-column prop="permissions" label="鏉冮檺鏍囪瘑" min-width="170"></el-table-column>
- <el-table-column prop="menuType" label="绫诲瀷" width="90"><template slot-scope="{ row }"><el-tag size="mini" :type="row.menuType === 0 ? 'info' : 'primary'">{{ row.menuType === 0 ? '鐩綍' : '鑿滃崟' }}</el-tag></template></el-table-column>
+ <el-table-column prop="icon" label="鍥炬爣" width="180">
+ <template slot-scope="{ row }">
+ <span v-if="row.menuType !== 2" class="icon-cell"><i :class="row.icon"></i><span>{{ row.icon }}</span></span>
+ <span v-else class="muted">-</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="url" label="璺敱鍦板潃" min-width="220">
+ <template slot-scope="{ row }">
+ <span v-if="row.menuType !== 2">{{ row.url }}</span>
+ <span v-else class="muted">-</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="permissions" label="鏉冮檺鏍囪瘑" min-width="200"></el-table-column>
+ <el-table-column label="绫诲瀷" width="90">
+ <template slot-scope="{ row }">
+ <el-tag size="mini" :type="typeTag(row.menuType)">{{ typeName(row.menuType) }}</el-tag>
+ </template>
+ </el-table-column>
<el-table-column prop="sortNo" label="鎺掑簭" width="80" align="center"></el-table-column>
- <el-table-column prop="visible" label="鏄剧ず" width="80" align="center"><template slot-scope="{ row }"><el-tag size="mini" :type="row.visible === 0 ? 'info' : 'success'">{{ row.visible === 0 ? '闅愯棌' : '鏄剧ず' }}</el-tag></template></el-table-column>
- <el-table-column label="鎿嶄綔" width="200" fixed="right"><template slot-scope="{ row }"><el-button type="text" @click="openCreate(row)">鏂板涓嬬骇</el-button><el-button type="text" @click="openEdit(row)">缂栬緫</el-button><el-button type="text" class="danger-link" @click="remove(row)">鍒犻櫎</el-button></template></el-table-column>
+ <el-table-column label="鏄剧ず" width="80" align="center">
+ <template slot-scope="{ row }">
+ <el-tag size="mini" :type="row.visible === 0 ? 'info' : 'success'">{{ row.visible === 0 ? '闅愯棌' : '鏄剧ず' }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="270" fixed="right">
+ <template slot-scope="{ row }">
+ <el-button v-if="row.menuType !== 2" type="text" v-permission="'system:menus:add'" @click="openCreate(row)">鏂板涓嬬骇</el-button>
+ <el-button v-if="row.menuType === 1" type="text" v-permission="'system:menus:buttons'" @click="openButtons(row)">椤甸潰鎸夐挳</el-button>
+ <el-button type="text" v-permission="'system:menus:edit'" @click="openEdit(row)">缂栬緫</el-button>
+ <el-button type="text" class="danger-link" v-permission="'system:menus:delete'" @click="remove(row)">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
</el-table>
</div>
+
<el-dialog :title="form.id ? '缂栬緫鑿滃崟' : '鏂板鑿滃崟'" :visible.sync="dialogVisible" width="640px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="105px">
<el-form-item label="涓婄骇鑿滃崟"><el-input :value="parentName" readonly></el-input></el-form-item>
+ <el-form-item label="鑿滃崟绫诲瀷">
+ <el-radio-group v-model="form.menuType">
+ <el-radio-button v-for="item in typeOptions" :key="item.value" :label="item.value">{{ item.label }}</el-radio-button>
+ </el-radio-group>
+ </el-form-item>
<el-form-item label="鑿滃崟鍚嶇О" prop="name"><el-input v-model="form.name"></el-input></el-form-item>
- <el-form-item label="鑿滃崟绫诲瀷"><el-radio-group v-model="form.menuType"><el-radio-button :label="0">鐩綍</el-radio-button><el-radio-button :label="1">鑿滃崟</el-radio-button></el-radio-group></el-form-item>
- <el-form-item label="璺敱鍦板潃"><el-input v-model="form.url" placeholder="/fault/report"></el-input></el-form-item>
- <el-form-item label="鍥炬爣"><el-input v-model="form.icon" placeholder="el-icon-tickets"></el-input></el-form-item>
- <el-form-item label="鏉冮檺鏍囪瘑"><el-input v-model="form.permissions"></el-input></el-form-item>
+ <el-form-item v-if="form.menuType !== 2" label="璺敱鍦板潃"><el-input v-model="form.url" placeholder="/fault/report"></el-input></el-form-item>
+ <el-form-item v-if="form.menuType !== 2" label="鍥炬爣"><el-input v-model="form.icon" placeholder="el-icon-tickets"></el-input></el-form-item>
+ <el-form-item label="鏉冮檺鏍囪瘑" :prop="form.menuType === 2 ? 'permissions' : ''">
+ <el-input v-model="form.permissions" :placeholder="form.menuType === 2 ? '渚嬪 fault:report:add' : '鍙�夛紝渚嬪 fault-report'"></el-input>
+ </el-form-item>
<el-form-item label="鎺掑簭"><el-input-number v-model="form.sortNo" :min="1" style="width:160px"></el-input-number></el-form-item>
<el-form-item label="鏄惁鏄剧ず"><el-switch v-model="form.visible" :active-value="1" :inactive-value="0"></el-switch></el-form-item>
</el-form>
<template slot="footer"><el-button @click="dialogVisible = false">鍙栨秷</el-button><el-button type="primary" :loading="saving" @click="save">淇濆瓨</el-button></template>
+ </el-dialog>
+
+ <el-dialog :title="'椤甸潰鎸夐挳閰嶇疆 - ' + (buttonMenu.name || '')" :visible.sync="buttonDialogVisible" width="620px" append-to-body>
+ <p class="dialog-tip">涓嬪垪鎸夐挳鏉ヨ嚜鍚庣鎸夐挳鐩綍锛屽嬀閫夊悗鑷姩鐢熸垚鎸夐挳绫诲瀷鑿滃崟锛屽彲鍦ㄨ彍鍗曟潈闄愮鐞嗕腑鎺堟潈缁欒鑹层��</p>
+ <el-checkbox-group v-model="buttonSelected" class="button-list">
+ <el-checkbox v-for="item in buttonAvailable" :key="item.code" :label="item.code" class="button-item">
+ <span class="button-name">{{ item.name }}</span>
+ <span class="button-code">{{ item.code }}</span>
+ </el-checkbox>
+ </el-checkbox-group>
+ <div v-if="!buttonAvailable.length" class="empty-tip">璇ラ〉闈㈡殏鏈櫥璁板彲鐢ㄦ寜閽�</div>
+ <template slot="footer">
+ <el-button @click="buttonDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" :loading="buttonSaving" @click="saveButtons">淇濆瓨鎸夐挳</el-button>
+ </template>
</el-dialog>
</div>
</template>
@@ -35,19 +82,130 @@
import api from '@/api'
export default {
name: 'MenuManage',
- data() { return { tree: [], flat: [], dialogVisible: false, saving: false, form: {}, rules: { name: [{ required: true, message: '璇疯緭鍏ヨ彍鍗曞悕绉�', trigger: 'blur' }] } } },
- computed: { parentName() { const parent = this.flat.find(item => item.id === this.form.pid); return parent ? parent.name : '鏃狅紙涓�绾ц彍鍗曪級' } },
+ data() {
+ return {
+ tree: [],
+ flat: [],
+ dialogVisible: false,
+ saving: false,
+ form: {},
+ parent: null,
+ rules: { name: [{ required: true, message: '璇疯緭鍏ヨ彍鍗曞悕绉�', trigger: 'blur' }] },
+ buttonDialogVisible: false,
+ buttonSaving: false,
+ buttonMenu: {},
+ buttonAvailable: [],
+ buttonSelected: []
+ }
+ },
+ computed: {
+ parentName() {
+ if (!this.parent) return this.form.pid && this.form.pid !== 0 ? '涓婄骇鑿滃崟' : '鏃狅紙涓�绾ц彍鍗曪級'
+ return this.parent.name
+ },
+ typeOptions() {
+ if (this.parent && this.parent.menuType === 1) {
+ return [{ label: '鎸夐挳', value: 2 }]
+ }
+ if (this.parent && this.parent.menuType === 0) {
+ return [{ label: '鐩綍', value: 0 }, { label: '鑿滃崟', value: 1 }]
+ }
+ if (this.form.id && this.form.menuType === 2) {
+ return [{ label: '鎸夐挳', value: 2 }]
+ }
+ return [{ label: '鐩綍', value: 0 }, { label: '鑿滃崟', value: 1 }]
+ }
+ },
created() { this.load() },
methods: {
- load() { Promise.all([api.get('/system/menu/nav'), api.get('/system/menu/list')]).then(([tree, list]) => { this.tree = tree.data || []; this.flat = list.data || [] }) },
- openCreate(parent) { this.form = { pid: parent ? parent.id : 0, name: '', url: '', icon: 'el-icon-tickets', menuType: parent ? 1 : 0, permissions: '', sortNo: 10, visible: 1 }; this.dialogVisible = true },
- openEdit(row) { this.form = { ...row }; this.dialogVisible = true },
- save() { this.$refs.form.validate(valid => { if (!valid) return; this.saving = true; const request = this.form.id ? api.put('/system/menu/' + this.form.id, this.form) : api.post('/system/menu', this.form); request.then(() => { this.$message.success('淇濆瓨鎴愬姛'); this.dialogVisible = false; this.load() }).finally(() => { this.saving = false }) }) },
- remove(row) { this.$confirm(`纭畾鍒犻櫎鑿滃崟鈥�${row.name}鈥濆悧锛焋, '鍒犻櫎纭', { type: 'warning' }).then(() => api.delete('/system/menu/' + row.id)).then(() => { this.$message.success('鍒犻櫎鎴愬姛'); this.load() }).catch(() => {}) }
+ load() {
+ Promise.all([api.get('/system/menu/tree'), api.get('/system/menu/list')]).then(([tree, list]) => {
+ this.tree = tree.data || []
+ this.flat = list.data || []
+ })
+ },
+ typeName(type) {
+ return type === 2 ? '鎸夐挳' : type === 0 ? '鐩綍' : '鑿滃崟'
+ },
+ typeTag(type) {
+ return type === 2 ? 'warning' : type === 0 ? 'info' : 'primary'
+ },
+ openCreate(parent) {
+ this.parent = parent || null
+ const menuType = parent && parent.menuType === 1 ? 2 : parent ? 1 : 0
+ this.form = {
+ pid: parent ? parent.id : 0,
+ name: '',
+ url: '',
+ icon: menuType === 2 ? '' : 'el-icon-tickets',
+ menuType,
+ permissions: '',
+ sortNo: 10,
+ visible: 1
+ }
+ this.dialogVisible = true
+ },
+ openEdit(row) {
+ this.parent = this.flat.find(item => item.id === row.pid) || null
+ this.form = { ...row }
+ this.dialogVisible = true
+ },
+ save() {
+ this.$refs.form.validate(valid => {
+ if (!valid) return
+ if (this.form.menuType === 2 && !this.form.permissions) {
+ this.$message.error('鎸夐挳鏉冮檺鏍囪瘑涓嶈兘涓虹┖')
+ return
+ }
+ const payload = { ...this.form }
+ if (payload.menuType === 2) {
+ payload.url = null
+ payload.icon = null
+ }
+ delete payload.children
+ this.saving = true
+ const request = payload.id ? api.put('/system/menu/' + payload.id, payload) : api.post('/system/menu', payload)
+ request.then(() => {
+ this.$message.success('淇濆瓨鎴愬姛')
+ this.dialogVisible = false
+ this.load()
+ }).finally(() => { this.saving = false })
+ })
+ },
+ remove(row) {
+ this.$confirm(`纭畾鍒犻櫎鈥�${row.name}鈥濆悧锛焋, '鍒犻櫎纭', { type: 'warning' })
+ .then(() => api.delete('/system/menu/' + row.id))
+ .then(() => { this.$message.success('鍒犻櫎鎴愬姛'); this.load() })
+ .catch(() => {})
+ },
+ openButtons(row) {
+ this.buttonMenu = row
+ api.get('/system/menu/buttons', { params: { menuId: row.id } }).then(res => {
+ const data = res.data || {}
+ this.buttonAvailable = data.available || []
+ this.buttonSelected = data.selected || []
+ this.buttonDialogVisible = true
+ })
+ },
+ saveButtons() {
+ this.buttonSaving = true
+ api.post('/system/menu/buttons', { menuId: this.buttonMenu.id, codes: this.buttonSelected }).then(() => {
+ this.$message.success('椤甸潰鎸夐挳宸插悓姝�')
+ this.buttonDialogVisible = false
+ this.load()
+ }).finally(() => { this.buttonSaving = false })
+ }
}
}
</script>
<style scoped>
.danger-link { color: #d9534f; }
.icon-cell { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
+.muted { color: #a3afbc; }
+.dialog-tip { margin: 0 0 12px; color: #738296; font-size: 13px; }
+.button-list { display: flex; flex-direction: column; gap: 2px; }
+.button-item { margin-right: 0; }
+.button-name { color: #31465d; }
+.button-code { margin-left: 8px; color: #93a2b1; font-size: 12px; }
+.empty-tip { padding: 30px 0; color: #93a2b1; text-align: center; }
</style>
--
Gitblit v1.9.1