auth.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. <template>
  2. <CzrDialog
  3. :show="show"
  4. :title="titleCpt"
  5. @onClose="$emit('update:show', false)"
  6. @onSubmit="onSubmit"
  7. width="42.5rem"
  8. height="80%"
  9. :loading="state.loading"
  10. >
  11. <div class="bm-form flex size-full gap-4">
  12. <div class="flex flex-1 flex-col gap-4">
  13. <div>
  14. <CzrFormColumn
  15. class="__czr-table-form-column"
  16. :span="24"
  17. label-width="0px"
  18. v-model:param="state.text"
  19. placeholder="输入关键词以检索"
  20. :prefix-icon="Search"
  21. />
  22. </div>
  23. <div class="flex-1 overflow-y-auto">
  24. <el-tree
  25. ref="ref_tree"
  26. class="tree"
  27. :data="treeDataCpt"
  28. default-expand-all
  29. :filter-node-method="filterNode"
  30. show-checkbox
  31. node-key="value"
  32. :check-on-click-leaf="false"
  33. :expand-on-click-node="false"
  34. :check-strictly="true"
  35. @node-click="onNodeClick"
  36. >
  37. <template #default="{ node, data }">
  38. <div>
  39. {{ data.label }}
  40. <template v-if="data.auths?.length > 0">
  41. <span
  42. :class="
  43. data.auths.filter((v) =>
  44. state.activeAuths.includes(v.value),
  45. ).length === data.auths.length
  46. ? 'text-[var(--czr-success-color)]'
  47. : 'text-[var(--czr-error-color)]'
  48. "
  49. >
  50. ({{
  51. data.auths.filter((v) =>
  52. state.activeAuths.includes(v.value),
  53. ).length
  54. }}/{{ data.auths.length }})
  55. </span>
  56. </template>
  57. </div>
  58. </template>
  59. </el-tree>
  60. </div>
  61. </div>
  62. <div class="flex-1" v-if="state.currentMenu.title">
  63. <el-card
  64. class="size-full"
  65. :body-style="{ height: '100%', width: '100%' }"
  66. >
  67. <div class="flex size-full flex-col gap-4">
  68. <div class="font-bold">{{ state.currentMenu.title }}</div>
  69. <div class="flex flex-1 flex-col gap-2 overflow-auto">
  70. <template v-if="state.currentMenu.auths?.length > 0">
  71. <el-button-group size="small">
  72. <el-button type="primary" @click="onCheckAll(true)"
  73. >全选</el-button
  74. >
  75. <el-button type="info" @click="onCheckAll(false)"
  76. >取消全选</el-button
  77. >
  78. </el-button-group>
  79. <el-checkbox-group v-model="state.activeAuths">
  80. <template v-for="item in state.currentMenu.auths">
  81. <el-checkbox :value="item.value">
  82. {{ item.label }}
  83. </el-checkbox>
  84. </template>
  85. </el-checkbox-group>
  86. </template>
  87. <template v-else> 该菜单暂无可配置权限 </template>
  88. </div>
  89. </div>
  90. </el-card>
  91. </div>
  92. </div>
  93. </CzrDialog>
  94. </template>
  95. <script setup lang="ts">
  96. import {
  97. computed,
  98. getCurrentInstance,
  99. nextTick,
  100. reactive,
  101. ref,
  102. watch,
  103. } from 'vue'
  104. import { ElMessage, ElMessageBox } from 'element-plus'
  105. import { useAppStore, useDialogStore, useDictionaryStore } from '@/stores'
  106. import { useRouter } from 'vue-router'
  107. import { Search } from '@element-plus/icons-vue'
  108. import CzrDialog from '@/components/czr-ui/CzrDialog.vue'
  109. import { rolesAuths, rolesAuthsAdd } from '@/api/modules/center/role'
  110. import bigModelRouter from '@/router/modules/big-model'
  111. const DialogStore = useDialogStore()
  112. const AppStore = useAppStore()
  113. const router = useRouter()
  114. const emit = defineEmits(['update:show', 'refresh'])
  115. const { proxy } = getCurrentInstance()
  116. const props = defineProps({
  117. show: { default: false },
  118. transfer: <any>{},
  119. })
  120. const state: any = reactive({
  121. loading: false,
  122. text: '',
  123. authsOptions: [],
  124. activeAuths: [],
  125. currentMenu: {
  126. title: '',
  127. auths: [],
  128. },
  129. })
  130. const ref_tree = ref()
  131. const titleCpt = computed(() => {
  132. let t = '授权'
  133. return t
  134. })
  135. watch(
  136. () => props.show,
  137. (n) => {
  138. if (n) {
  139. initDictionary()
  140. initData()
  141. }
  142. },
  143. )
  144. const initDictionary = () => {}
  145. const initData = () => {
  146. state.loading = true
  147. state.activeAuths = []
  148. ref_tree.value?.setCheckedKeys([], false)
  149. rolesAuths(props.transfer.roleId)
  150. .then(({ data }: any) => {
  151. const authArr: any = []
  152. const menuArr: any = []
  153. data.forEach((v) => {
  154. if (v.auth.includes(AppStore.permission.splitAuth)) {
  155. authArr.push(v.auth)
  156. } else {
  157. menuArr.push(v.auth)
  158. }
  159. })
  160. state.activeAuths = authArr
  161. ref_tree.value?.setCheckedKeys(menuArr, true)
  162. })
  163. .catch(() => {})
  164. .finally(() => {
  165. state.loading = false
  166. })
  167. }
  168. watch(
  169. () => state.text,
  170. (val) => {
  171. ref_tree.value!.filter(val)
  172. },
  173. )
  174. const filterNode = (value, data) => {
  175. if (!value) return true
  176. return data.label.includes(value)
  177. }
  178. const treeDataCpt = computed(() => {
  179. const subsMap = new Map()
  180. const menus: any = []
  181. const formatRou = (rou) => {
  182. const obj = {
  183. label: rou.meta?.title || rou.name,
  184. value: rou.name,
  185. children: [],
  186. auths:
  187. rou.meta?.auths?.map((v) => ({
  188. label: v.label,
  189. value: `${rou.name}${AppStore.permission.splitAuth}${v.value}`,
  190. })) || [],
  191. active: [],
  192. }
  193. if (rou.children?.length > 0) {
  194. obj.children = rou.children.map((v) => formatRou(v))
  195. }
  196. return obj
  197. }
  198. const deep = (arr) => {
  199. return arr.filter((v) => {
  200. if (v.children?.length > 0) {
  201. v.children = deep(v.children)
  202. }
  203. if (!v.meta.noAuth) {
  204. return true
  205. }
  206. })
  207. }
  208. deep(bigModelRouter()).forEach((r) => {
  209. if (r.meta?.root) {
  210. if (subsMap.has(r.meta.root)) {
  211. subsMap.set(r.meta.root, [...subsMap.get(r.meta.root), formatRou(r)])
  212. } else {
  213. subsMap.set(r.meta.root, [formatRou(r)])
  214. }
  215. } else {
  216. menus.push(formatRou(r))
  217. }
  218. })
  219. menus.forEach((v) => {
  220. if (subsMap.has(v.value)) {
  221. v.children.push(...subsMap.get(v.value))
  222. }
  223. })
  224. return menus
  225. })
  226. const onNodeClick = (data, node) => {
  227. const getTitle = (n, title = '') => {
  228. if (n.label) {
  229. if (title) {
  230. title = n.label + ' > ' + title
  231. } else {
  232. title = n.label
  233. }
  234. }
  235. if (n.parent) {
  236. return getTitle(n.parent, title)
  237. }
  238. return title
  239. }
  240. state.currentMenu.title = getTitle(node)
  241. state.currentMenu.auths = data.auths
  242. }
  243. const onCheckAll = (flag) => {
  244. if (flag) {
  245. state.currentMenu.auths.forEach((v) => {
  246. if (!state.activeAuths.includes(v.value)) {
  247. state.activeAuths.push(v.value)
  248. }
  249. })
  250. } else {
  251. state.activeAuths = state.activeAuths.filter(
  252. (v) => !state.currentMenu.auths.some((a) => a.value === v),
  253. )
  254. }
  255. }
  256. const onSubmit = () => {
  257. const arr = [...ref_tree.value.getCheckedKeys(), ...state.activeAuths]
  258. state.loading = true
  259. rolesAuthsAdd(props.transfer.roleId, arr)
  260. .then(({ data }: any) => {
  261. ElMessage.success('授权成功!')
  262. })
  263. .finally(() => {
  264. state.loading = false
  265. initData()
  266. })
  267. }
  268. </script>
  269. <style lang="scss" scoped>
  270. :deep(.tree) {
  271. background-color: transparent;
  272. }
  273. </style>