From 6761ab20917bdf420135a472c6c2d96fa0840047 Mon Sep 17 00:00:00 2001 From: smail Date: Sat, 26 Sep 2026 12:05:47 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E9=BD=90=E6=96=B0=E5=A2=9E=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E9=85=8D=E7=BD=AE=E5=85=A5=E5=8F=A3=E5=8F=8A=E5=88=86?= =?UTF-8?q?=E7=BB=84=E4=BA=8B=E5=8A=A1=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/controllers/user.go | 48 +++++++++++++++++- .../SystemNotification/SystemNotification.js | 6 +-- .../SystemNotification/SystemNotification.vue | 6 ++- .../UserManagement/UserManagement.css | 20 ++++++++ .../UserManagement/UserManagement.js | 31 +++++++++--- .../UserManagement/UserManagement.vue | 49 +++++++++++++++---- 6 files changed, 137 insertions(+), 23 deletions(-) diff --git a/backend/controllers/user.go b/backend/controllers/user.go index 67c4bf9..b2e39ec 100644 --- a/backend/controllers/user.go +++ b/backend/controllers/user.go @@ -44,6 +44,7 @@ func decodeUserRequest(w http.ResponseWriter, req *http.Request, dst any) error } type userCreateRequest struct { + GroupIDs *[]int `json:"group_ids"` Id int `json:"id"` Account string `json:"account"` Username string `json:"username"` @@ -93,6 +94,15 @@ func CreateUser(ctx *context.Context, w http.ResponseWriter, req *http.Request) } user := models.User{Name: username, Account: account, Disabled: reqData.Disabled, SessionVersion: 1, SecurityVersion: 1} user.ApplyRole(targetRole) + groupIDs := []int{0} + if reqData.GroupIDs != nil { + groupIDs = *reqData.GroupIDs + } + if len(groupIDs) > 0 && (len(groupIDs) != 1 || groupIDs[0] != 0) { + if !requireAccountGroupManagement(ctx, w) { + return + } + } if err := auth.SetPassword(&user, reqData.Password); err != nil { if writePasswordBusy(w, err) { return @@ -105,12 +115,46 @@ func CreateUser(ctx *context.Context, w http.ResponseWriter, req *http.Request) response.NewErrorResponse(response.ServerError, "密码设置失败", "").FPrint(w) return } - if _, err := db.Instance.Insert(&user); err != nil { + if db.Instance == nil { + writeAccountGroupError(w, accountgroup.ErrDenied) + return + } + tx := db.Instance.NewSession() + defer tx.Close() + if err := tx.Begin(); err != nil { + writeAccountGroupError(w, err) + return + } + defer tx.Rollback() + actor := models.User{ID: ctx.UserID, SessionVersion: ctx.SessionVersion, SecurityVersion: ctx.SecurityVersion} + actor.ApplyRole(ctx.EffectiveRole()) + if err := accountgroup.CheckWrite(tx, accountWriteGuard(ctx, actor)); err != nil { + writeAccountGroupError(w, err) + return + } + groupIDs, err = accountgroup.ValidateMembership(tx, user.EffectiveRole(), groupIDs) + if err != nil { + writeAccountGroupError(w, err) + return + } + if _, err := tx.Insert(&user); err != nil { log.WithContext(ctx).WithError(err).Error("create user failed") response.NewErrorResponse(response.ServerError, "创建用户失败", "").FPrint(w) return } - response.NewSuccessResponse(user).FPrint(w) + if err := accountgroup.ReplaceMembership(tx, user.ID, groupIDs); err != nil { + writeAccountGroupError(w, err) + return + } + if err := tx.Commit(); err != nil { + writeAccountGroupError(w, err) + return + } + response.NewSuccessResponse(struct { + models.User + GroupIDs []int + MembershipVersion int + }{user, groupIDs, user.SecurityVersion}).FPrint(w) } type userListRequest struct { diff --git a/frontend/src/views/components/SystemNotification/SystemNotification.js b/frontend/src/views/components/SystemNotification/SystemNotification.js index 937ba1e..34b25c0 100644 --- a/frontend/src/views/components/SystemNotification/SystemNotification.js +++ b/frontend/src/views/components/SystemNotification/SystemNotification.js @@ -4,7 +4,7 @@ import { http } from '@/utils/axios' import { useGlobalStatusStore } from '@/stores/useGlobalStatusStore' import { isRecentAuthenticationCancelled, requestWithRecentAuthentication } from '@/utils/recentAuthentication' -export function usePage() { +export function usePage(props = {}) { const globalStatus = useGlobalStatusStore() const manager = computed(() => globalStatus.hasCapability('notification.manage')) const loading = ref(false) @@ -14,7 +14,7 @@ export function usePage() { const accounts = ref([]) const page = ref(1) const total = ref(0) - const selectedID = ref(Number(globalStatus.userInfos?.id || 0)) + const selectedID = ref(Number(props.userId || globalStatus.userInfos?.id || 0)) const form = reactive({ enabled: false, minute_limit: 10, day_limit: 100 }) const secret = ref('') const secretAccount = ref('') @@ -70,7 +70,7 @@ export function usePage() { } } const loadAccounts = async () => { - if (!manager.value) return + if (!manager.value || props.userId) return const sequence = ++accountsSequence try { const data = dataOf(await http.get('/api/v1/system/notification/accounts', { params: { page: page.value } })) diff --git a/frontend/src/views/components/SystemNotification/SystemNotification.vue b/frontend/src/views/components/SystemNotification/SystemNotification.vue index ac23aa9..f3feaf3 100644 --- a/frontend/src/views/components/SystemNotification/SystemNotification.vue +++ b/frontend/src/views/components/SystemNotification/SystemNotification.vue @@ -16,7 +16,7 @@ :closable="false" /> -
+

账号授权管理

@@ -111,11 +111,13 @@ diff --git a/frontend/src/views/components/UserManagement/UserManagement.css b/frontend/src/views/components/UserManagement/UserManagement.css index ea99906..904beb4 100644 --- a/frontend/src/views/components/UserManagement/UserManagement.css +++ b/frontend/src/views/components/UserManagement/UserManagement.css @@ -305,3 +305,23 @@ flex: 0 0 auto; margin: 0; } + +.account-form { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + column-gap: 18px; +} + +.account-form-wide { + grid-column: 1 / -1; +} + +.account-form > :deep(.el-form-item) { + min-width: 0; +} + +@media (max-width: 560px) { + .account-form { + grid-template-columns: minmax(0, 1fr); + } +} diff --git a/frontend/src/views/components/UserManagement/UserManagement.js b/frontend/src/views/components/UserManagement/UserManagement.js index 1aed045..2cc1ff1 100644 --- a/frontend/src/views/components/UserManagement/UserManagement.js +++ b/frontend/src/views/components/UserManagement/UserManagement.js @@ -67,7 +67,7 @@ export function usePage() { const selectedMembershipGroups = computed(() => { const user = selectedUser.value - if (!user) return [] + if (!user && editModel.value !== 'create') return [] return groups.accountGroups.value.filter(group => groups.canManageGroups.value ? editingGroupIDs.value.includes(group.id) @@ -76,8 +76,8 @@ export function usePage() { }) const openMembershipDialog = () => { - if (submitting.value || !selectedUser.value) return - groups.editMembership(selectedUser.value, { + if (submitting.value || (!selectedUser.value && editModel.value !== 'create')) return + groups.editMembership(selectedUser.value || { Role: editUserInfo.role }, { role: editUserInfo.role, groupIDs: editingGroupIDs.value, onApply: ids => { editingGroupIDs.value = ids } @@ -88,6 +88,13 @@ export function usePage() { const externalEmailDialogVisible = ref(false) + const notificationDialogVisible = ref(false) + const notificationPanel = ref(null) + const canManageNotification = computed(() => globalStatus.hasCapability('notification.manage')) + const closeNotificationDialog = async done => { + if (await notificationPanel.value?.beforeLeave()) done() + } + const securityDialogOpening = ref('') const submitting = ref(false) @@ -148,6 +155,8 @@ export function usePage() { const createUser = function () { if (!canCreateUser.value) return selectedUser.value = null + editingGroupIDs.value = [0] + editingMembershipVersion.value = 0 editUserInfo.account = "" editUserInfo.name = "" editUserInfo.disabled = false @@ -206,12 +215,12 @@ export function usePage() { } if (creating || roleChanged.value) newData.role = editUserInfo.role if (creating || editUserInfo.password !== '') newData.password = editUserInfo.password - if (!creating && groups.canManageGroups.value && (membershipChanged.value || roleChanged.value)) { + if (groups.canManageGroups.value && (creating || membershipChanged.value || roleChanged.value)) { if (editUserInfo.role !== 'admin' && editingGroupIDs.value.length !== 1) { throw new Error('普通用户只能属于一个分组,请在分组管理中选择一个分组') } newData.group_ids = [...editingGroupIDs.value] - newData.membership_version = editingMembershipVersion.value + if (!creating) newData.membership_version = editingMembershipVersion.value } const res = await requestWithRecentAuthentication(() => creating ? http.post('/api/v1/user/create', newData) @@ -221,8 +230,12 @@ export function usePage() { } ElNotification({ title: lang.succ, message: '用户信息已保存', type: 'success' }) editUserInfo.password = '' - reflushList() - userInfoDialog.value = false + if (creating) { + handleEdit(0, res.data) + } else { + userInfoDialog.value = false + } + await reflushList() } catch (error) { if (!isRecentAuthenticationCancelled(error)) { ElNotification({ @@ -262,6 +275,10 @@ export function usePage() { recoveryCodeWebManaged, recoveryCodeDialogVisible, externalEmailDialogVisible, + notificationDialogVisible, + notificationPanel, + canManageNotification, + closeNotificationDialog, securityDialogOpening, openRecoveryCodeDialog, openExternalEmailDialog, diff --git a/frontend/src/views/components/UserManagement/UserManagement.vue b/frontend/src/views/components/UserManagement/UserManagement.vue index 3ddd3cd..4452fc6 100644 --- a/frontend/src/views/components/UserManagement/UserManagement.vue +++ b/frontend/src/views/components/UserManagement/UserManagement.vue @@ -162,14 +162,14 @@
- +