grantPermissionForm.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. <template>
  2. <xn-form-container
  3. title="授权权限"
  4. :width="drawerWidth"
  5. :visible="visible"
  6. :destroy-on-close="true"
  7. :show-pagination="false"
  8. @close="onClose"
  9. >
  10. <a-alert
  11. message="注:此功能界面需要与代码查询条件配合使用,并非所有接口都需设置数据范围,多用于业务模块!"
  12. type="warning"
  13. closable
  14. />
  15. <a-spin :spinning="spinningLoading">
  16. <a-table
  17. class="mt-4"
  18. size="middle"
  19. :columns="columns"
  20. :data-source="loadDatas"
  21. bordered
  22. :row-key="(record) => record.api"
  23. >
  24. <template #headerCell="{ column }">
  25. <template v-if="column.key === 'api'">
  26. <a-checkbox @update:checked="(val) => onCheckAllChange(val)"> 接口 </a-checkbox>
  27. </template>
  28. <template v-if="column.key === 'dataScope'">
  29. <span>{{ column.title }}</span>
  30. <a-radio-group v-model:value="scopeRadioValue" @change="radioChange" style="padding: 0 10px">
  31. <a-radio value="SCOPE_ALL"> 全部 </a-radio>
  32. <a-radio value="SCOPE_SELF"> 仅自已 </a-radio>
  33. <a-radio value="SCOPE_ORG"> 所属组织 </a-radio>
  34. <a-radio value="SCOPE_ORG_CHILD"> 所属组织及以下 </a-radio>
  35. </a-radio-group>
  36. </template>
  37. </template>
  38. <template #customFilterDropdown="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }">
  39. <div style="padding: 8px">
  40. <a-input
  41. ref="searchInput"
  42. placeholder="请输入接口或接口名称"
  43. :value="selectedKeys[0]"
  44. style="width: 188px; margin-bottom: 8px; display: block"
  45. @change="(e) => setSelectedKeys(e.target.value ? [e.target.value] : [])"
  46. @pressEnter="handleSearch(selectedKeys, confirm, column.dataIndex)"
  47. />
  48. <a-button
  49. type="primary"
  50. size="small"
  51. style="width: 90px; margin-right: 8px"
  52. @click="handleSearch(selectedKeys, confirm, column.dataIndex)"
  53. >
  54. <template #icon><SearchOutlined /></template>
  55. 搜索
  56. </a-button>
  57. <a-button size="small" style="width: 90px" @click="handleReset(clearFilters)"> 重置 </a-button>
  58. </div>
  59. </template>
  60. <template #customFilterIcon="{ filtered }">
  61. <search-outlined :style="{ color: filtered ? '#108ee9' : undefined }" />
  62. </template>
  63. <template #bodyCell="{ column, record }">
  64. <template v-if="column.dataIndex === 'api'">
  65. <a-checkbox :checked="record.check" @update:checked="(val) => changeApi(record, val)">
  66. {{ record.api }}
  67. </a-checkbox>
  68. </template>
  69. <template v-if="column.dataIndex === 'dataScope'">
  70. <template v-if="record.dataScope.length > 0">
  71. <template v-for="item in record.dataScope" :key="item.id + record.api">
  72. <a-radio
  73. v-model:checked="item.check"
  74. :name="item.title"
  75. @change="(evt) => changeDataScope(record, evt)"
  76. >
  77. <a-badge
  78. v-if="
  79. (item.value === 'SCOPE_ORG_DEFINE') &
  80. record.dataScope[4].check &
  81. (item.scopeDefineOrgIdList !== undefined)
  82. "
  83. :count="item.scopeDefineOrgIdList.length"
  84. :number-style="{ backgroundColor: '#52c41a' }"
  85. >
  86. {{ item.title }}</a-badge
  87. >
  88. <div v-else>{{ item.title }}</div>
  89. </a-radio>
  90. </template>
  91. <a-button v-if="record.dataScope[4].check" type="link" size="small" @click="handleDefineOrg(record)"
  92. >选择组织</a-button
  93. >
  94. </template>
  95. </template>
  96. </template>
  97. </a-table>
  98. </a-spin>
  99. <template #footer>
  100. <a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
  101. <a-button type="primary" :loading="submitLoading" @click="onSubmit">保存</a-button>
  102. </template>
  103. <ScopeDefineOrg ref="scopeDefineOrgModal" @click="scopeDefineOrgClick" />
  104. </xn-form-container>
  105. </template>
  106. <script setup name="grantResourceForm">
  107. import { SearchOutlined } from '@ant-design/icons-vue'
  108. import userApi from '@/api/sys/userApi'
  109. import roleApi from '@/api/sys/roleApi'
  110. import ScopeDefineOrg from './scopeDefineOrg.vue'
  111. const visible = ref(false)
  112. const spinningLoading = ref(false)
  113. const scopeDefineOrgModal = ref(null)
  114. const emit = defineEmits({ successful: null })
  115. const submitLoading = ref(false)
  116. const CustomValue = 'SCOPE_ORG_DEFINE'
  117. // 抽屉的宽度
  118. const drawerWidth = 1000
  119. // 自动获取宽度,默认获取浏览器的宽度的90%
  120. //(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth) * 0.9
  121. const loadDatas = ref([])
  122. // 标题接口列搜索, 数据范围默认
  123. const scopeRadioValue = ref()
  124. const state = reactive({
  125. searchText: '',
  126. searchedColumn: ''
  127. })
  128. const searchInput = ref()
  129. const columns = [
  130. {
  131. key: 'api',
  132. title: '接口',
  133. dataIndex: 'api',
  134. width: 380,
  135. customFilterDropdown: true,
  136. onFilter: (value, record) => record.api.includes(value),
  137. onFilterDropdownVisibleChange: (visible) => {
  138. if (visible) {
  139. setTimeout(() => {
  140. searchInput.value.focus()
  141. }, 100)
  142. }
  143. }
  144. },
  145. {
  146. key: 'dataScope',
  147. title: '数据范围',
  148. dataIndex: 'dataScope'
  149. }
  150. ]
  151. // 获取数据
  152. const loadData = async () => {
  153. spinningLoading.value = true
  154. const res = await roleApi.rolePermissionTreeSelector()
  155. // 获取他已有的权限
  156. const param = {
  157. id: grantPermissionParam.id
  158. }
  159. const resOwn = await userApi.userOwnPermission(param)
  160. // 数据转换
  161. echoModuleData(res, resOwn)
  162. spinningLoading.value = false
  163. }
  164. // 数据转换
  165. const echoModuleData = (res, resOwn) => {
  166. res.forEach((api) => {
  167. const obj = {
  168. api: api,
  169. dataScope: datascope(api),
  170. check: false
  171. }
  172. if (resOwn.grantInfoList.length > 0) {
  173. resOwn.grantInfoList.forEach((item) => {
  174. if (item.apiUrl === subStrApi(api)) {
  175. obj.check = true
  176. // dataScopeInfo
  177. obj.dataScope.forEach((o) => {
  178. if (o.value === item.scopeCategory) {
  179. o.check = true
  180. // 如果是自定义
  181. if (item.scopeCategory === 'SCOPE_ORG_DEFINE') {
  182. o.scopeDefineOrgIdList = item.scopeDefineOrgIdList
  183. }
  184. }
  185. })
  186. }
  187. })
  188. }
  189. loadDatas.value.push(obj)
  190. })
  191. }
  192. const datascope = (id) => {
  193. return [
  194. {
  195. id: `SCOPE_ALL_${id}`,
  196. title: '全部',
  197. value: 'SCOPE_ALL',
  198. check: false
  199. },
  200. {
  201. id: `SCOPE_SELF_${id}`,
  202. title: '仅自己',
  203. value: 'SCOPE_SELF',
  204. check: false
  205. },
  206. {
  207. id: `SCOPE_ORG_${id}`,
  208. title: '所属组织',
  209. value: 'SCOPE_ORG',
  210. check: false
  211. },
  212. {
  213. id: `SCOPE_ORG_CHILD_${id}`,
  214. title: '所属组织及以下',
  215. value: 'SCOPE_ORG_CHILD',
  216. check: false
  217. },
  218. {
  219. id: `SCOPE_ORG_DEFINE_${id}`,
  220. title: '自定义',
  221. value: 'SCOPE_ORG_DEFINE',
  222. check: false
  223. }
  224. ]
  225. }
  226. // 点击数据权限选择
  227. const changeDataScope = (record, evt) => {
  228. const name = evt.target.name
  229. // 这里做互斥,每个
  230. record.dataScope.forEach((item) => {
  231. if (item.title !== name) {
  232. item.check = false
  233. }
  234. })
  235. changeChildCheckBox(record, evt)
  236. }
  237. // 处理自定义
  238. const handleDefineOrg = (recordDataScope) => {
  239. // 弹框选择子自定义
  240. const data = recordDataScope.dataScope.find((f) => f.value === CustomValue)
  241. // 选中了
  242. if (data.check) {
  243. // 获取到选中的key数组,传过去,让其那边回显
  244. const checkKeysStr = recordDataScope.dataScope[4].scopeDefineOrgIdList
  245. scopeDefineOrgModal.value.onOpen(data.id, checkKeysStr)
  246. } else {
  247. // 清理缓存中的结构,去掉就行
  248. handleDatascope(false, record.id, null)
  249. }
  250. }
  251. // 自定义数据弹窗回调
  252. const scopeDefineOrgClick = (value) => {
  253. handleDatascope(true, value.dataScopeId, value.defineOrgIdData.scopeDefineOrgIdList)
  254. }
  255. // 处理Datascope数据被选中自定义或取消自定义数据
  256. const handleDatascope = (check, id, orgData) => {
  257. loadDatas.value.forEach((item) => {
  258. if (id === 'SCOPE_ORG_DEFINE_' + item.api) {
  259. item.dataScope.forEach((items) => {
  260. if (items.value === 'SCOPE_ORG_DEFINE') {
  261. if (check) {
  262. items.scopeDefineOrgIdList = orgData
  263. } else {
  264. items.scopeDefineOrgIdList = []
  265. }
  266. }
  267. })
  268. }
  269. })
  270. }
  271. // 打开抽屉
  272. const onOpen = (record) => {
  273. grantPermissionParam.id = record.id
  274. visible.value = true
  275. loadData()
  276. }
  277. // 关闭抽屉
  278. const onClose = () => {
  279. // 将这些缓存的给清空
  280. loadDatas.value = []
  281. scopeRadioValue.value = ''
  282. visible.value = false
  283. }
  284. // 全选
  285. const onCheckAllChange = (value) => {
  286. spinningLoading.value = true
  287. loadDatas.value.forEach((data) => {
  288. changeApi(data, value)
  289. spinningLoading.value = false
  290. })
  291. }
  292. // 选中接口
  293. const changeApi = (record, val) => {
  294. record.check = val
  295. if (val) {
  296. let checkStatus = 0
  297. for (let i = 0; i < record.dataScope.length; i++) {
  298. if (record.dataScope[i].check) {
  299. checkStatus++
  300. }
  301. }
  302. if (checkStatus === 0) {
  303. record.dataScope[0].check = true
  304. }
  305. } else {
  306. // 去掉已选中的
  307. record.dataScope.forEach((item) => {
  308. item.check = false
  309. if (item.value === 'SCOPE_ORG_DEFINE') {
  310. item.scopeDefineOrgIdList = []
  311. }
  312. })
  313. }
  314. }
  315. // 设置选中状态
  316. const changeChildCheckBox = (record, evt) => {
  317. let checked = evt.target.checked
  318. if (!checked) {
  319. record.check = false
  320. } else if (checked) {
  321. record.check = checked
  322. }
  323. }
  324. // 提交数据模型
  325. let grantPermissionParam = {
  326. // 角色id
  327. id: '',
  328. // 授权权限信息
  329. grantInfoList: []
  330. }
  331. // 提交之前转换数据
  332. const convertData = () => {
  333. grantPermissionParam.grantInfoList = []
  334. loadDatas.value.forEach((table) => {
  335. if (table.check) {
  336. table.dataScope.forEach((item) => {
  337. if (item.check) {
  338. const dataScopeInfo = {
  339. apiUrl: subStrApi(table.api),
  340. scopeCategory: item.value,
  341. scopeDefineOrgIdList: item.scopeDefineOrgIdList === undefined ? [] : item.scopeDefineOrgIdList
  342. }
  343. grantPermissionParam.grantInfoList.push(dataScopeInfo)
  344. }
  345. })
  346. }
  347. })
  348. return grantPermissionParam
  349. }
  350. // 截取api串中的中文及括号
  351. const subStrApi = (api) => {
  352. return api.substring(0, api.indexOf('['))
  353. }
  354. // 验证并提交数据
  355. const onSubmit = () => {
  356. const param = convertData()
  357. submitLoading.value = true
  358. userApi
  359. .userGrantPermission(param)
  360. .then(() => {
  361. onClose()
  362. emit('successful')
  363. })
  364. .finally(() => {
  365. submitLoading.value = false
  366. })
  367. }
  368. // 标题接口列搜索
  369. const handleSearch = (selectedKeys, confirm, dataIndex) => {
  370. confirm()
  371. state.searchText = selectedKeys[0]
  372. state.searchedColumn = dataIndex
  373. }
  374. // 标题接口列搜索重置
  375. const handleReset = (clearFilters) => {
  376. clearFilters()
  377. state.searchText = ''
  378. }
  379. // 标题数据范围列radio-group事件
  380. const radioChange = (obj) => {
  381. loadDatas.value.forEach((data) => {
  382. data.dataScope.forEach((data) => {
  383. if (obj.target.value === data.value) {
  384. data.check = true
  385. } else {
  386. data.check = false
  387. }
  388. })
  389. })
  390. }
  391. // 调用这个函数将子组件的一些数据和方法暴露出去
  392. defineExpose({
  393. onOpen
  394. })
  395. </script>
  396. <style scoped>
  397. /* 重写复选框的样式 */
  398. .ant-checkbox-wrapper {
  399. margin-left: 0px !important;
  400. padding-top: 2px !important;
  401. padding-bottom: 2px !important;
  402. }
  403. </style>