Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 
 

291 Zeilen
10 KiB

  1. {% extends "../layout.html" %}
  2. {% block title %}角色权限{% endblock %}
  3. {% block content %}
  4. <div id="roleApp">
  5. <!-- 搜索栏 -->
  6. <el-card shadow="never" class="mb-4">
  7. <el-form :inline="true" @submit.prevent="loadList()" class="flex items-center flex-wrap gap-2">
  8. <el-form-item label="关键字" class="!mb-0">
  9. <el-input v-model="keyword" placeholder="角色名称" clearable style="width:180px;" />
  10. </el-form-item>
  11. <el-form-item class="!mb-0">
  12. <el-button type="primary" @click="loadList()">搜索</el-button>
  13. <el-button @click="resetFilter">重置</el-button>
  14. </el-form-item>
  15. </el-form>
  16. </el-card>
  17. <!-- 角色列表 -->
  18. <el-card shadow="never">
  19. <template #header>
  20. <el-button type="primary" @click="showAddModal">+ 新增</el-button>
  21. </template>
  22. <el-table :data="tableData" v-loading="loading" stripe border>
  23. <el-table-column prop="name" label="角色名称">
  24. <template #default="{ row }">
  25. <span class="text-orange-500 font-medium">${ row.name }</span>
  26. </template>
  27. </el-table-column>
  28. <el-table-column prop="is_default" label="默认" width="80" align="center">
  29. <template #default="{ row }">
  30. <el-tag v-if="row.is_default" type="success" size="small">是</el-tag>
  31. </template>
  32. </el-table-column>
  33. <el-table-column prop="code" label="角色编码">
  34. <template #default="{ row }">${ row.code || '-' }</template>
  35. </el-table-column>
  36. <el-table-column prop="description" label="描述">
  37. <template #default="{ row }">${ row.description || '-' }</template>
  38. </el-table-column>
  39. <el-table-column label="操作" width="200" align="center">
  40. <template #default="{ row }">
  41. <el-button type="primary" link @click="openPermDrawer(row)">分配权限</el-button>
  42. <el-button v-if="!row.is_default" type="primary" link @click="editRole(row)">编辑</el-button>
  43. <el-button v-if="!row.is_default" type="danger" link @click="deleteRole(row)">删除</el-button>
  44. </template>
  45. </el-table-column>
  46. </el-table>
  47. <div class="flex justify-end mt-4">
  48. <el-pagination
  49. v-model:current-page="pagination.page"
  50. :page-size="pagination.pageSize"
  51. :total="pagination.total"
  52. layout="total, prev, pager, next"
  53. @current-change="loadList"
  54. />
  55. </div>
  56. </el-card>
  57. <!-- 新增/编辑弹窗 -->
  58. <el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" destroy-on-close>
  59. <el-form :model="form" label-width="80px">
  60. <el-form-item label="角色名称" required>
  61. <el-input v-model="form.name" placeholder="请输入角色名称" />
  62. </el-form-item>
  63. <el-form-item label="角色编码">
  64. <el-input v-model="form.code" placeholder="请输入角色编码(如 ADMIN)" />
  65. </el-form-item>
  66. <el-form-item label="描述">
  67. <el-input v-model="form.description" type="textarea" :rows="3" placeholder="请输入角色描述" />
  68. </el-form-item>
  69. <el-form-item label="排序">
  70. <el-input-number v-model="form.sort" :min="0" />
  71. </el-form-item>
  72. <el-form-item label="默认角色">
  73. <el-switch v-model="form.is_default" :active-value="1" :inactive-value="0" />
  74. </el-form-item>
  75. </el-form>
  76. <template #footer>
  77. <el-button @click="dialogVisible = false">取消</el-button>
  78. <el-button type="primary" @click="saveRole" :loading="saving">确定</el-button>
  79. </template>
  80. </el-dialog>
  81. <!-- 权限分配抽屉 -->
  82. <el-drawer v-model="permDrawerVisible" :title="permTitle" size="420px" destroy-on-close>
  83. <template #header>
  84. <span class="font-semibold">${ permTitle }</span>
  85. </template>
  86. <div class="mb-3">
  87. <el-checkbox v-model="expandAll" @change="handleExpandAll">全部展开</el-checkbox>
  88. </div>
  89. <el-tree
  90. ref="permTreeRef"
  91. :data="permissionTree"
  92. show-checkbox
  93. node-key="key"
  94. :default-expand-all="expandAll"
  95. :props="{ label: 'name', children: 'children' }"
  96. />
  97. <template #footer>
  98. <el-button @click="permDrawerVisible = false">取消</el-button>
  99. <el-button type="primary" @click="savePermissions" :loading="permSaving">确定</el-button>
  100. </template>
  101. </el-drawer>
  102. </div>
  103. {% endblock %}
  104. {% block js %}
  105. <script>
  106. const permissionTreeData = {{ permissionTree | dump | safe }};
  107. const { createApp, ref, reactive, onMounted, nextTick } = Vue;
  108. const app = createApp({
  109. delimiters: ['${', '}'],
  110. setup() {
  111. const keyword = ref('');
  112. const loading = ref(false);
  113. const tableData = ref([]);
  114. const pagination = reactive({ page: 1, pageSize: 20, total: 0 });
  115. // 弹窗
  116. const dialogVisible = ref(false);
  117. const dialogTitle = ref('新增角色');
  118. const saving = ref(false);
  119. const form = reactive({ id: null, name: '', code: '', description: '', sort: 0, is_default: 0 });
  120. // 权限抽屉
  121. const permDrawerVisible = ref(false);
  122. const permTitle = ref('权限分配');
  123. const permTreeRef = ref(null);
  124. const expandAll = ref(false);
  125. const permSaving = ref(false);
  126. const currentPermRoleId = ref(null);
  127. const permissionTree = ref(permissionTreeData || []);
  128. // 加载列表
  129. async function loadList(page) {
  130. if (typeof page === 'number') pagination.page = page;
  131. loading.value = true;
  132. try {
  133. const params = new URLSearchParams({ page: pagination.page, pageSize: pagination.pageSize, keyword: keyword.value });
  134. const res = await fetch('/admin/system/role/list?' + params).then(r => r.json());
  135. if (res.code === 0) {
  136. tableData.value = res.data.data || [];
  137. pagination.total = res.data.count || 0;
  138. } else {
  139. ElementPlus.ElMessage.error(res.msg || '加载失败');
  140. }
  141. } finally {
  142. loading.value = false;
  143. }
  144. }
  145. // 重置筛选
  146. function resetFilter() {
  147. keyword.value = '';
  148. pagination.page = 1;
  149. loadList();
  150. }
  151. // 显示新增弹窗
  152. function showAddModal() {
  153. dialogTitle.value = '新增角色';
  154. Object.assign(form, { id: null, name: '', code: '', description: '', sort: 0, is_default: 0 });
  155. dialogVisible.value = true;
  156. }
  157. // 编辑角色
  158. function editRole(row) {
  159. dialogTitle.value = '编辑角色';
  160. Object.assign(form, { id: row.id, name: row.name, code: row.code || '', description: row.description || '', sort: row.sort || 0, is_default: row.is_default || 0 });
  161. dialogVisible.value = true;
  162. }
  163. // 保存角色
  164. async function saveRole() {
  165. if (!form.name.trim()) {
  166. ElementPlus.ElMessage.warning('角色名称不能为空');
  167. return;
  168. }
  169. saving.value = true;
  170. try {
  171. const url = form.id ? '/admin/system/role/edit' : '/admin/system/role/add';
  172. const body = { name: form.name, code: form.code, description: form.description, sort: form.sort, is_default: form.is_default };
  173. if (form.id) body.id = form.id;
  174. const res = await fetch(url, {
  175. method: 'POST',
  176. headers: { 'Content-Type': 'application/json' },
  177. body: JSON.stringify(body)
  178. }).then(r => r.json());
  179. if (res.code === 0) {
  180. ElementPlus.ElMessage.success('保存成功');
  181. dialogVisible.value = false;
  182. loadList();
  183. } else {
  184. ElementPlus.ElMessage.error(res.msg || '保存失败');
  185. }
  186. } finally {
  187. saving.value = false;
  188. }
  189. }
  190. // 删除角色
  191. async function deleteRole(row) {
  192. try {
  193. await ElementPlus.ElMessageBox.confirm('确定要删除该角色吗?', '提示', { type: 'warning' });
  194. const res = await fetch('/admin/system/role/delete', {
  195. method: 'POST',
  196. headers: { 'Content-Type': 'application/json' },
  197. body: JSON.stringify({ id: row.id })
  198. }).then(r => r.json());
  199. if (res.code === 0) {
  200. ElementPlus.ElMessage.success('删除成功');
  201. loadList();
  202. } else {
  203. ElementPlus.ElMessage.error(res.msg || '删除失败');
  204. }
  205. } catch {}
  206. }
  207. // 打开权限抽屉
  208. async function openPermDrawer(row) {
  209. currentPermRoleId.value = row.id;
  210. permTitle.value = `【${row.name}】权限分配`;
  211. permDrawerVisible.value = true;
  212. await nextTick();
  213. const res = await fetch('/admin/system/role/detail?id=' + row.id).then(r => r.json());
  214. const currentPerms = res.code === 0 ? (res.data.permissions || []) : [];
  215. if (permTreeRef.value) {
  216. permTreeRef.value.setCheckedKeys(currentPerms);
  217. }
  218. }
  219. // 全部展开/折叠
  220. function handleExpandAll(val) {
  221. if (permTreeRef.value) {
  222. const nodes = permTreeRef.value.store.nodesMap;
  223. for (const key in nodes) {
  224. nodes[key].expanded = val;
  225. }
  226. }
  227. }
  228. // 保存权限
  229. async function savePermissions() {
  230. permSaving.value = true;
  231. try {
  232. const permissions = permTreeRef.value ? permTreeRef.value.getCheckedKeys(true) : [];
  233. const res = await fetch('/admin/system/role/assignPermissions', {
  234. method: 'POST',
  235. headers: { 'Content-Type': 'application/json' },
  236. body: JSON.stringify({ id: currentPermRoleId.value, permissions })
  237. }).then(r => r.json());
  238. if (res.code === 0) {
  239. ElementPlus.ElMessage.success('权限保存成功');
  240. permDrawerVisible.value = false;
  241. } else {
  242. ElementPlus.ElMessage.error(res.msg || '保存失败');
  243. }
  244. } finally {
  245. permSaving.value = false;
  246. }
  247. }
  248. onMounted(() => loadList());
  249. return {
  250. keyword, loading, tableData, pagination,
  251. dialogVisible, dialogTitle, saving, form,
  252. permDrawerVisible, permTitle, permTreeRef, expandAll, permSaving, permissionTree,
  253. loadList, resetFilter, showAddModal, editRole, saveRole, deleteRole,
  254. openPermDrawer, handleExpandAll, savePermissions
  255. };
  256. }
  257. });
  258. app.use(ElementPlus, { locale: ElementPlusLocaleZhCn });
  259. app.mount('#roleApp');
  260. </script>
  261. {% endblock %}