diff --git a/gofaster/app/dist/renderer/js/index.js b/gofaster/app/dist/renderer/js/index.js index 9c2501f..8a8c658 100644 --- a/gofaster/app/dist/renderer/js/index.js +++ b/gofaster/app/dist/renderer/js/index.js @@ -1603,6 +1603,333 @@ input[type="checkbox"][data-v-de6ba976] { /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___); +/***/ }), + +/***/ "./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/modules/role-management/components/RolePermissionAssignment.vue?vue&type=style&index=0&id=91c1b50a&scoped=true&lang=css": +/*!*********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\ + !*** ./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/modules/role-management/components/RolePermissionAssignment.vue?vue&type=style&index=0&id=91c1b50a&scoped=true&lang=css ***! + \*********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/ +/***/ ((module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../../../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ "./node_modules/css-loader/dist/runtime/noSourceMaps.js"); +/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../../../../node_modules/css-loader/dist/runtime/api.js */ "./node_modules/css-loader/dist/runtime/api.js"); +/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__); +// Imports + + +var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default())); +// Module +___CSS_LOADER_EXPORT___.push([module.id, ` +.role-permission-assignment[data-v-91c1b50a] { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} +.large-modal[data-v-91c1b50a] { + width: 90%; + max-width: 1200px; + height: 80vh; + max-height: 800px; +} +.modal-overlay[data-v-91c1b50a] { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} +.modal[data-v-91c1b50a] { + background: white; + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + display: flex; + flex-direction: column; + max-height: 90vh; +} +.modal-header[data-v-91c1b50a] { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 24px; + border-bottom: 1px solid #e5e7eb; + background: #f9fafb; + border-radius: 8px 8px 0 0; +} +.modal-header h3[data-v-91c1b50a] { + margin: 0; + color: #1f2937; + font-size: 18px; + font-weight: 600; +} +.close-btn[data-v-91c1b50a] { + background: none; + border: none; + font-size: 18px; + cursor: pointer; + color: #6b7280; + padding: 4px; + border-radius: 4px; + transition: all 0.2s; +} +.close-btn[data-v-91c1b50a]:hover { + background: #e5e7eb; + color: #374151; +} +.modal-body[data-v-91c1b50a] { + flex: 1; + padding: 24px; + overflow-y: auto; +} +.modal-footer[data-v-91c1b50a] { + display: flex; + justify-content: flex-end; + gap: 12px; + padding: 20px 24px; + border-top: 1px solid #e5e7eb; + background: #f9fafb; + border-radius: 0 0 8px 8px; +} +.loading[data-v-91c1b50a] { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 40px; + color: #6b7280; +} +.loading i[data-v-91c1b50a] { + font-size: 24px; + margin-bottom: 12px; +} +.role-info[data-v-91c1b50a] { + margin-bottom: 24px; + padding: 16px; + background: #f8fafc; + border-radius: 8px; + border: 1px solid #e2e8f0; +} +.role-details h4[data-v-91c1b50a] { + margin: 0 0 8px 0; + color: #1e293b; + font-size: 16px; + font-weight: 600; +} +.role-description[data-v-91c1b50a] { + margin: 0 0 12px 0; + color: #64748b; + font-size: 14px; +} +.role-stats[data-v-91c1b50a] { + display: flex; + gap: 16px; +} +.stat-item[data-v-91c1b50a] { + display: flex; + align-items: center; + gap: 6px; + color: #475569; + font-size: 14px; +} +.stat-item i[data-v-91c1b50a] { + color: #3b82f6; +} +.permission-assignment[data-v-91c1b50a] { + border: 1px solid #e2e8f0; + border-radius: 8px; + overflow: hidden; +} +.assignment-tabs[data-v-91c1b50a] { + display: flex; + background: #f1f5f9; + border-bottom: 1px solid #e2e8f0; +} +.tab-btn[data-v-91c1b50a] { + flex: 1; + padding: 12px 16px; + background: none; + border: none; + cursor: pointer; + font-size: 14px; + font-weight: 500; + color: #64748b; + transition: all 0.2s; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} +.tab-btn.active[data-v-91c1b50a] { + background: white; + color: #3b82f6; + border-bottom: 2px solid #3b82f6; +} +.tab-btn[data-v-91c1b50a]:hover:not(.active) { + background: #e2e8f0; + color: #475569; +} +.permission-list[data-v-91c1b50a] { + padding: 20px; +} +.list-header[data-v-91c1b50a] { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +} +.list-header h5[data-v-91c1b50a] { + margin: 0; + color: #1e293b; + font-size: 16px; + font-weight: 600; +} +.list-actions[data-v-91c1b50a] { + display: flex; + gap: 8px; +} +.empty-state[data-v-91c1b50a] { + text-align: center; + padding: 40px 20px; + color: #64748b; +} +.empty-state i[data-v-91c1b50a] { + font-size: 48px; + margin-bottom: 16px; + opacity: 0.5; +} +.empty-state p[data-v-91c1b50a] { + margin: 0; + font-size: 14px; +} +.permission-items[data-v-91c1b50a] { + display: flex; + flex-direction: column; + gap: 8px; +} +.permission-item[data-v-91c1b50a] { + display: flex; + align-items: center; + padding: 12px; + border: 1px solid #e2e8f0; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s; + background: white; +} +.permission-item[data-v-91c1b50a]:hover { + border-color: #3b82f6; + box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1); +} +.permission-item.selected[data-v-91c1b50a] { + border-color: #3b82f6; + background: #eff6ff; +} +.permission-checkbox[data-v-91c1b50a] { + margin-right: 12px; +} +.permission-checkbox input[type="checkbox"][data-v-91c1b50a] { + width: 16px; + height: 16px; + cursor: pointer; +} +.permission-info[data-v-91c1b50a] { + flex: 1; +} +.permission-name[data-v-91c1b50a] { + font-weight: 500; + color: #1e293b; + margin-bottom: 4px; +} +.permission-details[data-v-91c1b50a] { + display: flex; + gap: 12px; + margin-bottom: 4px; +} +.permission-code[data-v-91c1b50a] { + font-family: 'Courier New', monospace; + font-size: 12px; + color: #64748b; + background: #f1f5f9; + padding: 2px 6px; + border-radius: 4px; +} +.permission-resource[data-v-91c1b50a] { + font-size: 12px; + color: #8b5cf6; + background: #f3f4f6; + padding: 2px 6px; + border-radius: 4px; +} +.permission-description[data-v-91c1b50a] { + font-size: 12px; + color: #64748b; + line-height: 1.4; +} +.permission-actions[data-v-91c1b50a] { + margin-left: 12px; +} +.btn[data-v-91c1b50a] { + padding: 6px 12px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + font-weight: 500; + transition: all 0.2s; + display: inline-flex; + align-items: center; + gap: 4px; +} +.btn-sm[data-v-91c1b50a] { + padding: 4px 8px; + font-size: 11px; +} +.btn-primary[data-v-91c1b50a] { + background: #3b82f6; + color: white; +} +.btn-primary[data-v-91c1b50a]:hover:not(:disabled) { + background: #2563eb; +} +.btn-primary[data-v-91c1b50a]:disabled { + background: #9ca3af; + cursor: not-allowed; +} +.btn-danger[data-v-91c1b50a] { + background: #ef4444; + color: white; +} +.btn-danger[data-v-91c1b50a]:hover:not(:disabled) { + background: #dc2626; +} +.btn-danger[data-v-91c1b50a]:disabled { + background: #9ca3af; + cursor: not-allowed; +} +.btn-secondary[data-v-91c1b50a] { + background: #6b7280; + color: white; +} +.btn-secondary[data-v-91c1b50a]:hover:not(:disabled) { + background: #4b5563; +} +.btn-secondary[data-v-91c1b50a]:disabled { + background: #9ca3af; + cursor: not-allowed; +} +`, ""]); +// Exports +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___); + + /***/ }), /***/ "./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/modules/role-management/components/UserRoleAssignment.vue?vue&type=style&index=0&id=7f7c6d92&scoped=true&lang=css": @@ -1953,6 +2280,9 @@ th[data-v-03643bfa] { .actions .btn-sm.btn-danger[data-v-03643bfa]:hover { color: #d32f2f; } +.actions .btn-sm.btn-warning[data-v-03643bfa]:hover { + color: #ff9800; +} .btn[data-v-03643bfa]:hover { opacity: 0.9; } @@ -4684,32 +5014,62 @@ __webpack_require__.r(__webpack_exports__); try { console.log('🧹 开始清理缓存...') - // 清理localStorage - const keysToClear = [ - 'gofaster-settings', - 'isLoggedIn', - 'token', - 'user', - 'app-settings', - 'theme-settings', - 'user-preferences' - ] - - keysToClear.forEach(key => { - if (localStorage.getItem(key)) { - localStorage.removeItem(key) - console.log('✅ 已清理localStorage键:', key) - } - }) + // 询问用户是否要清理用户设置 + const clearSettings = confirm('是否要清理用户设置?\n\n选择"确定"将清理所有设置(包括主题、记住密码等)\n选择"取消"将保留用户设置,只清理其他缓存') - // 清理所有以gofaster开头的键 - const allKeys = Object.keys(localStorage) - allKeys.forEach(key => { - if (key.startsWith('gofaster') || key.startsWith('electron')) { - localStorage.removeItem(key) - console.log('✅ 已清理localStorage键:', key) - } - }) + if (clearSettings) { + // 清理所有localStorage + const keysToClear = [ + 'gofaster-settings', + 'isLoggedIn', + 'token', + 'user', + 'app-settings', + 'theme-settings', + 'user-preferences' + ] + + keysToClear.forEach(key => { + if (localStorage.getItem(key)) { + localStorage.removeItem(key) + console.log('✅ 已清理localStorage键:', key) + } + }) + + // 清理所有以gofaster开头的键 + const allKeys = Object.keys(localStorage) + allKeys.forEach(key => { + if (key.startsWith('gofaster') || key.startsWith('electron')) { + localStorage.removeItem(key) + console.log('✅ 已清理localStorage键:', key) + } + }) + } else { + // 只清理非设置相关的缓存 + const keysToClear = [ + 'isLoggedIn', + 'token', + 'user', + 'app-settings', + 'theme-settings' + ] + + keysToClear.forEach(key => { + if (localStorage.getItem(key)) { + localStorage.removeItem(key) + console.log('✅ 已清理localStorage键:', key) + } + }) + + // 清理以electron开头的键,但保留gofaster-settings + const allKeys = Object.keys(localStorage) + allKeys.forEach(key => { + if (key.startsWith('electron')) { + localStorage.removeItem(key) + console.log('✅ 已清理localStorage键:', key) + } + }) + } // 通过IPC清理Electron缓存 if (window.electronAPI && window.electronAPI.clearElectronCache) { @@ -4832,39 +5192,8 @@ __webpack_require__.r(__webpack_exports__); } ;(0,vue__WEBPACK_IMPORTED_MODULE_0__.onMounted)(() => { - // 清理localStorage以修复缓存问题 - try { - console.log('🔧 开始清理localStorage...') - const keysToClear = [ - 'gofaster-settings', - 'isLoggedIn', - 'token', - 'user', - 'app-settings', - 'theme-settings', - 'user-preferences' - ] - - keysToClear.forEach(key => { - if (localStorage.getItem(key)) { - localStorage.removeItem(key) - console.log('✅ 已清理localStorage键:', key) - } - }) - - // 清理所有以gofaster开头的键 - const allKeys = Object.keys(localStorage) - allKeys.forEach(key => { - if (key.startsWith('gofaster') || key.startsWith('electron')) { - localStorage.removeItem(key) - console.log('✅ 已清理localStorage键:', key) - } - }) - - console.log('✅ localStorage清理完成') - } catch (error) { - console.error('❌ 清理localStorage失败:', error) - } + // 不再自动清理localStorage,保留用户设置 + console.log('🔧 应用启动,保留用户设置...') // 初始化收藏菜单 updateFavoriteMenu() @@ -5786,6 +6115,238 @@ __webpack_require__.r(__webpack_exports__); }); +/***/ }), + +/***/ "./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/modules/role-management/components/RolePermissionAssignment.vue?vue&type=script&lang=js": +/*!*************************************************************************************************************************************************************************!*\ + !*** ./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/modules/role-management/components/RolePermissionAssignment.vue?vue&type=script&lang=js ***! + \*************************************************************************************************************************************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) +/* harmony export */ }); +/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ "./node_modules/vue/dist/vue.runtime.esm-bundler.js"); +/* harmony import */ var _services_roleService_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../services/roleService.js */ "./src/renderer/modules/role-management/services/roleService.js"); + + + + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({ + name: 'RolePermissionAssignment', + props: { + modelValue: { + type: Boolean, + default: false + }, + role: { + type: Object, + default: null + } + }, + emits: ['update:modelValue', 'permissions-updated'], + setup(props, { emit }) { + const visible = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(false) + const loading = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(false) + const currentRole = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(null) + const allPermissions = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)([]) + const assignedPermissions = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)([]) + const selectedAssignedPermissions = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)([]) + const selectedAvailablePermissions = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)([]) + const activeTab = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)('assigned') + + // 计算可分配权限 + const availablePermissions = (0,vue__WEBPACK_IMPORTED_MODULE_0__.computed)(() => { + const assignedIds = assignedPermissions.value.map(p => p.id) + return allPermissions.value.filter(p => !assignedIds.includes(p.id)) + }) + + // 监听visible变化 + ;(0,vue__WEBPACK_IMPORTED_MODULE_0__.watch)(() => props.modelValue, (newVal) => { + visible.value = newVal + if (newVal && props.role) { + currentRole.value = props.role + loadData() + } + }) + + // 加载数据 + const loadData = async () => { + if (!currentRole.value) return + + loading.value = true + try { + await Promise.all([ + loadAllPermissions(), + loadRolePermissions() + ]) + } catch (error) { + console.error('加载权限数据失败:', error) + } finally { + loading.value = false + } + } + + // 加载所有权限 + const loadAllPermissions = async () => { + try { + const response = await _services_roleService_js__WEBPACK_IMPORTED_MODULE_1__["default"].getPermissions() + if (response.code === 200) { + allPermissions.value = response.data || [] + } + } catch (error) { + console.error('加载权限列表失败:', error) + } + } + + // 加载角色权限 + const loadRolePermissions = async () => { + try { + const response = await _services_roleService_js__WEBPACK_IMPORTED_MODULE_1__["default"].getRolePermissions(currentRole.value.id) + if (response.code === 200) { + assignedPermissions.value = response.data || [] + } + } catch (error) { + console.error('加载角色权限失败:', error) + } + } + + // 切换已分配权限选择 + const toggleAssignedPermission = (permissionId) => { + const index = selectedAssignedPermissions.value.indexOf(permissionId) + if (index > -1) { + selectedAssignedPermissions.value.splice(index, 1) + } else { + selectedAssignedPermissions.value.push(permissionId) + } + } + + // 切换可分配权限选择 + const toggleAvailablePermission = (permissionId) => { + const index = selectedAvailablePermissions.value.indexOf(permissionId) + if (index > -1) { + selectedAvailablePermissions.value.splice(index, 1) + } else { + selectedAvailablePermissions.value.push(permissionId) + } + } + + // 清除已分配权限选择 + const clearAssignedSelection = () => { + selectedAssignedPermissions.value = [] + } + + // 清除可分配权限选择 + const clearAvailableSelection = () => { + selectedAvailablePermissions.value = [] + } + + // 分配单个权限 + const assignPermission = async (permissionId) => { + try { + const response = await _services_roleService_js__WEBPACK_IMPORTED_MODULE_1__["default"].assignPermissionsToRole(currentRole.value.id, [permissionId]) + if (response.code === 200) { + await loadRolePermissions() + emit('permissions-updated') + } + } catch (error) { + console.error('分配权限失败:', error) + } + } + + // 移除单个权限 + const removePermission = async (permissionId) => { + try { + const response = await _services_roleService_js__WEBPACK_IMPORTED_MODULE_1__["default"].removePermissionsFromRole(currentRole.value.id, [permissionId]) + if (response.code === 200) { + await loadRolePermissions() + emit('permissions-updated') + } + } catch (error) { + console.error('移除权限失败:', error) + } + } + + // 批量分配权限 + const assignSelectedPermissions = async () => { + if (selectedAvailablePermissions.value.length === 0) return + + try { + const response = await _services_roleService_js__WEBPACK_IMPORTED_MODULE_1__["default"].assignPermissionsToRole( + currentRole.value.id, + selectedAvailablePermissions.value + ) + if (response.code === 200) { + selectedAvailablePermissions.value = [] + await loadRolePermissions() + emit('permissions-updated') + } + } catch (error) { + console.error('批量分配权限失败:', error) + } + } + + // 批量移除权限 + const removeSelectedPermissions = async () => { + if (selectedAssignedPermissions.value.length === 0) return + + try { + const response = await _services_roleService_js__WEBPACK_IMPORTED_MODULE_1__["default"].removePermissionsFromRole( + currentRole.value.id, + selectedAssignedPermissions.value + ) + if (response.code === 200) { + selectedAssignedPermissions.value = [] + await loadRolePermissions() + emit('permissions-updated') + } + } catch (error) { + console.error('批量移除权限失败:', error) + } + } + + // 刷新权限 + const refreshPermissions = async () => { + await loadData() + } + + // 关闭对话框 + const handleClose = () => { + visible.value = false + emit('update:modelValue', false) + // 重置状态 + selectedAssignedPermissions.value = [] + selectedAvailablePermissions.value = [] + activeTab.value = 'assigned' + } + + return { + visible, + loading, + currentRole, + allPermissions, + assignedPermissions, + availablePermissions, + selectedAssignedPermissions, + selectedAvailablePermissions, + activeTab, + toggleAssignedPermission, + toggleAvailablePermission, + clearAssignedSelection, + clearAvailableSelection, + assignPermission, + removePermission, + assignSelectedPermissions, + removeSelectedPermissions, + refreshPermissions, + handleClose + } + } +}); + + /***/ }), /***/ "./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/modules/role-management/components/UserRoleAssignment.vue?vue&type=script&lang=js": @@ -5994,17 +6555,24 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ }); /* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ "./node_modules/vue/dist/vue.runtime.esm-bundler.js"); /* harmony import */ var _services_roleService_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../services/roleService.js */ "./src/renderer/modules/role-management/services/roleService.js"); +/* harmony import */ var _components_RolePermissionAssignment_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../components/RolePermissionAssignment.vue */ "./src/renderer/modules/role-management/components/RolePermissionAssignment.vue"); + /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({ name: 'RoleManagement', + components: { + RolePermissionAssignment: _components_RolePermissionAssignment_vue__WEBPACK_IMPORTED_MODULE_2__["default"] + }, setup() { const loading = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(false) const saving = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(false) const showCreateDialog = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(false) + const showPermissionDialog = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(false) const editingRole = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(null) + const selectedRole = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(null) const roles = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)([]) const currentPage = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(1) @@ -6138,27 +6706,43 @@ __webpack_require__.r(__webpack_exports__); return new Date(dateStr).toLocaleString('zh-CN') } + // 分配权限 + const assignPermissions = (role) => { + selectedRole.value = role + showPermissionDialog.value = true + } + + // 处理权限更新 + const handlePermissionsUpdated = () => { + console.log('权限分配已更新') + // 可以在这里添加一些提示或刷新逻辑 + } + ;(0,vue__WEBPACK_IMPORTED_MODULE_0__.onMounted)(() => { loadRoles() }) - return { - loading, - saving, - showCreateDialog, - editingRole, - roles, - currentPage, - pageSize, - total, - roleForm, - createNewRole, - editRole, - saveRole, - deleteRole, - handleCurrentChange, - formatDate - } + return { + loading, + saving, + showCreateDialog, + showPermissionDialog, + editingRole, + selectedRole, + roles, + currentPage, + pageSize, + total, + roleForm, + createNewRole, + editRole, + saveRole, + deleteRole, + assignPermissions, + handlePermissionsUpdated, + handleCurrentChange, + formatDate + } } }); @@ -8524,6 +9108,304 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { /***/ }), +/***/ "./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/modules/role-management/components/RolePermissionAssignment.vue?vue&type=template&id=91c1b50a&scoped=true": +/*!*****************************************************************************************************************************************************************************************************************************************************************!*\ + !*** ./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/modules/role-management/components/RolePermissionAssignment.vue?vue&type=template&id=91c1b50a&scoped=true ***! + \*****************************************************************************************************************************************************************************************************************************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ render: () => (/* binding */ render) +/* harmony export */ }); +/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ "./node_modules/vue/dist/vue.runtime.esm-bundler.js"); + + +const _hoisted_1 = { class: "role-permission-assignment" } +const _hoisted_2 = { class: "modal-header" } +const _hoisted_3 = { class: "modal-body" } +const _hoisted_4 = { + key: 0, + class: "loading" +} +const _hoisted_5 = { + key: 1, + class: "permission-content" +} +const _hoisted_6 = { class: "role-info" } +const _hoisted_7 = { class: "role-details" } +const _hoisted_8 = { class: "role-description" } +const _hoisted_9 = { class: "role-stats" } +const _hoisted_10 = { class: "stat-item" } +const _hoisted_11 = { class: "stat-item" } +const _hoisted_12 = { class: "permission-assignment" } +const _hoisted_13 = { class: "assignment-tabs" } +const _hoisted_14 = { + key: 0, + class: "permission-list assigned-permissions" +} +const _hoisted_15 = { class: "list-header" } +const _hoisted_16 = { class: "list-actions" } +const _hoisted_17 = ["disabled"] +const _hoisted_18 = { + key: 0, + class: "empty-state" +} +const _hoisted_19 = { + key: 1, + class: "permission-items" +} +const _hoisted_20 = ["onClick"] +const _hoisted_21 = { class: "permission-checkbox" } +const _hoisted_22 = ["checked", "onChange"] +const _hoisted_23 = { class: "permission-info" } +const _hoisted_24 = { class: "permission-name" } +const _hoisted_25 = { class: "permission-details" } +const _hoisted_26 = { class: "permission-code" } +const _hoisted_27 = { class: "permission-resource" } +const _hoisted_28 = { class: "permission-description" } +const _hoisted_29 = { class: "permission-actions" } +const _hoisted_30 = ["onClick"] +const _hoisted_31 = { + key: 1, + class: "permission-list available-permissions" +} +const _hoisted_32 = { class: "list-header" } +const _hoisted_33 = { class: "list-actions" } +const _hoisted_34 = ["disabled"] +const _hoisted_35 = { + key: 0, + class: "empty-state" +} +const _hoisted_36 = { + key: 1, + class: "permission-items" +} +const _hoisted_37 = ["onClick"] +const _hoisted_38 = { class: "permission-checkbox" } +const _hoisted_39 = ["checked", "onChange"] +const _hoisted_40 = { class: "permission-info" } +const _hoisted_41 = { class: "permission-name" } +const _hoisted_42 = { class: "permission-details" } +const _hoisted_43 = { class: "permission-code" } +const _hoisted_44 = { class: "permission-resource" } +const _hoisted_45 = { class: "permission-description" } +const _hoisted_46 = { class: "permission-actions" } +const _hoisted_47 = ["onClick"] +const _hoisted_48 = { class: "modal-footer" } +const _hoisted_49 = ["disabled"] + +function render(_ctx, _cache, $props, $setup, $data, $options) { + return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_1, [ + ($setup.visible) + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", { + key: 0, + class: "modal-overlay", + onClick: _cache[10] || (_cache[10] = (...args) => ($setup.handleClose && $setup.handleClose(...args))) + }, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", { + class: "modal large-modal", + onClick: _cache[9] || (_cache[9] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)(() => {}, ["stop"])) + }, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_2, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, "角色权限分配 - " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.currentRole?.name), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "close-btn", + onClick: _cache[0] || (_cache[0] = (...args) => ($setup.handleClose && $setup.handleClose(...args))) + }, _cache[11] || (_cache[11] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-times" }, null, -1 /* CACHED */) + ])) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_3, [ + ($setup.loading) + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_4, _cache[12] || (_cache[12] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-spinner fa-spin" }, null, -1 /* CACHED */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", null, "加载中...", -1 /* CACHED */) + ]))) + : ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_5, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 角色信息 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_6, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_7, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h4", null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.currentRole?.name), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("p", _hoisted_8, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.currentRole?.description), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_9, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_10, [ + _cache[13] || (_cache[13] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-shield-alt" }, null, -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" 已分配权限: " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.assignedPermissions.length), 1 /* TEXT */) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_11, [ + _cache[14] || (_cache[14] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-list" }, null, -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" 总权限数: " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.allPermissions.length), 1 /* TEXT */) + ]) + ]) + ]) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 权限分配区域 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_12, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_13, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(["tab-btn", { active: $setup.activeTab === 'assigned' }]), + onClick: _cache[1] || (_cache[1] = $event => ($setup.activeTab = 'assigned')) + }, [ + _cache[15] || (_cache[15] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-check-circle" }, null, -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" 已分配权限 (" + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.assignedPermissions.length) + ") ", 1 /* TEXT */) + ], 2 /* CLASS */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(["tab-btn", { active: $setup.activeTab === 'available' }]), + onClick: _cache[2] || (_cache[2] = $event => ($setup.activeTab = 'available')) + }, [ + _cache[16] || (_cache[16] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-plus-circle" }, null, -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" 可分配权限 (" + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.availablePermissions.length) + ") ", 1 /* TEXT */) + ], 2 /* CLASS */) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 已分配权限列表 "), + ($setup.activeTab === 'assigned') + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_14, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_15, [ + _cache[18] || (_cache[18] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h5", null, "已分配权限", -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_16, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "btn btn-sm btn-danger", + onClick: _cache[3] || (_cache[3] = (...args) => ($setup.removeSelectedPermissions && $setup.removeSelectedPermissions(...args))), + disabled: $setup.selectedAssignedPermissions.length === 0 + }, [ + _cache[17] || (_cache[17] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-minus" }, null, -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" 移除选中 (" + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.selectedAssignedPermissions.length) + ") ", 1 /* TEXT */) + ], 8 /* PROPS */, _hoisted_17), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "btn btn-sm btn-secondary", + onClick: _cache[4] || (_cache[4] = (...args) => ($setup.clearAssignedSelection && $setup.clearAssignedSelection(...args))) + }, " 清除选择 ") + ]) + ]), + ($setup.assignedPermissions.length === 0) + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_18, _cache[19] || (_cache[19] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-inbox" }, null, -1 /* CACHED */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("p", null, "该角色暂无分配权限", -1 /* CACHED */) + ]))) + : ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_19, [ + ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($setup.assignedPermissions, (permission) => { + return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", { + key: permission.id, + class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(["permission-item", { selected: $setup.selectedAssignedPermissions.includes(permission.id) }]), + onClick: $event => ($setup.toggleAssignedPermission(permission.id)) + }, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_21, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", { + type: "checkbox", + checked: $setup.selectedAssignedPermissions.includes(permission.id), + onChange: $event => ($setup.toggleAssignedPermission(permission.id)) + }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_22) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_23, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_24, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(permission.name), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_25, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_26, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(permission.code), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_27, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(permission.resource), 1 /* TEXT */) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_28, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(permission.description), 1 /* TEXT */) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_29, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "btn btn-sm btn-danger", + onClick: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)($event => ($setup.removePermission(permission.id)), ["stop"]), + title: "移除权限" + }, [...(_cache[20] || (_cache[20] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-minus" }, null, -1 /* CACHED */) + ]))], 8 /* PROPS */, _hoisted_30) + ]) + ], 10 /* CLASS, PROPS */, _hoisted_20)) + }), 128 /* KEYED_FRAGMENT */)) + ])) + ])) + : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 可分配权限列表 "), + ($setup.activeTab === 'available') + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_31, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_32, [ + _cache[22] || (_cache[22] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h5", null, "可分配权限", -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_33, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "btn btn-sm btn-primary", + onClick: _cache[5] || (_cache[5] = (...args) => ($setup.assignSelectedPermissions && $setup.assignSelectedPermissions(...args))), + disabled: $setup.selectedAvailablePermissions.length === 0 + }, [ + _cache[21] || (_cache[21] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-plus" }, null, -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" 分配选中 (" + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.selectedAvailablePermissions.length) + ") ", 1 /* TEXT */) + ], 8 /* PROPS */, _hoisted_34), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "btn btn-sm btn-secondary", + onClick: _cache[6] || (_cache[6] = (...args) => ($setup.clearAvailableSelection && $setup.clearAvailableSelection(...args))) + }, " 清除选择 ") + ]) + ]), + ($setup.availablePermissions.length === 0) + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_35, _cache[23] || (_cache[23] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-check-circle" }, null, -1 /* CACHED */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("p", null, "所有权限已分配完毕", -1 /* CACHED */) + ]))) + : ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_36, [ + ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($setup.availablePermissions, (permission) => { + return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", { + key: permission.id, + class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(["permission-item", { selected: $setup.selectedAvailablePermissions.includes(permission.id) }]), + onClick: $event => ($setup.toggleAvailablePermission(permission.id)) + }, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_38, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", { + type: "checkbox", + checked: $setup.selectedAvailablePermissions.includes(permission.id), + onChange: $event => ($setup.toggleAvailablePermission(permission.id)) + }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_39) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_40, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_41, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(permission.name), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_42, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_43, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(permission.code), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_44, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(permission.resource), 1 /* TEXT */) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_45, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(permission.description), 1 /* TEXT */) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_46, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "btn btn-sm btn-primary", + onClick: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)($event => ($setup.assignPermission(permission.id)), ["stop"]), + title: "分配权限" + }, [...(_cache[24] || (_cache[24] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-plus" }, null, -1 /* CACHED */) + ]))], 8 /* PROPS */, _hoisted_47) + ]) + ], 10 /* CLASS, PROPS */, _hoisted_37)) + }), 128 /* KEYED_FRAGMENT */)) + ])) + ])) + : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true) + ]) + ])) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_48, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "btn btn-secondary", + onClick: _cache[7] || (_cache[7] = (...args) => ($setup.handleClose && $setup.handleClose(...args))) + }, "关闭"), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "btn btn-primary", + onClick: _cache[8] || (_cache[8] = (...args) => ($setup.refreshPermissions && $setup.refreshPermissions(...args))), + disabled: $setup.loading + }, _cache[25] || (_cache[25] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-sync-alt" }, null, -1 /* CACHED */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" 刷新权限 ", -1 /* CACHED */) + ]), 8 /* PROPS */, _hoisted_49) + ]) + ]) + ])) + : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true) + ])) +} + +/***/ }), + /***/ "./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/modules/role-management/components/UserRoleAssignment.vue?vue&type=template&id=7f7c6d92&scoped=true": /*!***********************************************************************************************************************************************************************************************************************************************************!*\ !*** ./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/modules/role-management/components/UserRoleAssignment.vue?vue&type=template&id=7f7c6d92&scoped=true ***! @@ -8710,29 +9592,32 @@ const _hoisted_6 = { key: 2 } const _hoisted_7 = { class: "actions" } const _hoisted_8 = ["onClick"] const _hoisted_9 = ["onClick"] -const _hoisted_10 = { +const _hoisted_10 = ["onClick"] +const _hoisted_11 = { key: 3, class: "pagination" } -const _hoisted_11 = ["disabled"] -const _hoisted_12 = { class: "page-info" } -const _hoisted_13 = ["disabled"] -const _hoisted_14 = { class: "modal-header" } -const _hoisted_15 = { class: "modal-body" } -const _hoisted_16 = { class: "form-group" } +const _hoisted_12 = ["disabled"] +const _hoisted_13 = { class: "page-info" } +const _hoisted_14 = ["disabled"] +const _hoisted_15 = { class: "modal-header" } +const _hoisted_16 = { class: "modal-body" } const _hoisted_17 = { class: "form-group" } const _hoisted_18 = { class: "form-group" } -const _hoisted_19 = { class: "form-actions" } -const _hoisted_20 = ["disabled"] +const _hoisted_19 = { class: "form-group" } +const _hoisted_20 = { class: "form-actions" } +const _hoisted_21 = ["disabled"] function render(_ctx, _cache, $props, $setup, $data, $options) { + const _component_RolePermissionAssignment = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)("RolePermissionAssignment") + return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_1, [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_2, [ - _cache[13] || (_cache[13] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h2", null, "角色管理", -1 /* CACHED */)), + _cache[14] || (_cache[14] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h2", null, "角色管理", -1 /* CACHED */)), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { class: "btn btn-primary", onClick: _cache[0] || (_cache[0] = (...args) => ($setup.createNewRole && $setup.createNewRole(...args))) - }, _cache[12] || (_cache[12] = [ + }, _cache[13] || (_cache[13] = [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-plus" }, null, -1 /* CACHED */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" 新建角色 ", -1 /* CACHED */) ])) @@ -8740,26 +9625,26 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 角色列表 "), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_3, [ ($setup.loading) - ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_4, _cache[14] || (_cache[14] = [ + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_4, _cache[15] || (_cache[15] = [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-spinner fa-spin" }, null, -1 /* CACHED */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", null, "加载中...", -1 /* CACHED */) ]))) : ($setup.roles.length === 0) ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_5, [ - _cache[16] || (_cache[16] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("p", null, [ + _cache[17] || (_cache[17] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("p", null, [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-inbox" }), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" 暂无角色数据") ], -1 /* CACHED */)), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { class: "btn btn-primary", onClick: _cache[1] || (_cache[1] = (...args) => ($setup.createNewRole && $setup.createNewRole(...args))) - }, _cache[15] || (_cache[15] = [ + }, _cache[16] || (_cache[16] = [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-plus" }, null, -1 /* CACHED */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" 创建第一个角色 ", -1 /* CACHED */) ])) ])) : ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("table", _hoisted_6, [ - _cache[19] || (_cache[19] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("thead", null, [ + _cache[21] || (_cache[21] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("thead", null, [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("tr", null, [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("th", null, "ID"), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("th", null, "角色名称"), @@ -8783,16 +9668,25 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_7, [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { class: "btn btn-sm btn-info", - onClick: $event => ($setup.editRole(role)) - }, [...(_cache[17] || (_cache[17] = [ + onClick: $event => ($setup.editRole(role)), + title: "编辑角色" + }, [...(_cache[18] || (_cache[18] = [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-edit" }, null, -1 /* CACHED */) ]))], 8 /* PROPS */, _hoisted_8), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "btn btn-sm btn-warning", + onClick: $event => ($setup.assignPermissions(role)), + title: "分配权限" + }, [...(_cache[19] || (_cache[19] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-shield-alt" }, null, -1 /* CACHED */) + ]))], 8 /* PROPS */, _hoisted_9), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { class: "btn btn-sm btn-danger", - onClick: $event => ($setup.deleteRole(role)) - }, [...(_cache[18] || (_cache[18] = [ + onClick: $event => ($setup.deleteRole(role)), + title: "删除角色" + }, [...(_cache[20] || (_cache[20] = [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-trash" }, null, -1 /* CACHED */) - ]))], 8 /* PROPS */, _hoisted_9) + ]))], 8 /* PROPS */, _hoisted_10) ]) ]) ])) @@ -8801,24 +9695,24 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { ])), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 分页 "), ($setup.total > $setup.pageSize) - ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_10, [ + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_11, [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { disabled: $setup.currentPage === 1, onClick: _cache[2] || (_cache[2] = $event => ($setup.handleCurrentChange($setup.currentPage - 1))), class: "btn btn-sm" - }, _cache[20] || (_cache[20] = [ + }, _cache[22] || (_cache[22] = [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-chevron-left" }, null, -1 /* CACHED */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" 上一页 ", -1 /* CACHED */) - ]), 8 /* PROPS */, _hoisted_11), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_12, " 第 " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.currentPage) + " 页,共 " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(Math.ceil($setup.total / $setup.pageSize)) + " 页 ", 1 /* TEXT */), + ]), 8 /* PROPS */, _hoisted_12), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_13, " 第 " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.currentPage) + " 页,共 " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(Math.ceil($setup.total / $setup.pageSize)) + " 页 ", 1 /* TEXT */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { disabled: $setup.currentPage >= Math.ceil($setup.total / $setup.pageSize), onClick: _cache[3] || (_cache[3] = $event => ($setup.handleCurrentChange($setup.currentPage + 1))), class: "btn btn-sm" - }, _cache[21] || (_cache[21] = [ + }, _cache[23] || (_cache[23] = [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" 下一页 ", -1 /* CACHED */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-chevron-right" }, null, -1 /* CACHED */) - ]), 8 /* PROPS */, _hoisted_13) + ]), 8 /* PROPS */, _hoisted_14) ])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true) ]), @@ -8833,21 +9727,21 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { class: "modal", onClick: _cache[10] || (_cache[10] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)(() => {}, ["stop"])) }, [ - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_14, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_15, [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.editingRole ? '编辑角色' : '新建角色'), 1 /* TEXT */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { class: "close-btn", onClick: _cache[4] || (_cache[4] = $event => ($setup.showCreateDialog = false)) - }, _cache[22] || (_cache[22] = [ + }, _cache[24] || (_cache[24] = [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "fas fa-times" }, null, -1 /* CACHED */) ])) ]), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_15, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_16, [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("form", { onSubmit: _cache[9] || (_cache[9] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)((...args) => ($setup.saveRole && $setup.saveRole(...args)), ["prevent"])) }, [ - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_16, [ - _cache[23] || (_cache[23] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "角色名称 *", -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_17, [ + _cache[25] || (_cache[25] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "角色名称 *", -1 /* CACHED */)), (0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", { "onUpdate:modelValue": _cache[5] || (_cache[5] = $event => (($setup.roleForm.name) = $event)), type: "text", @@ -8857,8 +9751,8 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { [vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.roleForm.name] ]) ]), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_17, [ - _cache[24] || (_cache[24] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "角色代码 *", -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_18, [ + _cache[26] || (_cache[26] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "角色代码 *", -1 /* CACHED */)), (0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", { "onUpdate:modelValue": _cache[6] || (_cache[6] = $event => (($setup.roleForm.code) = $event)), type: "text", @@ -8868,8 +9762,8 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { [vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.roleForm.code] ]) ]), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_18, [ - _cache[25] || (_cache[25] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "描述", -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_19, [ + _cache[27] || (_cache[27] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "描述", -1 /* CACHED */)), (0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("textarea", { "onUpdate:modelValue": _cache[7] || (_cache[7] = $event => (($setup.roleForm.description) = $event)), rows: "3", @@ -8878,7 +9772,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { [vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.roleForm.description] ]) ]), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_19, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_20, [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { type: "button", class: "btn btn-secondary", @@ -8888,13 +9782,20 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { type: "submit", class: "btn btn-primary", disabled: $setup.saving - }, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.saving ? '保存中...' : ($setup.editingRole ? '更新' : '创建')), 9 /* TEXT, PROPS */, _hoisted_20) + }, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.saving ? '保存中...' : ($setup.editingRole ? '更新' : '创建')), 9 /* TEXT, PROPS */, _hoisted_21) ]) ], 32 /* NEED_HYDRATION */) ]) ]) ])) - : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true) + : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 权限分配对话框 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_RolePermissionAssignment, { + modelValue: $setup.showPermissionDialog, + "onUpdate:modelValue": _cache[12] || (_cache[12] = $event => (($setup.showPermissionDialog) = $event)), + role: $setup.selectedRole, + onPermissionsUpdated: $setup.handlePermissionsUpdated + }, null, 8 /* PROPS */, ["modelValue", "role", "onPermissionsUpdated"]) ])) } @@ -10356,6 +11257,39 @@ if(true) { /***/ }), +/***/ "./node_modules/vue-style-loader/index.js??clonedRuleSet-12.use[0]!./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/modules/role-management/components/RolePermissionAssignment.vue?vue&type=style&index=0&id=91c1b50a&scoped=true&lang=css": +/*!***************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\ + !*** ./node_modules/vue-style-loader/index.js??clonedRuleSet-12.use[0]!./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/modules/role-management/components/RolePermissionAssignment.vue?vue&type=style&index=0&id=91c1b50a&scoped=true&lang=css ***! + \***************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +// style-loader: Adds some css to the DOM by adding a diff --git a/gofaster/app/src/renderer/modules/role-management/services/roleService.js b/gofaster/app/src/renderer/modules/role-management/services/roleService.js index a0ea8bd..b7a5f2f 100644 --- a/gofaster/app/src/renderer/modules/role-management/services/roleService.js +++ b/gofaster/app/src/renderer/modules/role-management/services/roleService.js @@ -136,6 +136,40 @@ export const roleService = { } catch (error) { throw error } + }, + + // 获取角色的权限列表 + async getRolePermissions(roleId) { + try { + const response = await api.get(`/auth/permissions/roles/${roleId}`) + return response + } catch (error) { + throw error + } + }, + + // 为角色分配权限 + async assignPermissionsToRole(roleId, permissionIds) { + try { + const response = await api.post(`/auth/permissions/roles/${roleId}/assign`, { + permission_ids: permissionIds + }) + return response + } catch (error) { + throw error + } + }, + + // 从角色移除权限 + async removePermissionsFromRole(roleId, permissionIds) { + try { + const response = await api.delete(`/auth/permissions/roles/${roleId}/remove`, { + data: { permission_ids: permissionIds } + }) + return response + } catch (error) { + throw error + } } } diff --git a/gofaster/app/src/renderer/modules/role-management/views/RoleManagement.vue b/gofaster/app/src/renderer/modules/role-management/views/RoleManagement.vue index f28cfdf..4cf1bdb 100644 --- a/gofaster/app/src/renderer/modules/role-management/views/RoleManagement.vue +++ b/gofaster/app/src/renderer/modules/role-management/views/RoleManagement.vue @@ -41,12 +41,15 @@ {{ formatDate(role.created_at) }}
- - + + +
@@ -124,20 +127,33 @@ + + + @@ -445,6 +477,10 @@ th { color: #d32f2f; } +.actions .btn-sm.btn-warning:hover { + color: #ff9800; +} + .btn:hover { opacity: 0.9; } diff --git a/gofaster/backend/internal/auth/migration/add_permission_code_field.go b/gofaster/backend/internal/auth/migration/add_permission_code_field.go new file mode 100644 index 0000000..dd7d6cf --- /dev/null +++ b/gofaster/backend/internal/auth/migration/add_permission_code_field.go @@ -0,0 +1,58 @@ +package migration + +import ( + "fmt" + "gofaster/internal/auth/model" + + "gorm.io/gorm" +) + +// AddPermissionCodeField 为权限表添加code字段 +func AddPermissionCodeField(db *gorm.DB) error { + fmt.Println("🔧 开始为权限表添加code字段...") + + // 检查code字段是否已存在 + var count int64 + err := db.Raw("SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'permissions' AND column_name = 'code'").Count(&count).Error + if err != nil { + return fmt.Errorf("检查code字段失败: %v", err) + } + + if count > 0 { + fmt.Println("✅ code字段已存在,跳过添加") + return nil + } + + // 添加code字段 + err = db.Exec("ALTER TABLE permissions ADD COLUMN code VARCHAR(50)").Error + if err != nil { + return fmt.Errorf("添加code字段失败: %v", err) + } + + // 为现有权限记录生成code + var permissions []model.Permission + err = db.Find(&permissions).Error + if err != nil { + return fmt.Errorf("查询现有权限失败: %v", err) + } + + fmt.Printf("🔧 为 %d 条现有权限记录生成code...\n", len(permissions)) + + for _, permission := range permissions { + code := fmt.Sprintf("%s:%s", permission.Resource, permission.Action) + err = db.Model(&permission).Update("code", code).Error + if err != nil { + return fmt.Errorf("更新权限 %d 的code失败: %v", permission.ID, err) + } + fmt.Printf("✅ 已为权限 '%s' 生成code: %s\n", permission.Name, code) + } + + // 添加唯一索引 + err = db.Exec("CREATE UNIQUE INDEX idx_permissions_code ON permissions(code)").Error + if err != nil { + return fmt.Errorf("创建code唯一索引失败: %v", err) + } + + fmt.Println("✅ 权限表code字段添加完成") + return nil +} diff --git a/gofaster/backend/internal/auth/migration/migration.go b/gofaster/backend/internal/auth/migration/migration.go index 6e5e32e..c1fe476 100644 --- a/gofaster/backend/internal/auth/migration/migration.go +++ b/gofaster/backend/internal/auth/migration/migration.go @@ -52,6 +52,19 @@ func RunMigrations(db *gorm.DB) error { return err } + // 自动迁移权限相关表 + if err := db.AutoMigrate( + &model.Permission{}, + &model.RolePermission{}, + ); err != nil { + return err + } + + // 为权限表添加code字段 + if err := AddPermissionCodeField(db); err != nil { + return err + } + // 创建默认角色 if err := createDefaultRoles(db); err != nil { return err @@ -67,6 +80,11 @@ func RunMigrations(db *gorm.DB) error { return err } + // 创建默认权限 + if err := createDefaultPermissions(db); err != nil { + return err + } + return nil } @@ -194,3 +212,134 @@ func createDefaultPasswordPolicy(db *gorm.DB) error { return nil } + +// createDefaultPermissions 创建默认权限 +func createDefaultPermissions(db *gorm.DB) error { + // 检查是否已存在权限 + var count int64 + db.Model(&model.Permission{}).Count(&count) + if count > 0 { + return nil // 已存在权限,跳过 + } + + permissions := []model.Permission{ + // 用户管理权限 + { + Name: "查看用户列表", + Code: "user:list", + Description: "查看系统用户列表", + Resource: "user", + Action: "list", + }, + { + Name: "创建用户", + Code: "user:create", + Description: "创建新用户", + Resource: "user", + Action: "create", + }, + { + Name: "编辑用户", + Code: "user:update", + Description: "编辑用户信息", + Resource: "user", + Action: "update", + }, + { + Name: "删除用户", + Code: "user:delete", + Description: "删除用户", + Resource: "user", + Action: "delete", + }, + // 角色管理权限 + { + Name: "查看角色列表", + Code: "role:list", + Description: "查看系统角色列表", + Resource: "role", + Action: "list", + }, + { + Name: "创建角色", + Code: "role:create", + Description: "创建新角色", + Resource: "role", + Action: "create", + }, + { + Name: "编辑角色", + Code: "role:update", + Description: "编辑角色信息", + Resource: "role", + Action: "update", + }, + { + Name: "删除角色", + Code: "role:delete", + Description: "删除角色", + Resource: "role", + Action: "delete", + }, + { + Name: "分配角色权限", + Code: "role:assign_permissions", + Description: "为角色分配权限", + Resource: "role", + Action: "assign_permissions", + }, + // 权限管理权限 + { + Name: "查看权限列表", + Code: "permission:list", + Description: "查看系统权限列表", + Resource: "permission", + Action: "list", + }, + { + Name: "创建权限", + Code: "permission:create", + Description: "创建新权限", + Resource: "permission", + Action: "create", + }, + { + Name: "编辑权限", + Code: "permission:update", + Description: "编辑权限信息", + Resource: "permission", + Action: "update", + }, + { + Name: "删除权限", + Code: "permission:delete", + Description: "删除权限", + Resource: "permission", + Action: "delete", + }, + // 系统管理权限 + { + Name: "系统设置", + Code: "system:settings", + Description: "管理系统设置", + Resource: "system", + Action: "settings", + }, + { + Name: "查看系统日志", + Code: "system:logs", + Description: "查看系统日志", + Resource: "system", + Action: "logs", + }, + } + + for _, permission := range permissions { + if err := db.Create(&permission).Error; err != nil { + return err + } + } + + fmt.Println("✅ 默认权限创建完成") + return nil +} diff --git a/gofaster/backend/internal/auth/model/permission.go b/gofaster/backend/internal/auth/model/permission.go index 5d4c49c..99ef6ad 100644 --- a/gofaster/backend/internal/auth/model/permission.go +++ b/gofaster/backend/internal/auth/model/permission.go @@ -7,6 +7,7 @@ import ( type Permission struct { model.BaseModel Name string `gorm:"uniqueIndex;size:50" json:"name"` + Code string `gorm:"uniqueIndex;size:50" json:"code"` Description string `gorm:"size:200" json:"description"` Resource string `gorm:"size:100" json:"resource"` Action string `gorm:"size:50" json:"action"` // create, read, update, delete等 diff --git a/gofaster/backend/internal/auth/repository/permission_repo.go b/gofaster/backend/internal/auth/repository/permission_repo.go index e06a93c..50b8056 100644 --- a/gofaster/backend/internal/auth/repository/permission_repo.go +++ b/gofaster/backend/internal/auth/repository/permission_repo.go @@ -15,6 +15,7 @@ type PermissionRepository interface { Delete(ctx context.Context, id uint) error GetByID(ctx context.Context, id uint) (*model.Permission, error) GetByName(ctx context.Context, name string) (*model.Permission, error) + GetByCode(ctx context.Context, code string) (*model.Permission, error) List(ctx context.Context, offset, limit int) ([]*model.Permission, int64, error) GetByResource(ctx context.Context, resource string) ([]*model.Permission, error) GetByUserID(ctx context.Context, userID uint) ([]*model.Permission, error) @@ -61,6 +62,15 @@ func (r *permissionRepository) GetByName(ctx context.Context, name string) (*mod return &permission, nil } +func (r *permissionRepository) GetByCode(ctx context.Context, code string) (*model.Permission, error) { + var permission model.Permission + err := r.DB().WithContext(ctx).Where("code = ?", code).First(&permission).Error + if err != nil { + return nil, err + } + return &permission, nil +} + func (r *permissionRepository) List(ctx context.Context, offset, limit int) ([]*model.Permission, int64, error) { var permissions []*model.Permission var total int64 diff --git a/gofaster/backend/internal/auth/routes/auth_routes.go b/gofaster/backend/internal/auth/routes/auth_routes.go index 28906db..b1be056 100644 --- a/gofaster/backend/internal/auth/routes/auth_routes.go +++ b/gofaster/backend/internal/auth/routes/auth_routes.go @@ -82,8 +82,8 @@ func RegisterAuthRoutes(router *gin.RouterGroup, db *gorm.DB, jwtSecret string) // 注册资源管理路由 RegisterResourceRoutes(router, db, jwtSecret) - // 注册权限管理路由 - RegisterPermissionRoutes(router, db, jwtSecret) + // 注册权限管理路由 - 注册到 /auth 组下 + RegisterPermissionRoutes(auth, db, jwtSecret) // 注册角色管理路由 RegisterRoleRoutes(router, db, jwtSecret) diff --git a/gofaster/backend/internal/auth/service/permission_service.go b/gofaster/backend/internal/auth/service/permission_service.go index 4199c0c..462a5be 100644 --- a/gofaster/backend/internal/auth/service/permission_service.go +++ b/gofaster/backend/internal/auth/service/permission_service.go @@ -32,6 +32,20 @@ func (s *PermissionService) CreatePermission(ctx context.Context, permission *mo return fmt.Errorf("权限名称 %s 已存在", permission.Name) } + // 如果code为空,自动生成code + if permission.Code == "" { + permission.Code = fmt.Sprintf("%s:%s", permission.Resource, permission.Action) + } + + // 检查权限代码是否已存在 + existingCode, err := s.permissionRepo.GetByCode(ctx, permission.Code) + if err != nil && err != gorm.ErrRecordNotFound { + return fmt.Errorf("检查权限代码失败: %v", err) + } + if existingCode != nil { + return fmt.Errorf("权限代码 %s 已存在", permission.Code) + } + return s.permissionRepo.Create(ctx, permission) } diff --git a/gofaster/test-permission-assignment.ps1 b/gofaster/test-permission-assignment.ps1 new file mode 100644 index 0000000..fec1771 --- /dev/null +++ b/gofaster/test-permission-assignment.ps1 @@ -0,0 +1,41 @@ +# 测试权限分配功能脚本 +Write-Host "🧪 开始测试权限分配功能..." -ForegroundColor Yellow + +# 启动后端服务 +Write-Host "🚀 启动后端服务..." -ForegroundColor Green +Start-Process -FilePath "powershell" -ArgumentList "-ExecutionPolicy", "Bypass", "-File", "start-backend-only.ps1" -WindowStyle Minimized + +# 等待后端启动 +Write-Host "⏳ 等待后端服务启动..." -ForegroundColor Cyan +Start-Sleep -Seconds 10 + +# 测试权限API +Write-Host "🔍 测试权限API..." -ForegroundColor Green + +# 测试获取权限列表 +Write-Host "📋 测试获取权限列表..." -ForegroundColor Cyan +try { + $permissionsResponse = Invoke-RestMethod -Uri "http://localhost:8080/api/auth/permissions" -Method GET -Headers @{ + "Authorization" = "Bearer YOUR_TOKEN_HERE" + } -ErrorAction Stop + Write-Host "✅ 权限列表API正常" -ForegroundColor Green + Write-Host "权限数量: $($permissionsResponse.data.Count)" -ForegroundColor Cyan +} catch { + Write-Host "❌ 权限列表API测试失败: $($_.Exception.Message)" -ForegroundColor Red +} + +# 测试获取角色列表 +Write-Host "👥 测试获取角色列表..." -ForegroundColor Cyan +try { + $rolesResponse = Invoke-RestMethod -Uri "http://localhost:8080/api/auth/roles" -Method GET -Headers @{ + "Authorization" = "Bearer YOUR_TOKEN_HERE" + } -ErrorAction Stop + Write-Host "✅ 角色列表API正常" -ForegroundColor Green + Write-Host "角色数量: $($rolesResponse.data.data.Count)" -ForegroundColor Cyan +} catch { + Write-Host "❌ 角色列表API测试失败: $($_.Exception.Message)" -ForegroundColor Red +} + +Write-Host "🎉 权限分配功能测试完成!" -ForegroundColor Green +Write-Host "现在可以启动前端应用进行界面测试:" -ForegroundColor Cyan +Write-Host "cd app && npm run dev:enhanced" -ForegroundColor Yellow diff --git a/gofaster/test-permission-endpoint.ps1 b/gofaster/test-permission-endpoint.ps1 new file mode 100644 index 0000000..cc15d6d --- /dev/null +++ b/gofaster/test-permission-endpoint.ps1 @@ -0,0 +1,49 @@ +# 测试权限端点 +Write-Host "🔍 测试权限端点..." -ForegroundColor Yellow + +# 启动后端服务 +Write-Host "🚀 启动后端服务..." -ForegroundColor Green +Start-Process -FilePath "go" -ArgumentList "run", "main.go" -WorkingDirectory "backend" -WindowStyle Hidden + +# 等待服务启动 +Write-Host "⏳ 等待服务启动..." -ForegroundColor Yellow +Start-Sleep -Seconds 5 + +try { + # 测试权限端点 + Write-Host "🔍 测试 /api/auth/permissions 端点..." -ForegroundColor Cyan + $response = Invoke-RestMethod -Uri "http://localhost:8080/api/auth/permissions" -Method GET -ErrorAction Stop + Write-Host "✅ 权限端点响应:" -ForegroundColor Green + $response | ConvertTo-Json -Depth 3 +} catch { + Write-Host "❌ 权限端点测试失败:" -ForegroundColor Red + Write-Host $_.Exception.Message -ForegroundColor Red + + # 测试其他端点 + Write-Host "🔍 测试 /api/auth/roles 端点..." -ForegroundColor Cyan + try { + $response = Invoke-RestMethod -Uri "http://localhost:8080/api/auth/roles" -Method GET -ErrorAction Stop + Write-Host "✅ 角色端点响应:" -ForegroundColor Green + $response | ConvertTo-Json -Depth 3 + } catch { + Write-Host "❌ 角色端点也失败:" -ForegroundColor Red + Write-Host $_.Exception.Message -ForegroundColor Red + } + + # 测试健康检查端点 + Write-Host "🔍 测试 /health 端点..." -ForegroundColor Cyan + try { + $response = Invoke-RestMethod -Uri "http://localhost:8080/health" -Method GET -ErrorAction Stop + Write-Host "✅ 健康检查端点响应:" -ForegroundColor Green + $response | ConvertTo-Json -Depth 3 + } catch { + Write-Host "❌ 健康检查端点也失败:" -ForegroundColor Red + Write-Host $_.Exception.Message -ForegroundColor Red + } +} finally { + # 停止后端服务 + Write-Host "🛑 停止后端服务..." -ForegroundColor Yellow + Get-Process -Name "go" -ErrorAction SilentlyContinue | Stop-Process -Force +} + +Write-Host "✅ 测试完成" -ForegroundColor Green