index.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. <!-- 角色管理 -->
  2. <template>
  3. <div class="app-container">
  4. <PageSearch
  5. ref="searchRef"
  6. :search-config="searchConfig"
  7. @query-click="handleQueryClick"
  8. @reset-click="handleResetClick"
  9. />
  10. <PageContent ref="contentRef" :content-config="contentConfig">
  11. <template #toolbar="{ toolbarRight, onToolbar, removeIds, cols }">
  12. <CrudToolbarLeft
  13. :remove-ids="removeIds"
  14. :perm-create="['module_system:role:create']"
  15. :perm-delete="['module_system:role:delete']"
  16. :perm-patch="['module_system:role:patch']"
  17. @add="handleOpenDialog('create')"
  18. @delete="onToolbar('delete')"
  19. @more="handleMoreClick"
  20. />
  21. <div class="data-table__toolbar--right">
  22. <CrudToolbarRight :buttons="toolbarRight" :cols="cols" :on-toolbar="onToolbar">
  23. <template #prepend>
  24. <el-tooltip content="导出">
  25. <el-button
  26. v-hasPerm="['module_system:role:export']"
  27. type="warning"
  28. icon="download"
  29. circle
  30. @click="handleOpenExportsModal"
  31. />
  32. </el-tooltip>
  33. </template>
  34. </CrudToolbarRight>
  35. </div>
  36. </template>
  37. <template #table="{ data, loading, tableRef, onSelectionChange, pagination }">
  38. <div class="data-table__content">
  39. <el-table
  40. :ref="tableRef as any"
  41. v-loading="loading"
  42. row-key="id"
  43. :data="data"
  44. height="100%"
  45. border
  46. stripe
  47. @selection-change="onSelectionChange"
  48. >
  49. <template #empty>
  50. <el-empty :image-size="80" description="暂无数据" />
  51. </template>
  52. <el-table-column
  53. v-if="contentCols.find((col) => col.prop === 'selection')?.show"
  54. type="selection"
  55. width="55"
  56. align="center"
  57. />
  58. <el-table-column
  59. v-if="contentCols.find((col) => col.prop === 'index')?.show"
  60. fixed
  61. label="序号"
  62. width="60"
  63. >
  64. <template #default="scope">
  65. {{ (pagination.currentPage - 1) * pagination.pageSize + scope.$index + 1 }}
  66. </template>
  67. </el-table-column>
  68. <el-table-column
  69. v-if="contentCols.find((col) => col.prop === 'name')?.show"
  70. key="name"
  71. label="角色名称"
  72. prop="name"
  73. min-width="100"
  74. />
  75. <el-table-column
  76. v-if="contentCols.find((col) => col.prop === 'code')?.show"
  77. key="code"
  78. label="角色编码"
  79. prop="code"
  80. min-width="100"
  81. />
  82. <el-table-column
  83. v-if="contentCols.find((col) => col.prop === 'data_scope')?.show"
  84. key="data_scope"
  85. label="数据权限"
  86. prop="data_scope"
  87. min-width="200"
  88. >
  89. <template #default="scope">
  90. <el-tag v-if="scope.row.data_scope === 1" type="primary">仅本人数据权限</el-tag>
  91. <el-tag v-else-if="scope.row.data_scope === 2" type="info">本部门数据权限</el-tag>
  92. <el-tag v-else-if="scope.row.data_scope === 3" type="warning">
  93. 本部门及以下数据权限
  94. </el-tag>
  95. <el-tag v-else-if="scope.row.data_scope === 4" type="success">全部数据权限</el-tag>
  96. <el-tag v-else type="danger">自定义数据权限</el-tag>
  97. </template>
  98. </el-table-column>
  99. <el-table-column
  100. v-if="contentCols.find((col) => col.prop === 'depts')?.show"
  101. key="depts"
  102. label="所属部门"
  103. prop="depts"
  104. min-width="200"
  105. >
  106. <template #default="scope">
  107. <template v-if="scope.row.depts && scope.row.depts.length > 0">
  108. <el-tag
  109. v-for="dept in scope.row.depts.slice(0, 3)"
  110. :key="dept.id"
  111. type="info"
  112. style="margin-right: 4px; margin-bottom: 4px"
  113. >
  114. {{ dept.name }}
  115. </el-tag>
  116. <el-tag v-if="scope.row.depts.length > 3" type="info" style="margin-bottom: 4px">
  117. +{{ scope.row.depts.length - 3 }}
  118. </el-tag>
  119. </template>
  120. <span v-else style="color: var(--el-text-color-placeholder)">-</span>
  121. </template>
  122. </el-table-column>
  123. <el-table-column
  124. v-if="contentCols.find((col) => col.prop === 'order')?.show"
  125. key="order"
  126. label="排序"
  127. prop="order"
  128. min-width="80"
  129. show-overflow-tooltip
  130. />
  131. <el-table-column
  132. v-if="contentCols.find((col) => col.prop === 'status')?.show"
  133. key="status"
  134. label="状态"
  135. prop="status"
  136. min-width="80"
  137. >
  138. <template #default="scope">
  139. <el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
  140. {{ scope.row.status === "0" ? "启用" : "停用" }}
  141. </el-tag>
  142. </template>
  143. </el-table-column>
  144. <el-table-column
  145. v-if="contentCols.find((col) => col.prop === 'description')?.show"
  146. key="description"
  147. label="描述"
  148. prop="description"
  149. min-width="100"
  150. />
  151. <el-table-column
  152. v-if="contentCols.find((col) => col.prop === 'created_time')?.show"
  153. key="created_time"
  154. label="创建时间"
  155. prop="created_time"
  156. min-width="200"
  157. sortable
  158. />
  159. <el-table-column
  160. v-if="contentCols.find((col) => col.prop === 'updated_time')?.show"
  161. key="updated_time"
  162. label="更新时间"
  163. prop="updated_time"
  164. min-width="200"
  165. sortable
  166. />
  167. <el-table-column
  168. v-if="contentCols.find((col) => col.prop === 'operation')?.show"
  169. fixed="right"
  170. label="操作"
  171. align="center"
  172. min-width="280"
  173. >
  174. <template #default="scope">
  175. <el-button
  176. v-hasPerm="['module_system:role:permission']"
  177. type="warning"
  178. size="small"
  179. link
  180. icon="position"
  181. :disabled="scope.row.id === 1"
  182. @click="
  183. scope.row.id === 1
  184. ? ElMessage.warning('系统默认角色,不可操作')
  185. : handleOpenAssignPermDialog(scope.row.id, scope.row.name)
  186. "
  187. >
  188. 分配权限
  189. </el-button>
  190. <el-button
  191. v-hasPerm="['module_system:role:detail']"
  192. type="info"
  193. size="small"
  194. link
  195. icon="View"
  196. @click="handleOpenDialog('detail', scope.row.id)"
  197. >
  198. 详情
  199. </el-button>
  200. <el-button
  201. v-hasPerm="['module_system:role:update']"
  202. type="primary"
  203. size="small"
  204. link
  205. icon="edit"
  206. :disabled="scope.row.id === 1"
  207. @click="
  208. scope.row.id === 1
  209. ? ElMessage.warning('系统默认角色,不可操作')
  210. : handleOpenDialog('update', scope.row.id)
  211. "
  212. >
  213. 编辑
  214. </el-button>
  215. <el-button
  216. v-hasPerm="['module_system:role:delete']"
  217. type="danger"
  218. size="small"
  219. link
  220. icon="delete"
  221. :disabled="scope.row.id === 1"
  222. @click="
  223. scope.row.id === 1
  224. ? ElMessage.warning('系统默认角色,不可操作')
  225. : handleRowDelete(scope.row.id)
  226. "
  227. >
  228. 删除
  229. </el-button>
  230. </template>
  231. </el-table-column>
  232. </el-table>
  233. </div>
  234. </template>
  235. </PageContent>
  236. <EnhancedDialog
  237. v-model="dialogVisible.visible"
  238. :title="dialogVisible.title"
  239. @close="handleCloseDialog"
  240. >
  241. <template v-if="dialogVisible.type === 'detail'">
  242. <el-descriptions :column="4" border>
  243. <el-descriptions-item label="角色名称" :span="2">
  244. {{ detailFormData.name }}
  245. </el-descriptions-item>
  246. <el-descriptions-item label="排序" :span="2">
  247. {{ detailFormData.order }}
  248. </el-descriptions-item>
  249. <el-descriptions-item label="角色编码" :span="2">
  250. {{ detailFormData.code }}
  251. </el-descriptions-item>
  252. <el-descriptions-item label="数据权限" :span="2">
  253. <el-tag v-if="detailFormData.data_scope === 1" type="primary">仅本人数据权限</el-tag>
  254. <el-tag v-else-if="detailFormData.data_scope === 2" type="info">本部门数据权限</el-tag>
  255. <el-tag v-else-if="detailFormData.data_scope === 3" type="warning">
  256. 本部门及以下数据权限
  257. </el-tag>
  258. <el-tag v-else-if="detailFormData.data_scope === 4" type="success">全部数据权限</el-tag>
  259. <el-tag v-else type="danger">自定义数据权限</el-tag>
  260. </el-descriptions-item>
  261. <el-descriptions-item label="所属部门" :span="2">
  262. <template v-if="detailFormData.depts && detailFormData.depts.length > 0">
  263. <el-tag
  264. v-for="dept in detailFormData.depts"
  265. :key="dept.id"
  266. type="info"
  267. style="margin-right: 4px; margin-bottom: 4px"
  268. >
  269. {{ dept.name }}
  270. </el-tag>
  271. </template>
  272. <span v-else style="color: var(--el-text-color-placeholder)">-</span>
  273. </el-descriptions-item>
  274. <el-descriptions-item label="状态" :span="2">
  275. <el-tag :type="detailFormData.status === '0' ? 'success' : 'danger'">
  276. {{ detailFormData.status === "0" ? "启用" : "停用" }}
  277. </el-tag>
  278. </el-descriptions-item>
  279. <el-descriptions-item label="创建时间" :span="2">
  280. {{ detailFormData.created_time }}
  281. </el-descriptions-item>
  282. <el-descriptions-item label="更新时间" :span="2">
  283. {{ detailFormData.updated_time }}
  284. </el-descriptions-item>
  285. <el-descriptions-item label="描述" :span="4">
  286. {{ detailFormData.description }}
  287. </el-descriptions-item>
  288. </el-descriptions>
  289. </template>
  290. <template v-else>
  291. <el-form
  292. ref="dataFormRef"
  293. :model="formData"
  294. :rules="rules"
  295. label-suffix=":"
  296. label-width="auto"
  297. label-position="right"
  298. >
  299. <el-form-item label="角色名称" prop="name">
  300. <el-input v-model="formData.name" placeholder="请输入角色名称" />
  301. </el-form-item>
  302. <el-form-item label="排序" prop="order">
  303. <el-input-number
  304. v-model="formData.order"
  305. controls-position="right"
  306. :min="0"
  307. style="width: 100px"
  308. />
  309. </el-form-item>
  310. <el-form-item label="角色编码" prop="code">
  311. <el-input
  312. v-model="formData.code"
  313. placeholder="字母开头,2-16位字母/数字/下划线"
  314. maxlength="16"
  315. show-word-limit
  316. />
  317. </el-form-item>
  318. <el-form-item label="状态" prop="status">
  319. <el-radio-group v-model="formData.status">
  320. <el-radio value="0">启用</el-radio>
  321. <el-radio value="1">停用</el-radio>
  322. </el-radio-group>
  323. </el-form-item>
  324. <el-form-item label="描述" prop="description">
  325. <el-input
  326. v-model="formData.description"
  327. :rows="4"
  328. :maxlength="100"
  329. show-word-limit
  330. type="textarea"
  331. placeholder="请输入描述"
  332. />
  333. </el-form-item>
  334. </el-form>
  335. </template>
  336. <template #footer>
  337. <div class="dialog-footer">
  338. <el-button @click="handleCloseDialog">取消</el-button>
  339. <el-button
  340. v-if="dialogVisible.type !== 'detail'"
  341. type="primary"
  342. :loading="submitLoading"
  343. @click="handleSubmit"
  344. >
  345. 确定
  346. </el-button>
  347. <el-button v-else type="primary" @click="handleCloseDialog">确定</el-button>
  348. </div>
  349. </template>
  350. </EnhancedDialog>
  351. <PermissonDrawer
  352. v-if="drawerVisible"
  353. v-model="drawerVisible"
  354. :role-name="checkedRole.name"
  355. :role-id="checkedRole.id"
  356. @saved="refreshList"
  357. />
  358. <ExportModal
  359. v-model="exportsDialogVisible"
  360. :content-config="curdContentConfig"
  361. :query-params="exportQueryParams"
  362. :page-data="exportPageData"
  363. :selection-data="exportSelectionData"
  364. />
  365. </div>
  366. </template>
  367. <script setup lang="ts">
  368. defineOptions({
  369. name: "Role",
  370. inheritAttrs: false,
  371. });
  372. import { ElMessage, ElMessageBox } from "element-plus";
  373. import { ref, reactive, computed, unref } from "vue";
  374. import { fetchAllPages } from "@/utils/fetchAllPages";
  375. import RoleAPI, { RoleTable, RoleForm, TablePageQuery } from "@/api/module_system/role";
  376. import { useUserStore } from "@/store";
  377. import ExportModal from "@/components/CURD/ExportModal.vue";
  378. import CrudToolbarLeft from "@/components/CURD/CrudToolbarLeft.vue";
  379. import CrudToolbarRight from "@/components/CURD/CrudToolbarRight.vue";
  380. import PageSearch from "@/components/CURD/PageSearch.vue";
  381. import PageContent from "@/components/CURD/PageContent.vue";
  382. import EnhancedDialog from "@/components/CURD/EnhancedDialog.vue";
  383. import PermissonDrawer from "./components/PermissonDrawer.vue";
  384. import { useCrudList } from "@/components/CURD/useCrudList";
  385. import type { IContentConfig, ISearchConfig } from "@/components/CURD/types";
  386. const { searchRef, contentRef, handleQueryClick, handleResetClick, refreshList } = useCrudList();
  387. const dataFormRef = ref();
  388. const submitLoading = ref(false);
  389. const searchConfig = reactive<ISearchConfig>({
  390. permPrefix: "module_system:role",
  391. colon: true,
  392. isExpandable: true,
  393. showNumber: 2,
  394. form: { labelWidth: "auto" },
  395. formItems: [
  396. {
  397. prop: "name",
  398. label: "角色名称",
  399. type: "input",
  400. attrs: { placeholder: "请输入角色名称", clearable: true },
  401. },
  402. {
  403. prop: "status",
  404. label: "状态",
  405. type: "select",
  406. options: [
  407. { label: "启用", value: "true" },
  408. { label: "停用", value: "false" },
  409. ],
  410. attrs: { placeholder: "请选择状态", clearable: true, style: { width: "167.5px" } },
  411. },
  412. {
  413. prop: "created_time",
  414. label: "创建时间",
  415. type: "date-picker",
  416. initialValue: [],
  417. attrs: {
  418. type: "datetimerange",
  419. valueFormat: "YYYY-MM-DD HH:mm:ss",
  420. rangeSeparator: "至",
  421. startPlaceholder: "开始日期",
  422. endPlaceholder: "结束日期",
  423. style: { width: "340px" },
  424. },
  425. },
  426. ],
  427. });
  428. const contentCols = reactive<
  429. Array<{
  430. prop?: string;
  431. label?: string;
  432. show?: boolean;
  433. }>
  434. >([
  435. { prop: "selection", label: "选择框", show: true },
  436. { prop: "index", label: "序号", show: true },
  437. { prop: "name", label: "角色名称", show: true },
  438. { prop: "data_scope", label: "数据权限", show: true },
  439. { prop: "depts", label: "所属部门", show: true },
  440. { prop: "order", label: "排序", show: true },
  441. { prop: "code", label: "角色编码", show: true },
  442. { prop: "status", label: "状态", show: true },
  443. { prop: "description", label: "描述", show: true },
  444. { prop: "created_time", label: "创建时间", show: true },
  445. { prop: "updated_time", label: "更新时间", show: true },
  446. { prop: "operation", label: "操作", show: true },
  447. ]);
  448. const contentConfig = reactive<IContentConfig<TablePageQuery>>({
  449. permPrefix: "module_system:role",
  450. pk: "id",
  451. cols: contentCols as IContentConfig["cols"],
  452. hideColumnFilter: false,
  453. toolbar: [],
  454. defaultToolbar: [{ name: "refresh", perm: "query" }, "filter"],
  455. pagination: {
  456. pageSize: 10,
  457. pageSizes: [10, 20, 30, 50],
  458. },
  459. request: { page_no: "page_no", page_size: "page_size" },
  460. indexAction: async (params) => {
  461. const res = await RoleAPI.listRole(params as TablePageQuery);
  462. return {
  463. total: res.data.data?.total || 0,
  464. list: res.data.data?.items || res.data.data?.list || [],
  465. };
  466. },
  467. deleteAction: async (ids) => {
  468. await RoleAPI.deleteRole(
  469. ids
  470. .split(",")
  471. .map((s) => Number(s.trim()))
  472. .filter((n) => !Number.isNaN(n))
  473. );
  474. const userStore = useUserStore();
  475. await userStore.getUserInfo();
  476. },
  477. deleteConfirm: {
  478. title: "警告",
  479. message: "确认删除该项数据?",
  480. type: "warning",
  481. },
  482. });
  483. function handleRowDelete(id: number) {
  484. contentRef.value?.handleDelete(id);
  485. }
  486. const drawerVisible = ref(false);
  487. const checkedRole = ref({ id: 0, name: "" });
  488. const exportsDialogVisible = ref(false);
  489. const exportQueryParams = computed(() => searchRef.value?.getQueryParams() ?? {});
  490. const exportPageData = computed(() => (unref(contentRef.value?.pageData) ?? []) as RoleTable[]);
  491. const exportSelectionData = computed(
  492. () => (contentRef.value?.getSelectionData() ?? []) as RoleTable[]
  493. );
  494. const exportColumns = [
  495. { prop: "name", label: "角色名称" },
  496. { prop: "code", label: "角色编码" },
  497. { prop: "data_scope", label: "数据权限" },
  498. { prop: "depts", label: "所属部门" },
  499. { prop: "order", label: "排序" },
  500. { prop: "status", label: "状态" },
  501. { prop: "description", label: "描述" },
  502. { prop: "created_time", label: "创建时间" },
  503. { prop: "updated_time", label: "更新时间" },
  504. ];
  505. const curdContentConfig = {
  506. permPrefix: "module_system:role",
  507. cols: exportColumns as unknown as IContentConfig["cols"],
  508. exportsAction: async (params: Record<string, unknown>) => {
  509. const query: Record<string, unknown> = { ...params };
  510. if (typeof query.status === "string") {
  511. query.status = query.status === "true";
  512. }
  513. return fetchAllPages<RoleTable>({
  514. initialQuery: query,
  515. fetchPage: async (q) => {
  516. const res = await RoleAPI.listRole(q as unknown as TablePageQuery);
  517. return {
  518. total: res.data?.data?.total ?? 0,
  519. list: res.data?.data?.items || res.data?.data?.list || [],
  520. };
  521. },
  522. });
  523. },
  524. } as unknown as IContentConfig;
  525. function handleOpenExportsModal() {
  526. exportsDialogVisible.value = true;
  527. }
  528. const detailFormData = ref<RoleTable>({} as RoleTable);
  529. const formData = reactive<RoleForm>({
  530. id: undefined,
  531. name: undefined,
  532. order: 1,
  533. code: "",
  534. status: "0",
  535. description: undefined,
  536. });
  537. const dialogVisible = reactive({
  538. title: "",
  539. visible: false,
  540. type: "create" as "create" | "update" | "detail",
  541. });
  542. const CODE_PATTERN = /^[A-Za-z][A-Za-z0-9_]{1,15}$/;
  543. const rules = reactive({
  544. name: [{ required: true, message: "请输入角色名称", trigger: "blur" }],
  545. code: [
  546. { required: true, message: "请输入角色编码", trigger: "blur" },
  547. {
  548. pattern: CODE_PATTERN,
  549. message: "字母开头,2-16位字母/数字/下划线",
  550. trigger: "blur",
  551. },
  552. ],
  553. order: [{ required: true, message: "请输入角色排序", trigger: "blur" }],
  554. status: [{ required: true, message: "请选择状态", trigger: "blur" }],
  555. });
  556. const initialFormData: RoleForm = {
  557. id: undefined,
  558. name: undefined,
  559. order: 1,
  560. code: "",
  561. status: "0",
  562. description: undefined,
  563. };
  564. async function resetForm() {
  565. if (dataFormRef.value) {
  566. dataFormRef.value.resetFields();
  567. dataFormRef.value.clearValidate();
  568. }
  569. Object.assign(formData, initialFormData);
  570. }
  571. async function handleCloseDialog() {
  572. dialogVisible.visible = false;
  573. await resetForm();
  574. }
  575. async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
  576. dialogVisible.type = type;
  577. if (id) {
  578. const response = await RoleAPI.detailRole(id);
  579. if (type === "detail") {
  580. dialogVisible.title = "角色详情";
  581. Object.assign(detailFormData.value, response.data.data);
  582. } else if (type === "update") {
  583. dialogVisible.title = "修改角色";
  584. Object.assign(formData, response.data.data);
  585. }
  586. } else {
  587. dialogVisible.title = "新增角色";
  588. formData.id = undefined;
  589. }
  590. dialogVisible.visible = true;
  591. }
  592. async function handleSubmit() {
  593. dataFormRef.value.validate(async (valid: boolean) => {
  594. if (valid) {
  595. submitLoading.value = true;
  596. const id = formData.id;
  597. try {
  598. if (id) {
  599. await RoleAPI.updateRole(id, { id, ...formData });
  600. } else {
  601. await RoleAPI.createRole(formData);
  602. }
  603. dialogVisible.visible = false;
  604. await resetForm();
  605. refreshList();
  606. const userStore = useUserStore();
  607. await userStore.getUserInfo();
  608. } catch (error: unknown) {
  609. console.error(error);
  610. } finally {
  611. submitLoading.value = false;
  612. }
  613. }
  614. });
  615. }
  616. async function handleMoreClick(status: string) {
  617. const rows = contentRef.value?.getSelectionData() as RoleTable[] | undefined;
  618. const ids = (rows ?? []).map((r) => r.id).filter((id): id is number => id != null);
  619. if (!ids.length) {
  620. ElMessage.warning("请先选择要操作的数据");
  621. return;
  622. }
  623. ElMessageBox.confirm(`确认${status === "0" ? "启用" : "停用"}该项数据?`, "警告", {
  624. confirmButtonText: "确定",
  625. cancelButtonText: "取消",
  626. type: "warning",
  627. })
  628. .then(async () => {
  629. try {
  630. await RoleAPI.batchRole({ ids, status });
  631. refreshList();
  632. const userStore = useUserStore();
  633. await userStore.getUserInfo();
  634. } catch (error: unknown) {
  635. console.error(error);
  636. }
  637. })
  638. .catch(() => {
  639. ElMessageBox.close();
  640. });
  641. }
  642. function handleOpenAssignPermDialog(roleId: number, roleName: string) {
  643. checkedRole.value = { id: roleId, name: roleName };
  644. drawerVisible.value = true;
  645. }
  646. </script>