Permissions.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. <template>
  2. <div class="permissions-management">
  3. <div class="page-header">
  4. <h2>权限管理</h2>
  5. <p>管理系统权限和资源访问控制</p>
  6. </div>
  7. <!-- 操作栏 -->
  8. <div class="toolbar">
  9. <div class="toolbar-left">
  10. <el-button type="primary" @click="showCreateDialog = true">
  11. <el-icon><Plus /></el-icon>
  12. 创建权限
  13. </el-button>
  14. <el-button @click="refreshPermissions">
  15. <el-icon><Refresh /></el-icon>
  16. 刷新
  17. </el-button>
  18. </div>
  19. <div class="toolbar-right">
  20. <el-select
  21. v-model="selectedResource"
  22. placeholder="选择资源类型"
  23. style="width: 150px; margin-right: 12px;"
  24. @change="handleResourceFilter"
  25. clearable
  26. >
  27. <el-option label="全部资源" value="" />
  28. <el-option label="用户管理" value="user" />
  29. <el-option label="角色管理" value="role" />
  30. <el-option label="菜单管理" value="menu" />
  31. <el-option label="应用管理" value="app" />
  32. <el-option label="系统管理" value="system" />
  33. </el-select>
  34. <el-input
  35. v-model="searchKeyword"
  36. placeholder="搜索权限名称..."
  37. style="width: 300px"
  38. @input="handleSearch"
  39. >
  40. <template #prefix>
  41. <el-icon><Search /></el-icon>
  42. </template>
  43. </el-input>
  44. </div>
  45. </div>
  46. <!-- 权限列表 -->
  47. <el-table
  48. v-loading="loading"
  49. :data="permissions"
  50. style="width: 100%"
  51. @selection-change="handleSelectionChange"
  52. >
  53. <el-table-column type="selection" width="55" />
  54. <el-table-column prop="display_name" label="权限名称" min-width="150">
  55. <template #default="{ row }">
  56. <div class="permission-info">
  57. <span class="permission-name">{{ row.display_name }}</span>
  58. <el-tag size="small" style="margin-left: 8px;">{{ row.name }}</el-tag>
  59. </div>
  60. </template>
  61. </el-table-column>
  62. <el-table-column prop="resource" label="资源" width="120">
  63. <template #default="{ row }">
  64. <el-tag :type="getResourceTagType(row.resource)" size="small">
  65. {{ getResourceLabel(row.resource) }}
  66. </el-tag>
  67. </template>
  68. </el-table-column>
  69. <el-table-column prop="action" label="操作" width="100">
  70. <template #default="{ row }">
  71. <el-tag :type="getActionTagType(row.action)" size="small">
  72. {{ getActionLabel(row.action) }}
  73. </el-tag>
  74. </template>
  75. </el-table-column>
  76. <el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
  77. <el-table-column prop="is_active" label="状态" width="100" align="center">
  78. <template #default="{ row }">
  79. <el-switch
  80. v-model="row.is_active"
  81. @change="handleStatusChange(row)"
  82. />
  83. </template>
  84. </el-table-column>
  85. <el-table-column prop="created_at" label="创建时间" width="180">
  86. <template #default="{ row }">
  87. {{ formatDateTime(row.created_at) }}
  88. </template>
  89. </el-table-column>
  90. <el-table-column label="操作" width="200" fixed="right">
  91. <template #default="{ row }">
  92. <el-button type="primary" size="small" @click="editPermission(row)">
  93. 编辑
  94. </el-button>
  95. <el-button type="info" size="small" @click="viewRoles(row)">
  96. 查看角色
  97. </el-button>
  98. <el-button type="danger" size="small" @click="deletePermission(row)">
  99. 删除
  100. </el-button>
  101. </template>
  102. </el-table-column>
  103. </el-table>
  104. <!-- 分页 -->
  105. <div class="pagination">
  106. <el-pagination
  107. v-model:current-page="currentPage"
  108. v-model:page-size="pageSize"
  109. :page-sizes="[10, 20, 50, 100]"
  110. :total="total"
  111. layout="total, sizes, prev, pager, next, jumper"
  112. @size-change="handleSizeChange"
  113. @current-change="handleCurrentChange"
  114. />
  115. </div>
  116. <!-- 创建/编辑权限对话框 -->
  117. <el-dialog
  118. v-model="showCreateDialog"
  119. :title="editingPermission ? '编辑权限' : '创建权限'"
  120. width="600px"
  121. @close="resetForm"
  122. >
  123. <el-form
  124. ref="permissionFormRef"
  125. :model="permissionForm"
  126. :rules="permissionRules"
  127. label-width="100px"
  128. >
  129. <el-form-item label="权限名称" prop="display_name">
  130. <el-input v-model="permissionForm.display_name" placeholder="请输入权限显示名称" />
  131. </el-form-item>
  132. <el-form-item label="权限标识" prop="name">
  133. <el-input
  134. v-model="permissionForm.name"
  135. placeholder="请输入权限标识(如:user.create)"
  136. :disabled="!!editingPermission"
  137. />
  138. </el-form-item>
  139. <el-row :gutter="20">
  140. <el-col :span="12">
  141. <el-form-item label="资源类型" prop="resource">
  142. <el-select v-model="permissionForm.resource" placeholder="请选择资源类型">
  143. <el-option label="用户管理" value="user" />
  144. <el-option label="角色管理" value="role" />
  145. <el-option label="菜单管理" value="menu" />
  146. <el-option label="应用管理" value="app" />
  147. <el-option label="系统管理" value="system" />
  148. </el-select>
  149. </el-form-item>
  150. </el-col>
  151. <el-col :span="12">
  152. <el-form-item label="操作类型" prop="action">
  153. <el-select v-model="permissionForm.action" placeholder="请选择操作类型">
  154. <el-option label="查看" value="view" />
  155. <el-option label="创建" value="create" />
  156. <el-option label="编辑" value="edit" />
  157. <el-option label="删除" value="delete" />
  158. <el-option label="分配" value="assign" />
  159. <el-option label="配置" value="config" />
  160. <el-option label="审计" value="audit" />
  161. </el-select>
  162. </el-form-item>
  163. </el-col>
  164. </el-row>
  165. <el-form-item label="权限描述" prop="description">
  166. <el-input
  167. v-model="permissionForm.description"
  168. type="textarea"
  169. :rows="3"
  170. placeholder="请输入权限描述"
  171. />
  172. </el-form-item>
  173. </el-form>
  174. <template #footer>
  175. <el-button @click="showCreateDialog = false">取消</el-button>
  176. <el-button type="primary" @click="savePermission" :loading="saving">
  177. {{ editingPermission ? '更新' : '创建' }}
  178. </el-button>
  179. </template>
  180. </el-dialog>
  181. <!-- 查看权限角色对话框 -->
  182. <el-dialog
  183. v-model="showRolesDialog"
  184. title="权限关联角色"
  185. width="500px"
  186. >
  187. <div v-if="currentPermission">
  188. <h4>权限 "{{ currentPermission.display_name }}" 关联的角色</h4>
  189. <el-empty v-if="permissionRoles.length === 0" description="暂无关联角色" />
  190. <div v-else class="roles-list">
  191. <el-tag
  192. v-for="role in permissionRoles"
  193. :key="role.id"
  194. size="large"
  195. style="margin: 4px;"
  196. >
  197. {{ role.display_name }}
  198. <el-tag v-if="role.is_system" type="info" size="small" style="margin-left: 4px;">
  199. 系统角色
  200. </el-tag>
  201. </el-tag>
  202. </div>
  203. </div>
  204. <template #footer>
  205. <el-button @click="showRolesDialog = false">关闭</el-button>
  206. </template>
  207. </el-dialog>
  208. </div>
  209. </template>
  210. <script setup lang="ts">
  211. import { ref, reactive, onMounted } from 'vue'
  212. import { ElMessage, ElMessageBox } from 'element-plus'
  213. import { Plus, Search, Refresh } from '@element-plus/icons-vue'
  214. import request from '@/api/request'
  215. // 响应式数据
  216. const loading = ref(false)
  217. const saving = ref(false)
  218. const permissions = ref<any[]>([])
  219. const selectedPermissions = ref<any[]>([])
  220. const searchKeyword = ref('')
  221. const selectedResource = ref('')
  222. const currentPage = ref(1)
  223. const pageSize = ref(20)
  224. const total = ref(0)
  225. // 对话框状态
  226. const showCreateDialog = ref(false)
  227. const showRolesDialog = ref(false)
  228. const editingPermission = ref<any>(null)
  229. const currentPermission = ref<any>(null)
  230. const permissionRoles = ref<any[]>([])
  231. // 表单数据
  232. const permissionForm = reactive({
  233. display_name: '',
  234. name: '',
  235. resource: '',
  236. action: '',
  237. description: ''
  238. })
  239. // 表单验证规则
  240. const permissionRules = {
  241. display_name: [
  242. { required: true, message: '请输入权限名称', trigger: 'blur' }
  243. ],
  244. name: [
  245. { required: true, message: '请输入权限标识', trigger: 'blur' },
  246. { pattern: /^[a-zA-Z_][a-zA-Z0-9_.]*$/, message: '权限标识只能包含字母、数字、下划线和点号,且以字母或下划线开头', trigger: 'blur' }
  247. ],
  248. resource: [
  249. { required: true, message: '请选择资源类型', trigger: 'change' }
  250. ],
  251. action: [
  252. { required: true, message: '请选择操作类型', trigger: 'change' }
  253. ]
  254. }
  255. // 引用
  256. const permissionFormRef = ref()
  257. // 获取资源标签类型
  258. const getResourceTagType = (resource: string) => {
  259. const types: Record<string, string> = {
  260. user: 'primary',
  261. role: 'success',
  262. menu: 'warning',
  263. app: 'info',
  264. system: 'danger'
  265. }
  266. return types[resource] || 'default'
  267. }
  268. // 获取资源标签文本
  269. const getResourceLabel = (resource: string) => {
  270. const labels: Record<string, string> = {
  271. user: '用户',
  272. role: '角色',
  273. menu: '菜单',
  274. app: '应用',
  275. system: '系统'
  276. }
  277. return labels[resource] || resource
  278. }
  279. // 获取操作标签类型
  280. const getActionTagType = (action: string) => {
  281. const types: Record<string, string> = {
  282. view: 'info',
  283. create: 'success',
  284. edit: 'warning',
  285. delete: 'danger',
  286. assign: 'primary',
  287. config: 'warning',
  288. audit: 'info'
  289. }
  290. return types[action] || 'default'
  291. }
  292. // 获取操作标签文本
  293. const getActionLabel = (action: string) => {
  294. const labels: Record<string, string> = {
  295. view: '查看',
  296. create: '创建',
  297. edit: '编辑',
  298. delete: '删除',
  299. assign: '分配',
  300. config: '配置',
  301. audit: '审计'
  302. }
  303. return labels[action] || action
  304. }
  305. // 加载权限列表
  306. const loadPermissions = async () => {
  307. loading.value = true
  308. try {
  309. // TODO: 实现权限列表API
  310. // 模拟数据
  311. const mockData = {
  312. code: 0,
  313. data: {
  314. items: [
  315. {
  316. id: '1',
  317. name: 'user.view',
  318. display_name: '查看用户',
  319. resource: 'user',
  320. action: 'view',
  321. description: '查看用户列表和详情',
  322. is_active: true,
  323. created_at: new Date().toISOString()
  324. },
  325. {
  326. id: '2',
  327. name: 'user.create',
  328. display_name: '创建用户',
  329. resource: 'user',
  330. action: 'create',
  331. description: '创建新用户',
  332. is_active: true,
  333. created_at: new Date().toISOString()
  334. },
  335. {
  336. id: '3',
  337. name: 'role.view',
  338. display_name: '查看角色',
  339. resource: 'role',
  340. action: 'view',
  341. description: '查看角色列表和详情',
  342. is_active: true,
  343. created_at: new Date().toISOString()
  344. }
  345. ],
  346. total: 3,
  347. page: currentPage.value,
  348. page_size: pageSize.value
  349. }
  350. }
  351. permissions.value = mockData.data.items
  352. total.value = mockData.data.total
  353. } catch (error) {
  354. console.error('加载权限列表失败:', error)
  355. ElMessage.error('加载权限列表失败')
  356. } finally {
  357. loading.value = false
  358. }
  359. }
  360. // 刷新权限
  361. const refreshPermissions = () => {
  362. loadPermissions()
  363. }
  364. // 搜索处理
  365. const handleSearch = () => {
  366. currentPage.value = 1
  367. loadPermissions()
  368. }
  369. // 资源过滤
  370. const handleResourceFilter = () => {
  371. currentPage.value = 1
  372. loadPermissions()
  373. }
  374. // 分页处理
  375. const handleSizeChange = (size: number) => {
  376. pageSize.value = size
  377. currentPage.value = 1
  378. loadPermissions()
  379. }
  380. const handleCurrentChange = (page: number) => {
  381. currentPage.value = page
  382. loadPermissions()
  383. }
  384. // 选择处理
  385. const handleSelectionChange = (selection: any[]) => {
  386. selectedPermissions.value = selection
  387. }
  388. // 状态切换
  389. const handleStatusChange = async (permission: any) => {
  390. try {
  391. // TODO: 实现权限状态切换API
  392. ElMessage.success('权限状态更新成功')
  393. } catch (error) {
  394. console.error('更新权限状态失败:', error)
  395. ElMessage.error('更新权限状态失败')
  396. // 恢复原状态
  397. permission.is_active = !permission.is_active
  398. }
  399. }
  400. // 编辑权限
  401. const editPermission = (permission: any) => {
  402. editingPermission.value = permission
  403. Object.assign(permissionForm, {
  404. display_name: permission.display_name,
  405. name: permission.name,
  406. resource: permission.resource,
  407. action: permission.action,
  408. description: permission.description
  409. })
  410. showCreateDialog.value = true
  411. }
  412. // 查看权限关联角色
  413. const viewRoles = async (permission: any) => {
  414. currentPermission.value = permission
  415. try {
  416. // TODO: 实现获取权限关联角色API
  417. // 模拟数据
  418. permissionRoles.value = [
  419. { id: '1', display_name: '超级管理员', is_system: true },
  420. { id: '2', display_name: '管理员', is_system: true }
  421. ]
  422. showRolesDialog.value = true
  423. } catch (error) {
  424. console.error('获取权限角色失败:', error)
  425. ElMessage.error('获取权限角色失败')
  426. }
  427. }
  428. // 删除权限
  429. const deletePermission = async (permission: any) => {
  430. try {
  431. await ElMessageBox.confirm(
  432. `确定要删除权限 "${permission.display_name}" 吗?此操作不可恢复。`,
  433. '确认删除',
  434. {
  435. confirmButtonText: '确定',
  436. cancelButtonText: '取消',
  437. type: 'warning'
  438. }
  439. )
  440. // TODO: 实现删除权限API
  441. ElMessage.success('权限删除成功')
  442. loadPermissions()
  443. } catch (error) {
  444. if (error !== 'cancel') {
  445. ElMessage.error('删除权限失败')
  446. }
  447. }
  448. }
  449. // 保存权限
  450. const savePermission = async () => {
  451. try {
  452. await permissionFormRef.value.validate()
  453. saving.value = true
  454. if (editingPermission.value) {
  455. // TODO: 实现更新权限API
  456. ElMessage.success('权限更新成功')
  457. } else {
  458. // TODO: 实现创建权限API
  459. ElMessage.success('权限创建成功')
  460. }
  461. showCreateDialog.value = false
  462. loadPermissions()
  463. } catch (error) {
  464. console.error('保存权限失败:', error)
  465. ElMessage.error('保存权限失败')
  466. } finally {
  467. saving.value = false
  468. }
  469. }
  470. // 重置表单
  471. const resetForm = () => {
  472. editingPermission.value = null
  473. Object.assign(permissionForm, {
  474. display_name: '',
  475. name: '',
  476. resource: '',
  477. action: '',
  478. description: ''
  479. })
  480. permissionFormRef.value?.clearValidate()
  481. }
  482. // 格式化日期时间
  483. const formatDateTime = (dateTime: string) => {
  484. if (!dateTime) return '-'
  485. return new Date(dateTime).toLocaleString('zh-CN')
  486. }
  487. // 组件挂载
  488. onMounted(() => {
  489. loadPermissions()
  490. })
  491. </script>
  492. <style scoped>
  493. .permissions-management {
  494. padding: 20px;
  495. }
  496. .page-header {
  497. margin-bottom: 20px;
  498. }
  499. .page-header h2 {
  500. margin: 0 0 8px 0;
  501. color: #333;
  502. }
  503. .page-header p {
  504. margin: 0;
  505. color: #666;
  506. font-size: 14px;
  507. }
  508. .toolbar {
  509. display: flex;
  510. justify-content: space-between;
  511. align-items: center;
  512. margin-bottom: 20px;
  513. padding: 16px;
  514. background: #fff;
  515. border-radius: 8px;
  516. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  517. }
  518. .toolbar-left {
  519. display: flex;
  520. gap: 12px;
  521. }
  522. .toolbar-right {
  523. display: flex;
  524. gap: 12px;
  525. align-items: center;
  526. }
  527. .permission-info {
  528. display: flex;
  529. align-items: center;
  530. }
  531. .permission-name {
  532. font-weight: 500;
  533. }
  534. .roles-list {
  535. margin-top: 16px;
  536. }
  537. .pagination {
  538. margin-top: 20px;
  539. display: flex;
  540. justify-content: center;
  541. }
  542. :deep(.el-table) {
  543. background: #fff;
  544. border-radius: 8px;
  545. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  546. }
  547. :deep(.el-dialog__body) {
  548. padding: 20px;
  549. }
  550. </style>