From ffe06be91f95ffd2459ebdb2f9130d1e38d53ea7 Mon Sep 17 00:00:00 2001
From: zzw <228544128@qq.com>
Date: 星期二, 06 十月 2026 18:32:34 +0800
Subject: [PATCH] fix: preserve manually edited menu names

---
 emergency-repair-web/src/views/system/RolePermissionDialog.vue |  229 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 229 insertions(+), 0 deletions(-)

diff --git a/emergency-repair-web/src/views/system/RolePermissionDialog.vue b/emergency-repair-web/src/views/system/RolePermissionDialog.vue
new file mode 100644
index 0000000..3180bb7
--- /dev/null
+++ b/emergency-repair-web/src/views/system/RolePermissionDialog.vue
@@ -0,0 +1,229 @@
+<template>
+  <el-dialog
+    title="瑙掕壊鏉冮檺璁剧疆"
+    :visible.sync="dialogVisible"
+    width="860px"
+    append-to-body
+    :close-on-click-modal="false"
+  >
+    <div class="permission-head">
+      <div>
+        <span class="role-label">褰撳墠瑙掕壊</span>
+        <strong>{{ role.name }}</strong>
+        <span class="role-code">{{ role.code }}</span>
+      </div>
+      <span class="selected-total">宸查�夋潈闄� {{ selectedRoleIds.length }} 涓�</span>
+    </div>
+    <div class="permission-toolbar">
+      <el-radio-group v-model="permissionScope" size="small" @change="handleScopeChange">
+        <el-radio-button label="all">鍏ㄩ儴</el-radio-button>
+        <el-radio-button label="assigned">宸茶缃�</el-radio-button>
+      </el-radio-group>
+      <el-checkbox v-if="permissionScope === 'all'" v-model="checkAll" @change="toggleAll">鍏ㄩ��</el-checkbox>
+      <span class="permission-tip">{{ permissionTip }}</span>
+    </div>
+    <div v-loading="loading" class="permission-tree">
+      <el-tree
+        ref="permissionTree"
+        :data="displayMenus"
+        node-key="id"
+        show-checkbox
+        default-expand-all
+        empty-text="璇ヨ鑹插皻鏈缃潈闄�"
+        :render-content="renderContent"
+        :props="{ label: 'name', children: 'children' }"
+        @check="syncCheckAllState"
+      ></el-tree>
+    </div>
+    <template slot="footer">
+      <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+      <el-button type="primary" :loading="saving" v-permission="'system:roles:permission'" @click="save">淇濆瓨</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+import api from '@/api'
+export default {
+  name: 'RolePermissionDialog',
+  props: {
+    visible: { type: Boolean, default: false },
+    role: { type: Object, default: () => ({}) }
+  },
+  data() {
+    return {
+      menus: [],
+      flatMenus: [],
+      loading: false,
+      saving: false,
+      checkAll: false,
+      permissionScope: 'all',
+      selectedRoleIds: []
+    }
+  },
+  computed: {
+    dialogVisible: {
+      get() { return this.visible },
+      set(value) { this.$emit('update:visible', value) }
+    },
+    allMenuIds() {
+      return this.flatMenus.map(item => String(item.id))
+    },
+    displayMenus() {
+      if (this.permissionScope === 'all') return this.menus
+      const assigned = new Set(this.selectedRoleIds.map(String))
+      const filterAssigned = nodes => (nodes || []).reduce((result, node) => {
+        const children = filterAssigned(node.children)
+        if (assigned.has(String(node.id)) || children.length) {
+          result.push({ ...node, children })
+        }
+        return result
+      }, [])
+      return filterAssigned(this.menus)
+    },
+    permissionTip() {
+      return this.permissionScope === 'all'
+        ? '鑿滃崟鍜屾寜閽潈闄愬潎鍙嬀閫夛紝淇濆瓨鍚庡垏鎹㈣鑹蹭細鑷姩鍙嶆樉銆�'
+        : '浠呮樉绀哄綋鍓嶈鑹插凡璁剧疆鐨勬潈闄愬強鍏剁埗绾ц妭鐐广��'
+    }
+  },
+  watch: {
+    visible(value) {
+      if (value) this.open()
+    },
+  },
+  methods: {
+    open() {
+      if (!this.role || !this.role.id) return
+      this.loading = true
+      this.checkAll = false
+      this.permissionScope = 'all'
+      this.selectedRoleIds = []
+      Promise.all([
+        api.get('/system/menu/tree'),
+        api.get('/system/menu/list'),
+        api.get('/system/menu-permissions', { params: { roleId: this.role.id } })
+      ]).then(([tree, flat, permissions]) => {
+        this.menus = tree.data || []
+        this.flatMenus = flat.data || []
+        const data = permissions.data || {}
+        this.selectedRoleIds = (data.roleMenuIds || []).map(String)
+        this.$nextTick(() => {
+          if (!this.$refs.permissionTree) return
+          this.$refs.permissionTree.setCheckedKeys(this.displayCheckedKeys(this.selectedRoleIds))
+          this.syncCheckAllState()
+        })
+      }).finally(() => { this.loading = false })
+
+    },
+    handleScopeChange() {
+      this.selectedRoleIds = this.checkedMenuIds()
+      this.$nextTick(() => {
+        if (!this.$refs.permissionTree) return
+        this.$refs.permissionTree.setCheckedKeys(this.displayCheckedKeys(this.selectedRoleIds))
+        this.syncCheckAllState()
+      })
+    },
+    renderContent(h, { data }) {
+      const label = data.menuType === 2 ? data.name + ' [鎸夐挳 ' + (data.permissions || '') + ']' : data.name
+      return h('span', { class: { 'perm-button-node': data.menuType === 2 } }, label)
+    },
+    displayCheckedKeys(ids) {
+      const selected = new Set((ids || []).map(String))
+      const children = {}
+      const menuIds = new Set(this.flatMenus.map(item => String(item.id)))
+      this.flatMenus.forEach(item => {
+        const id = String(item.id)
+        const pid = item.pid == null ? '0' : String(item.pid)
+        if (!children[pid]) children[pid] = []
+        children[pid].push(id)
+      })
+      const result = []
+      const collectFullySelected = id => {
+        if (!selected.has(id)) return false
+        const childIds = children[id] || []
+        if (!childIds.length) {
+          result.push(id)
+          return true
+        }
+        const allChildrenSelected = childIds.map(collectFullySelected).every(Boolean)
+        if (allChildrenSelected) result.push(id)
+        return allChildrenSelected
+      }
+      this.flatMenus
+        .filter(item => item.pid == null || String(item.pid) === '0' || !menuIds.has(String(item.pid)))
+        .forEach(item => collectFullySelected(String(item.id)))
+      return result
+    },
+    checkedMenuIds() {
+      const tree = this.$refs.permissionTree
+      if (!tree) return []
+      return [...new Set([...(tree.getCheckedKeys(false) || []), ...(tree.getHalfCheckedKeys() || [])].map(String))]
+    },
+    syncCheckAllState() {
+      const checked = this.checkedMenuIds()
+      this.selectedRoleIds = checked
+      this.checkAll = checked.length === this.allMenuIds.length
+    },
+    toggleAll() {
+      if (!this.$refs.permissionTree) return
+      this.$refs.permissionTree.setCheckedKeys(this.checkAll ? this.allMenuIds : [])
+      this.$nextTick(this.syncCheckAllState)
+    },
+    save() {
+      const roleMenuIds = this.checkedMenuIds()
+      this.saving = true
+      api.post('/system/menu-permissions', { roleId: this.role.id, roleMenuIds }).then(() => {
+        this.selectedRoleIds = roleMenuIds
+        this.$message.success('瑙掕壊鏉冮檺宸蹭繚瀛�')
+        this.$emit('saved')
+        this.dialogVisible = false
+      }).finally(() => { this.saving = false })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.permission-head {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 12px;
+  color: #31465d;
+}
+.role-label {
+  margin-right: 10px;
+  color: #7b8b9c;
+  font-size: 13px;
+}
+.role-code {
+  margin-left: 8px;
+  color: #93a2b1;
+  font-size: 12px;
+}
+.selected-total {
+  color: #2f658f;
+  font-size: 13px;
+}
+.permission-toolbar {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 10px;
+}
+.permission-tip {
+  color: #7b8a9a;
+  font-size: 12px;
+}
+.permission-tree {
+  height: 460px;
+  padding: 8px 10px;
+  border: 1px solid #dce4ec;
+  border-radius: 4px;
+  overflow: auto;
+}
+::v-deep .perm-button-node {
+  color: #b8791f;
+}
+</style>

--
Gitblit v1.9.1