index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. <script setup name="Role" lang="ts">
  2. import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect } from "@/api/system/role";
  3. import { roleMenuTreeselect, treeselect as menuTreeselect } from '@/api/system/menu/index';
  4. import { RoleVO, RoleForm, RoleQuery, DeptTreeOption } from '@/api/system/role/types';
  5. import { MenuTreeOption, RoleMenuTree } from '@/api/system/menu/types';
  6. import { ComponentInternalInstance } from 'vue';
  7. import { ElTree, ElForm, DateModelType } from 'element-plus';
  8. const router = useRouter();
  9. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  10. const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
  11. const roleList = ref<RoleVO[]>();
  12. const loading = ref(true)
  13. const showSearch = ref(true)
  14. const ids = ref<Array<string | number>>([])
  15. const single = ref(true)
  16. const multiple = ref(true)
  17. const total = ref(0)
  18. const dateRange = ref<[DateModelType, DateModelType]>(['', ''])
  19. const menuOptions = ref<MenuTreeOption[]>([])
  20. const menuExpand = ref(false)
  21. const menuNodeAll = ref(false)
  22. const deptExpand = ref(true)
  23. const deptNodeAll = ref(false)
  24. const deptOptions = ref<DeptTreeOption[]>([])
  25. const openDataScope = ref(false)
  26. /** 数据范围选项*/
  27. const dataScopeOptions = ref([
  28. { value: "1", label: "全部数据权限" },
  29. { value: "2", label: "自定数据权限" },
  30. { value: "3", label: "本部门数据权限" },
  31. { value: "4", label: "本部门及以下数据权限" },
  32. { value: "5", label: "仅本人数据权限" }
  33. ])
  34. const queryFormRef = ref(ElForm);
  35. const roleFormRef = ref(ElForm);
  36. const dataScopeRef = ref(ElForm);
  37. const menuRef = ref(ElTree);
  38. const deptRef = ref(ElTree);
  39. const initForm: RoleForm = {
  40. roleId: undefined,
  41. roleSort: 1,
  42. status: '0',
  43. roleName: '',
  44. roleKey: '',
  45. menuCheckStrictly: true,
  46. deptCheckStrictly: true,
  47. remark: '',
  48. dataScope: 1,
  49. menuIds: [],
  50. deptIds: [],
  51. }
  52. const data = reactive<PageData<RoleForm, RoleQuery>>({
  53. form: {...initForm},
  54. queryParams: {
  55. pageNum: 1,
  56. pageSize: 10,
  57. roleName: '',
  58. roleKey: '',
  59. status: '',
  60. },
  61. rules: {
  62. roleName: [{ required: true, message: "角色名称不能为空", trigger: "blur" }],
  63. roleKey: [{ required: true, message: "权限字符不能为空", trigger: "blur" }],
  64. roleSort: [{ required: true, message: "角色顺序不能为空", trigger: "blur" }]
  65. }
  66. })
  67. const { form, queryParams, rules } = toRefs(data)
  68. const dialog = reactive<DialogOption>({
  69. visible: false,
  70. title: ''
  71. });
  72. /**
  73. * 查询角色列表
  74. */
  75. const getList = () => {
  76. loading.value = true
  77. listRole(proxy?.addDateRange(queryParams.value, dateRange.value)).then(res => {
  78. roleList.value = res.rows
  79. total.value = res.total
  80. loading.value = false
  81. })
  82. }
  83. /**
  84. * 搜索按钮操作
  85. */
  86. const handleQuery = () => {
  87. queryParams.value.pageNum = 1;
  88. getList();
  89. }
  90. /** 重置 */
  91. const resetQuery = () => {
  92. dateRange.value = ['', '']
  93. queryFormRef.value.resetFields();
  94. handleQuery();
  95. }
  96. /**删除按钮操作 */
  97. const handleDelete = async (row?: RoleVO) => {
  98. const roleids = row?.roleId || ids.value;
  99. await proxy?.$modal.confirm('是否确认删除角色编号为' + roleids + '数据项目');
  100. await delRole(roleids);
  101. getList();
  102. proxy?.$modal.msgSuccess('删除成功');
  103. }
  104. /** 导出按钮操作 */
  105. const handleExport = () => {
  106. proxy?.download("system/role/export", {
  107. ...queryParams.value,
  108. }, `role_${new Date().getTime()}.xlsx`)
  109. }
  110. /** 多选框选中数据 */
  111. const handleSelectionChange = (selection: RoleVO[]) => {
  112. ids.value = selection.map((item: RoleVO) => item.roleId);
  113. single.value = selection.length != 1;
  114. multiple.value = !selection.length;
  115. }
  116. /** 角色状态修改 */
  117. const handleStatusChange = async (row: RoleVO) => {
  118. let text = row.status === "0" ? "启用" : "停用";
  119. try {
  120. await proxy?.$modal.confirm('确认要"' + text + '""' + row.roleName + '"角色吗?');
  121. await changeRoleStatus(row.roleId, row.status);
  122. proxy?.$modal.msgSuccess(text + "成功");
  123. } catch {
  124. row.status = row.status === "0" ? "1" : "0";
  125. }
  126. }
  127. /** 分配用户 */
  128. const handleAuthUser = (row: RoleVO) => {
  129. router.push("/system/role-auth/user/" + row.roleId);
  130. }
  131. /** 查询菜单树结构 */
  132. const getMenuTreeselect = async () => {
  133. const res = await menuTreeselect();
  134. menuOptions.value = res.data;
  135. }
  136. /** 所有部门节点数据 */
  137. const getDeptAllCheckedKeys = () => {
  138. // 目前被选中的部门节点
  139. let checkedKeys = deptRef.value.getCheckedKeys();
  140. // 半选中的部门节点
  141. let halfCheckedKeys = deptRef.value.getHalfCheckedKeys();
  142. checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys);
  143. return checkedKeys
  144. }
  145. /** 重置新增的表单以及其他数据 */
  146. const reset = () => {
  147. menuRef.value.setCheckedKeys([]);
  148. menuExpand.value = false
  149. menuNodeAll.value = false
  150. deptExpand.value = true
  151. deptNodeAll.value = false
  152. form.value = initForm
  153. roleFormRef.value.resetFields();
  154. }
  155. /** 添加角色 */
  156. const handleAdd = () => {
  157. dialog.visible = true;
  158. dialog.title = "添加角色";
  159. nextTick(() => {
  160. reset();
  161. getMenuTreeselect();
  162. })
  163. }
  164. /** 修改角色 */
  165. const handleUpdate = async (row?: RoleVO) => {
  166. const roleId = row?.roleId || ids.value[0]
  167. const roleMenu = getRoleMenuTreeselect(roleId)
  168. const { data } = await getRole(roleId);
  169. dialog.visible = true;
  170. dialog.title = "修改角色";
  171. nextTick(() => {
  172. reset();
  173. Object.assign(form.value, data);
  174. form.value.roleSort = Number(form.value.roleSort);
  175. nextTick(async () => {
  176. const res = await roleMenu;
  177. let checkedKeys = res.checkedKeys;
  178. checkedKeys.forEach((v) => {
  179. nextTick(() => {
  180. menuRef.value.setChecked(v, true, false);
  181. })
  182. })
  183. })
  184. })
  185. }
  186. /** 根据角色ID查询菜单树结构 */
  187. const getRoleMenuTreeselect = (roleId: string | number) => {
  188. return roleMenuTreeselect(roleId).then((res): RoleMenuTree => {
  189. menuOptions.value = res.data.menus;
  190. return res.data;
  191. })
  192. }
  193. /** 根据角色ID查询部门树结构 */
  194. const getRoleDeptTreeSelect = async (roleId: string | number) => {
  195. const res = await deptTreeSelect(roleId);
  196. deptOptions.value = res.data.depts;
  197. return res.data;
  198. }
  199. /** 树权限(展开/折叠)*/
  200. const handleCheckedTreeExpand = (value: any, type: string) => {
  201. if (type == "menu") {
  202. let treeList = menuOptions.value;
  203. for (let i = 0; i < treeList.length; i++) {
  204. menuRef.value.store.nodesMap[treeList[i].id].expanded = value;
  205. }
  206. } else if (type == "dept") {
  207. let treeList = deptOptions.value;
  208. for (let i = 0; i < treeList.length; i++) {
  209. deptRef.value.store.nodesMap[treeList[i].id].expanded = value;
  210. }
  211. }
  212. }
  213. /** 树权限(全选/全不选) */
  214. const handleCheckedTreeNodeAll = (value: any, type: string) => {
  215. if (type == "menu") {
  216. menuRef.value.setCheckedNodes(value ? menuOptions.value : []);
  217. } else if (type == "dept") {
  218. deptRef.value.setCheckedNodes(value ? deptOptions.value : []);
  219. }
  220. }
  221. /** 树权限(父子联动) */
  222. const handleCheckedTreeConnect = (value: any, type: string) => {
  223. if (type == "menu") {
  224. form.value.menuCheckStrictly = value;
  225. } else if (type == "dept") {
  226. form.value.deptCheckStrictly = value;
  227. }
  228. }
  229. /** 所有菜单节点数据 */
  230. const getMenuAllCheckedKeys = () => {
  231. // 目前被选中的菜单节点
  232. let checkedKeys = menuRef.value.getCheckedKeys();
  233. // 半选中的菜单节点
  234. let halfCheckedKeys = menuRef.value.getHalfCheckedKeys();
  235. checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys);
  236. return checkedKeys;
  237. }
  238. /** 提交按钮 */
  239. const submitForm = () => {
  240. roleFormRef.value.validate(async (valid: boolean) => {
  241. if (valid) {
  242. form.value.menuIds = getMenuAllCheckedKeys()
  243. form.value.roleId ? await updateRole(form.value) : await addRole(form.value);
  244. proxy?.$modal.msgSuccess("操作成功")
  245. dialog.visible = false
  246. getList()
  247. }
  248. })
  249. }
  250. /** 取消按钮 */
  251. const cancel = () => {
  252. reset()
  253. dialog.visible = false;
  254. }
  255. /** 选择角色权限范围触发 */
  256. const dataScopeSelectChange = (value: string) => {
  257. if (value !== "2") {
  258. deptRef.value.setCheckedKeys([])
  259. }
  260. }
  261. /** 分配数据权限操作 */
  262. const handleDataScope = async (row: RoleVO) => {
  263. const roleDeptTreeselect = getRoleDeptTreeSelect(row.roleId);
  264. const response = await getRole(row.roleId);
  265. Object.assign(form.value, response.data);
  266. openDataScope.value = true;
  267. dialog.title = "分配数据权限";
  268. nextTick(async () => {
  269. const res = await roleDeptTreeselect;
  270. nextTick(() => {
  271. if (deptRef.value) {
  272. deptRef.value.setCheckedKeys(res.checkedKeys);
  273. }
  274. })
  275. })
  276. }
  277. /** 提交按钮(数据权限) */
  278. const submitDataScope = async () => {
  279. if (form.value.roleId) {
  280. form.value.deptIds = getDeptAllCheckedKeys();
  281. await dataScope(form.value);
  282. proxy?.$modal.msgSuccess("修改成功");
  283. openDataScope.value = false;
  284. getList();
  285. }
  286. }
  287. /** 取消按钮(数据权限)*/
  288. const cancelDataScope = () => {
  289. dataScopeRef.value.resetFields();
  290. form.value = {...initForm};
  291. openDataScope.value = false;
  292. }
  293. onMounted(() => {
  294. getList();
  295. });
  296. </script>
  297. <template>
  298. <div class="p-2">
  299. <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
  300. <div class="search" v-show="showSearch">
  301. <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
  302. <el-form-item label="角色名称" prop="roleName">
  303. <el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
  304. </el-form-item>
  305. <el-form-item label="权限字符" prop="roleKey">
  306. <el-input v-model="queryParams.roleKey" placeholder="请输入权限字符" clearable style="width: 240px" @keyup.enter="handleQuery" />
  307. </el-form-item>
  308. <el-form-item label="状态" prop="status">
  309. <el-select v-model="queryParams.status" placeholder="角色状态" clearable style="width: 240px">
  310. <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
  311. </el-select>
  312. </el-form-item>
  313. <el-form-item label="创建时间" style="width: 308px">
  314. <el-date-picker
  315. v-model="dateRange"
  316. value-format="YYYY-MM-DD"
  317. type="daterange"
  318. range-separator="-"
  319. start-placeholder="开始日期"
  320. end-placeholder="结束日期"
  321. :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
  322. ></el-date-picker>
  323. </el-form-item>
  324. <el-form-item>
  325. <el-button type="primary" @click="handleQuery" icon="Search">搜索</el-button>
  326. <el-button @click="resetQuery" icon="Refresh">重置</el-button>
  327. </el-form-item>
  328. </el-form>
  329. </div>
  330. </transition>
  331. <el-card shadow="never">
  332. <template #header>
  333. <el-row :gutter="10">
  334. <el-col :span="1.5">
  335. <el-button type="primary" plain @click="handleAdd()" icon="Plus" v-hasPermi="['system:role:add']">新增</el-button>
  336. </el-col>
  337. <el-col :span="1.5">
  338. <el-button type="success" plain @click="handleUpdate()" :disabled="single" icon="Edit" v-hasPermi="['system:role:edit']">修改</el-button>
  339. </el-col>
  340. <el-col :span="1.5">
  341. <el-button type="danger" plain :disabled="ids.length === 0" @click="handleDelete()" v-hasPermi="['system:role:delete']">删除</el-button>
  342. </el-col>
  343. <el-col :span="1.5">
  344. <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:role:export']">导出</el-button>
  345. </el-col>
  346. <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
  347. </el-row>
  348. </template>
  349. <el-table ref="roleTableRef" v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
  350. <el-table-column type="selection" width="55" align="center" />
  351. <el-table-column label="角色编号" prop="roleId" width="120" />
  352. <el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
  353. <el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="150" />
  354. <el-table-column label="显示顺序" prop="roleSort" width="100" />
  355. <el-table-column label="状态" align="center" width="100">
  356. <template #default="scope">
  357. <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
  358. </template>
  359. </el-table-column>
  360. <el-table-column label="创建时间" align="center" prop="createTime">
  361. <template #default="scope">
  362. <span>{{ parseTime(scope.row.createTime) }}</span>
  363. </template>
  364. </el-table-column>
  365. <el-table-column fixed="right" label="操作" width="180">
  366. <template #default="scope">
  367. <el-tooltip content="修改" placement="top" v-if="scope.row.roleId !== 1">
  368. <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
  369. </el-tooltip>
  370. <el-tooltip content="删除" placement="top" v-if="scope.row.roleId !== 1">
  371. <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:role:remove']"></el-button>
  372. </el-tooltip>
  373. <el-tooltip content="数据权限" placement="top" v-if="scope.row.roleId !== 1">
  374. <el-button link type="primary" icon="CircleCheck" @click="handleDataScope(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
  375. </el-tooltip>
  376. <el-tooltip content="分配用户" placement="top" v-if="scope.row.roleId !== 1">
  377. <el-button link type="primary" icon="User" @click="handleAuthUser(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
  378. </el-tooltip>
  379. </template>
  380. </el-table-column>
  381. </el-table>
  382. <pagination
  383. v-if="total > 0"
  384. v-model:total="total"
  385. v-model:page="queryParams.pageNum"
  386. v-model:limit="queryParams.pageSize"
  387. @pagination="handleQuery"
  388. />
  389. </el-card>
  390. <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
  391. <el-form ref="roleFormRef" :model="form" :rules="rules" label-width="100px">
  392. <el-form-item label="角色名称" prop="roleName">
  393. <el-input v-model="form.roleName" placeholder="请输入角色名称" />
  394. </el-form-item>
  395. <el-form-item prop="roleKey">
  396. <template #label>
  397. <span>
  398. <el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasRole('admin')`)" placement="top">
  399. <el-icon><question-filled /></el-icon>
  400. </el-tooltip>
  401. 权限字符
  402. </span>
  403. </template>
  404. <el-input v-model="form.roleKey" placeholder="请输入权限字符" />
  405. </el-form-item>
  406. <el-form-item label="角色顺序" prop="roleSort">
  407. <el-input-number v-model="form.roleSort" controls-position="right" :min="0" />
  408. </el-form-item>
  409. <el-form-item label="状态">
  410. <el-radio-group v-model="form.status">
  411. <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">{{
  412. dict.label
  413. }}</el-radio>
  414. </el-radio-group>
  415. </el-form-item>
  416. <el-form-item label="菜单权限">
  417. <el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
  418. <el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox>
  419. <el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox>
  420. <el-tree
  421. class="tree-border"
  422. :data="menuOptions"
  423. show-checkbox
  424. ref="menuRef"
  425. node-key="id"
  426. :check-strictly="!form.menuCheckStrictly"
  427. empty-text="加载中,请稍候"
  428. :props="{ label: 'label', children: 'children' }"
  429. ></el-tree>
  430. </el-form-item>
  431. <el-form-item label="备注">
  432. <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
  433. </el-form-item>
  434. </el-form>
  435. <template #footer>
  436. <div class="dialog-footer">
  437. <el-button type="primary" @click="submitForm">确 定</el-button>
  438. <el-button @click="cancel">取 消</el-button>
  439. </div>
  440. </template>
  441. </el-dialog>
  442. <!-- 分配角色数据权限对话框 -->
  443. <el-dialog :title="dialog.title" v-model="openDataScope" width="500px" append-to-body>
  444. <el-form :model="form" label-width="80px" ref="dataScopeRef">
  445. <el-form-item label="角色名称">
  446. <el-input v-model="form.roleName" :disabled="true" />
  447. </el-form-item>
  448. <el-form-item label="权限字符">
  449. <el-input v-model="form.roleKey" :disabled="true" />
  450. </el-form-item>
  451. <el-form-item label="权限范围">
  452. <el-select v-model="form.dataScope" @change="dataScopeSelectChange">
  453. <el-option v-for="item in dataScopeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
  454. </el-select>
  455. </el-form-item>
  456. <el-form-item label="数据权限" v-show="form.dataScope === 2">
  457. <el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</el-checkbox>
  458. <el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox>
  459. <el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')">父子联动</el-checkbox>
  460. <el-tree
  461. class="tree-border"
  462. :data="deptOptions"
  463. show-checkbox
  464. default-expand-all
  465. ref="deptRef"
  466. node-key="id"
  467. :check-strictly="!form.deptCheckStrictly"
  468. empty-text="加载中,请稍候"
  469. :props="{ label: 'label', children: 'children' }"
  470. ></el-tree>
  471. </el-form-item>
  472. </el-form>
  473. <template #footer>
  474. <div class="dialog-footer">
  475. <el-button type="primary" @click="submitDataScope">确 定</el-button>
  476. <el-button @click="cancelDataScope">取 消</el-button>
  477. </div>
  478. </template>
  479. </el-dialog>
  480. </div>
  481. </template>