role.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. <template>
  2. <div>
  3. <div>
  4. <n-card :bordered="false" title="角色权限管理">
  5. 为后台用户分配不同的角色权限,以达到操作隔离的目的
  6. </n-card>
  7. </div>
  8. <n-card :bordered="false" class="mt-4 proCard">
  9. <BasicTable
  10. :columns="columns"
  11. :request="loadDataTable"
  12. :row-key="(row) => row.id"
  13. ref="actionRef"
  14. :actionColumn="actionColumn"
  15. @update:checked-row-keys="onCheckedRow"
  16. >
  17. <template #tableTitle>
  18. <n-button type="primary" @click="addTable">
  19. <template #icon>
  20. <n-icon>
  21. <PlusOutlined />
  22. </n-icon>
  23. </template>
  24. 添加角色
  25. </n-button>
  26. </template>
  27. <template #action>
  28. <TableAction />
  29. </template>
  30. </BasicTable>
  31. </n-card>
  32. <n-modal
  33. v-model:show="showModal"
  34. :show-icon="false"
  35. preset="dialog"
  36. :title="formParams?.id > 0 ? '编辑角色 #' + formParams?.id : '添加角色'"
  37. style="width: 800px"
  38. >
  39. <n-form
  40. :model="formParams"
  41. ref="formRef"
  42. label-placement="left"
  43. :label-width="80"
  44. class="py-4"
  45. >
  46. <n-form-item label="角色名称" path="name">
  47. <n-input placeholder="角色名称" v-model:value="formParams.name" />
  48. </n-form-item>
  49. <n-form-item label="角色编码" path="key">
  50. <n-input placeholder="填字母或数字,不能重复" v-model:value="formParams.key" />
  51. </n-form-item>
  52. <n-form-item label="备注" path="remark">
  53. <n-input type="textarea" placeholder="请输入备注内容" v-model:value="formParams.remark" />
  54. </n-form-item>
  55. <n-form-item label="排序" path="sort">
  56. <n-input-number placeholder="请输入排序" v-model:value="formParams.sort" />
  57. </n-form-item>
  58. <n-form-item label="状态" path="status">
  59. <n-radio-group v-model:value="formParams.status" name="status">
  60. <n-radio-button
  61. v-for="status in statusOptions"
  62. :key="status.value"
  63. :value="status.value"
  64. :label="status.label"
  65. />
  66. </n-radio-group>
  67. </n-form-item>
  68. <n-form-item label="系统分配" path="key">
  69. <n-checkbox-group v-model:value="formSystems" @update:value="handleUpdateValue">
  70. <n-space item-style="display: flex;">
  71. <n-checkbox
  72. v-for="item in systems"
  73. :key="item.id"
  74. :value="item.id"
  75. :label="item.name"
  76. />
  77. </n-space>
  78. </n-checkbox-group>
  79. </n-form-item>
  80. <n-card title="权限分配">
  81. <n-tabs animated type="card" v-model:value="activeTab">
  82. <n-tab-pane
  83. v-for="item in activeTabPanes"
  84. :key="item.id"
  85. :name="item.id"
  86. :tab="item.name || item.id"
  87. >
  88. <n-form-item label="权限分配">
  89. <div class="checkbox-group-wrapper">
  90. <n-space vertical>
  91. <n-checkbox
  92. :indeterminate="isIndeterminate(item.id)"
  93. :checked="isAllChecked(item.id)"
  94. @update:checked="(checked) => handleCheckAll(checked, item.id)"
  95. >
  96. 全选
  97. </n-checkbox>
  98. <n-checkbox-group
  99. v-model:value="permissions[item.id]"
  100. @update:value="(newValue) => handlePermissionsUpdateValue(newValue, item.id)"
  101. >
  102. <n-space
  103. item-style="display: flex;"
  104. align="center"
  105. class="checkbox-container"
  106. >
  107. <n-checkbox
  108. v-for="option in optionsPermissions[item.id]"
  109. :key="option.value"
  110. :value="option.value"
  111. :label="option.label"
  112. />
  113. </n-space>
  114. </n-checkbox-group>
  115. </n-space>
  116. </div>
  117. </n-form-item>
  118. <!-- <n-form-item label="权限分配">
  119. <n-select
  120. multiple
  121. v-model:value="formParams.permissions[item.id]"
  122. :options="optionsPermissions"
  123. @update:value="(newValue) => handlePermissionsUpdateValue(newValue, item.id)"
  124. />
  125. </n-form-item>-->
  126. <n-form-item label="页面分配">
  127. <n-tree-select
  128. multiple
  129. cascade
  130. checkable
  131. v-model:value="formParams.pages[item.id]"
  132. :options="optionsPages[item.id]"
  133. @update:value="(newValue) => handlePagesUpdateValue(newValue, item.id)"
  134. />
  135. </n-form-item>
  136. </n-tab-pane>
  137. </n-tabs>
  138. </n-card>
  139. </n-form>
  140. <template #action>
  141. <n-space>
  142. <n-button @click="cancelForm">取消</n-button>
  143. <n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
  144. </n-space>
  145. </template>
  146. </n-modal>
  147. </div>
  148. </template>
  149. <script lang="ts" setup>
  150. import { reactive, ref, h, onMounted, computed } from 'vue';
  151. import { useMessage } from 'naive-ui';
  152. import { BasicTable, TableAction } from '@/components/Table';
  153. import { getRoleList, RoleAuthOption, RoleEdit, RolePageOption } from '@/api/system/role';
  154. import { columns } from './columns';
  155. import { PlusOutlined } from '@vicons/antd';
  156. import { cloneDeep } from 'lodash-es';
  157. import { statusOptions } from '@/enums/optionsiEnum';
  158. import { GetServiceList } from '@/api/service/service';
  159. const formRef: any = ref(null);
  160. const message = useMessage();
  161. const actionRef = ref();
  162. const showModal = ref(false);
  163. const formBtnLoading = ref(false);
  164. const formParams = ref<any>();
  165. const optionsPermissions = ref<any>();
  166. const optionsPages = ref<any>();
  167. // 当前激活的tab
  168. const activeTab = ref<number>(0);
  169. type SystemTab = {
  170. id: number;
  171. name: string;
  172. };
  173. const systems = ref<SystemTab[]>([]);
  174. // 根据选中的checkbox生成active的tab panes
  175. const activeTabPanes = computed(() => {
  176. if (!formSystems.value) {
  177. formSystems.value = [];
  178. }
  179. return systems.value.filter((tab) => formSystems.value.includes(tab.id));
  180. });
  181. const selectSystem = ref<any>([]);
  182. function handleUpdateValue(values) {
  183. // 找出新增的选项
  184. const added = values.filter((item) => !selectSystem.value.includes(item));
  185. // 如果有新增的tab,默认激活第一个新增的tab
  186. if (added.length > 0) {
  187. activeTab.value = added[0];
  188. added.forEach((item) => {
  189. pages.value[item] = formParams.value.pages[item] || [];
  190. permissions.value[item] = formParams.value.permissions[item] || [];
  191. });
  192. }
  193. // 如果当前激活的tab被移除了,则激活第一个可用的tab
  194. else if (!values.includes(activeTab.value)) {
  195. activeTab.value = values[0] || null;
  196. }
  197. for (let valueKey in pages.value) {
  198. if (!values.includes(Number(valueKey))) {
  199. delete pages.value[valueKey];
  200. }
  201. }
  202. for (let valueKey in permissions.value) {
  203. if (!values.includes(Number(valueKey))) {
  204. delete permissions.value[valueKey];
  205. }
  206. }
  207. selectSystem.value = values;
  208. }
  209. const pages = ref<object>({});
  210. const permissions = ref<object>({});
  211. const formSystems = ref<any>([]);
  212. // 检查是否全部选中
  213. const isAllChecked = (id) => {
  214. let totalCount = 0;
  215. if (optionsPermissions.value[id]) {
  216. totalCount = optionsPermissions.value[id].length;
  217. }
  218. return permissions.value[id]?.length === totalCount;
  219. };
  220. // 检查是否是部分选中(不确定状态)
  221. const isIndeterminate = (id) => {
  222. const selectedCount = permissions.value[id]?.length || 0;
  223. return selectedCount > 0 && selectedCount < optionsPermissions.value[id].length;
  224. };
  225. // 处理全选/全不选
  226. const handleCheckAll = (checked, sysId) => {
  227. console.log(checked, sysId);
  228. if (checked) {
  229. permissions.value[sysId] = optionsPermissions.value[sysId].map((opt) => opt.value);
  230. console.log(permissions.value[sysId]);
  231. // formParams.value.permissions[sysId] = optionsPermissions.value.map((opt) => opt.value);
  232. } else {
  233. permissions.value[sysId] = [];
  234. }
  235. };
  236. function handlePermissionsUpdateValue(val: any, sysId: number) {
  237. permissions.value[sysId] = val;
  238. }
  239. function handlePagesUpdateValue(val: any, sysId: number) {
  240. pages.value[sysId] = val;
  241. }
  242. const defaultState = {
  243. id: 0,
  244. key: '',
  245. name: '',
  246. permissions: [],
  247. pages: [],
  248. remark: '',
  249. sort: 0,
  250. status: 1,
  251. };
  252. const actionColumn = reactive({
  253. width: 200,
  254. title: '操作',
  255. key: 'action',
  256. fixed: 'right',
  257. render(record) {
  258. return h(TableAction, {
  259. style: 'button',
  260. actions: [
  261. {
  262. label: '编辑',
  263. onClick: handleEdit.bind(null, record),
  264. ifShow: function () {
  265. return record.id > 1;
  266. },
  267. },
  268. // {
  269. // label: '删除',
  270. // onClick: handleDelete.bind(null, record),
  271. // },
  272. ],
  273. });
  274. },
  275. });
  276. const loadDataTable = async (_res: any) => {
  277. return await getRoleList();
  278. };
  279. function addTable() {
  280. showModal.value = true;
  281. formParams.value = cloneDeep(defaultState);
  282. }
  283. function onCheckedRow(rowKeys: any[]) {
  284. console.log(rowKeys);
  285. }
  286. function reloadTable() {
  287. actionRef.value.reload();
  288. }
  289. function cancelForm() {
  290. actionRef.value.reload();
  291. showModal.value = false;
  292. return;
  293. // pages.value = formParams.value.pages;
  294. // permissions.value = formParams.value.permissions;
  295. // formSystems.value = formParams.value.systems;
  296. // selectSystem.value = formParams.value.systems;
  297. // showModal.value = false;
  298. }
  299. function confirmForm(e: any) {
  300. formParams.value.systems = formSystems.value;
  301. formParams.value.permissions = permissions.value;
  302. formParams.value.pages = pages.value;
  303. e.preventDefault();
  304. formBtnLoading.value = true;
  305. formRef.value.validate((errors) => {
  306. if (!errors) {
  307. RoleEdit(formParams.value)
  308. .then((_res) => {
  309. message.success('操作成功');
  310. setTimeout(() => {
  311. showModal.value = false;
  312. reloadTable();
  313. });
  314. })
  315. .catch((e: Error) => {
  316. message.error(e.message ?? '操作失败');
  317. });
  318. } else {
  319. message.error('请填写完整信息');
  320. }
  321. formBtnLoading.value = false;
  322. });
  323. }
  324. function handleEdit(record: Recordable) {
  325. showModal.value = true;
  326. formParams.value = cloneDeep(record);
  327. pages.value = record.pages;
  328. permissions.value = record.permissions;
  329. formSystems.value = record.systems;
  330. selectSystem.value = record.systems;
  331. activeTab.value = record.systems[0];
  332. }
  333. function handleDelete(record: Recordable) {
  334. console.log('点击了删除', record);
  335. message.info('点击了删除');
  336. }
  337. onMounted(async () => {
  338. optionsPermissions.value = await RoleAuthOption();
  339. optionsPages.value = await RolePageOption();
  340. systems.value = await GetServiceList();
  341. });
  342. </script>
  343. <style lang="less" scoped>
  344. .checkbox-group-wrapper {
  345. border: 1px solid #eee;
  346. padding: 12px;
  347. border-radius: 3px;
  348. }
  349. .checkbox-container {
  350. max-height: 300px; /* 设置最大高度 */
  351. overflow-y: auto; /* 超出时显示垂直滚动条 */
  352. padding-right: 8px; /* 为滚动条留出空间 */
  353. display: flex;
  354. flex-direction: column;
  355. gap: 8px; /* 设置选项之间的间距 */
  356. }
  357. /* 滚动条样式 */
  358. .checkbox-container::-webkit-scrollbar {
  359. width: 6px;
  360. }
  361. .checkbox-container::-webkit-scrollbar-thumb {
  362. background-color: #d9d9d9;
  363. border-radius: 3px;
  364. }
  365. .checkbox-container::-webkit-scrollbar-thumb:hover {
  366. background-color: #c1c1c1;
  367. }
  368. </style>